Java基础学习笔记(十三)—— 抽象类与接口

Java基础学习笔记(十三)—— 抽象类与接口

Nothing just happens,it's all part of a plan.

| @Author:TTODS


为什么要使用抽象类?

假设我们有一个2D画版,我们可以在画版上画出各种形状。我们先建一个Shape类,它有成员变量 color,draw方法,clear方法等

public class Shape{
	String color;
	Shape(){
		color = "Black";
	}
	void draw() {} //由于Shape本身是不明确的所以无法绘制
	public void clear(){}
	public void setColor(String c) {
		color = c;
	}
	public String getColor(String c) {
		return color;
	}
}

然后为Shape类建两个子类,Circle类和Rectangle

public class Circle extends Shape{
	private int radius;
	Circle(int r){
		radius  = r;
	}
	void draw() {
		System.out.printf("在画版上绘制了一个半径为 %d,颜色为 %s 的圆\n",radius,color);
	}
	void clear() {
		System.out.println("成功擦去画版上的圆");
	}
}
public class Rectangle extends Shape {
	private int length,width;
	Rectangle(int l,int w){
		length = l;
		width = w;
	}
	void draw() {
		System.out.printf("在画版上绘制了一个长为 %d,宽为%d,颜色为 %s 的矩形\n", length,width,color);
	}
	void clear() {
		System.out.println("成功擦去画版上的矩形");
	}
}

新建一个Painting类,在main方法中新建Circle类、Rectangle类和Shape类的实例,并分别调用它们的draw方法和clear方法。

public class Painting{
	public static void main(String[] args) {
		Circle c = new Circle(10);
		Rectangle r = new Rectangle(10,5);
		Shape s = new Shape();
		r.setColor("red");
		c.draw();
		c.clear();
		r.draw();
		r.clear();
		s.draw();//Shape的draw方法体为空
		s.clear();//Shape的clear方法体为空
	}
}

输出

在画版上绘制了一个半径为 10,颜色为 Black 的圆
成功擦去画版上的圆
在画版上绘制了一个长为 10,宽为5,颜色为 red 的矩形
成功擦去画版上的矩形

从上例中我们发现Shape类的draw方法和clear方法的方法体都是空的,因为Shape本来就是一个概念性的东西,无法在画版上画出,换句话说在本例中将Shape的实例化实际上没有任何意义。对于像Shapedrawclear这种在类中没有具体实现,要在子类中来实现的方法,我们可以将其声明为抽象方法,而一个含有抽象方法的类,必须要声明为抽象类

抽象类

抽象方法的声明:

abstract void  f();

抽象类的定义:

abstract class ClassName{
	abstract void f()
}

使用抽象类应该注意的点:

  1. 一个类如果包含了一个或多个抽象方法,那这个类必须是抽象类。
  2. 一个类即使没有抽象的方法,它也可以被声明为抽象类(通常被用来防止该类被实例化)
  3. 一个抽象类的子类必须要实现父类中所有的抽象方法,否则子类也必须声明为抽象类(这很容易推导,假设子类继承了父类的一个抽象方法,却没有将其实现,那子类也包含了抽象方法)
  4. 抽象方法无法被声明是private的,事实上抽象方法只能是public(默认),protected

什么是接口?

接口是比抽象类更加抽象的类,或者说抽象类是介于普通类与接口之间的手段。
接口是完全抽象的类,不提供任何已实现的方法(这是java8之前的说法,因为java8中,允许接口包含静态方法和默认方法)。

接口的创建

接口的创建于类类似,使用interface关键字代替class关键字

public interface Shape{
	 void draw();
	 void clear();
}

在实现接口时我们使用implements来指定接口,若有多个接口有逗号(,)隔开

public class Circle implements  Shape{
	private int radius;
	Circle(int r){
		radius  = r;
	}
	public void draw() {
		System.out.printf("在画版上绘制了一个半径为 %d 的圆\n",radius);
	}
	public void clear() {
		System.out.println("成功擦去画版上的圆");
	}
}
public class Rectangle implements Shape {
	private int length,width;
	Rectangle(int l,int w){
		length = l;
		width = w;
	}
	public void draw() {
		System.out.printf("在画版上绘制了一个长为 %d,宽为%d\n", length,width);
	}
	public void  clear() {
		System.out.println("成功擦去画版上的矩形");
	}
}

值得注意的是:

  1. 大家可能注意到在Shape接口中我删去了成员变量color,实际上接口中可以包含成员变量,但是接口中的成员变量都是静态成员变量,即使我们不设计,也会被隐式指明为static final.
  2. 接口中的抽象方法,我们无须加上abstract修饰(当然加上也不会报错),因为编译器已经知道接口中的方法都是抽象的。

接口中的默认方法

java8为default关键字添加了一个新的用途(之前仅在switch语句体中用到),接口中用default声明的方法允许实现接口时没有实现default方法的类使用接口中默认的方法体。看了下面这个例子也许能更好的理解default的用法与用途。
我们修改上面的Shape接口,为其添加一个新的shift()方法,用来实现形状在画版上的移动;

public interface Shape{
	 void draw();
	 void clear();
	 void shift();
}

点击保存,此时我们的编译器报错了,原因是我们之前写好的Circle类中并没有实现shift方法,也就是说之前所有基于接口Shape的类都必须更改。这种情况下我们可以使用default关键字了,修改Shape接口代码如下

public interface Shape{
	 void draw();
	 void clear();
	 default void shift() {
		 System.out.println("没有找到适合该实例类型shift方法(可能该实例类型是在Shape接口新增shift方法之前实现的)");
	 };
}

这样编译器不仅不会报错,我们甚至可以用Circle实例调用默认的shift方法,修改下Painting类中的main函数

public class Painting{
	public static void main(String[] args) {
		Circle c = new Circle(10);
		Rectangle r = new Rectangle(10,5);
		c.draw();
		c.clear();
		c.shift();//新增
		r.draw();
		r.clear();
		r.shift();//新增
	}
}

输出

在画版上绘制了一个半径为 10 的圆
成功擦去画版上的圆
没有找到适合该实例类型shift方法(可能该实例类型是在Shape接口新增shift方法之前实现的)
在画版上绘制了一个长为 10,宽为5
成功擦去画版上的矩形
没有找到适合该实例类型shift方法(可能该实例类型是在Shape接口新增shift方法之前实现的)

接口与多继承

我们知道在C++中允许一个类继承多个父类,这就带来了问题,如果这多个父类中存在方法名与参数列表一样的函数(方法),那么子类实例调用的方法是那个父类的呢?这就带来了问题,因此java中规定一个类只能继承一个父类。但java可以通过实现多个接口来实现多继承,因为接口中的方法都是抽象的,即使是名字和参数列表一样的方法,它们也都是没有实现的,最终还是取决于子类中对这些方法的实现。

public interface Interface1{
	 void methodA();
	 void methodB();
}
public interface Interface2{
	 void methodA();
	 void methodC();
}
public class Test implements Interface1,Interface2{
	public void methodA() {
		System.out.println("This is methodA()");
	}
		public void methodB() {
		System.out.println("This is methodB()");
	}
		public void methodC() {
		System.out.println("This is methodC()");
	}
	public static void main(String[] args) {
		Test test1 =new Test();
		test1.methodA();
		test1.methodB();
		test1.methodC();
	}
}

上面的Interface1Interface2都有一个method方法,而Test实现 了这两个接口,这是不会产生问题的。

但是java8中新增了默认方法,产生了类似C++中的冲突问题,在java中对于方法名和参数类别相同的默认方法,我们可以通过覆写冲突的方法,指定继承那个接口的方法,如下

public interface Interface1{
	 void methodA();
	 default void methodB() {
		 System.out.println("This is Interface1.methodB()");
	 }
}
public interface Interface2{
	 void methodA();
	 default void methodB() {
		 System.out.println("This is Interface1.methodB()");
	 }
}
public class Test implements Interface1,Interface2{
	public void methodA() {
		System.out.println("This is methodA");
	}
	@Override
	public void methodB() {
		Interface1.super.methodB(); //继承Interface1的methodB()方法
		//Interface2.super.methodB();  继承Interface1的methodB()方法
	}
	public static void main(String[] args) {
		Test test1 =new Test();
		test1.methodA();
	}
}

关于java的接口实现,几个注意点:

  1. 接口中抽象方法必须实现
  2. 默认方法根据选择覆盖
  3. 静态方法不需要实现
  4. 多继承中,类的成员变量仍来自一个类,接口不提供成员变量(接口中的变量都是final static

接口继承

接口和类一样,可以用extends实现接口间的继承

public interface Interface1 extends Interface2{
 .......
}

接口与虚类的区别

  1. 接口支持多继承,而抽象类(包括具体类)只能继承一个父类。
  2. 接口中不能有实例成员变量,接口所声明的成员变量全部是静态常量,即便是变量不加public static final修饰符也是静态常量。抽象类与普通类一样各种形式的成员变量都可以声明。
  3. 接口中没有包含构造方法,由于没有实例成员变量,也就不需要构造方法了。抽象类中可以有实例成员变量,也需要构造方法。
  4. 抽象类中可以声明抽象方法和具体方法。Java 8之前接口中只有抽象方法,而Java 8之后接口中也可以声明具体方法,具体方法通过声明默认方法实现。

- THE END -
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章