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
的实例化实际上没有任何意义。对于像Shape
中draw
和clear
这种在类中没有具体实现,要在子类中来实现的方法,我们可以将其声明为抽象方法,而一个含有抽象方法的类,必须要声明为抽象类;
抽象类
抽象方法的声明:
abstract void f();
抽象类的定义:
abstract class ClassName{
abstract void f()
}
使用抽象类应该注意的点:
- 一个类如果包含了一个或多个抽象方法,那这个类必须是抽象类。
- 一个类即使没有抽象的方法,它也可以被声明为抽象类(通常被用来防止该类被实例化)
- 一个抽象类的子类必须要实现父类中所有的抽象方法,否则子类也必须声明为抽象类(这很容易推导,假设子类继承了父类的一个抽象方法,却没有将其实现,那子类也包含了抽象方法)
- 抽象方法无法被声明是
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("成功擦去画版上的矩形");
}
}
值得注意的是:
- 大家可能注意到在Shape接口中我删去了成员变量
color
,实际上接口中可以包含成员变量,但是接口中的成员变量都是静态成员变量,即使我们不设计,也会被隐式指明为static final
. - 接口中的抽象方法,我们无须加上
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();
}
}
上面的Interface1
和Interface2
都有一个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的接口实现,几个注意点:
- 接口中抽象方法必须实现
- 默认方法根据选择覆盖
- 静态方法不需要实现
- 多继承中,类的成员变量仍来自一个类,接口不提供成员变量(接口中的变量都是
final static
)
接口继承
接口和类一样,可以用extends
实现接口间的继承
public interface Interface1 extends Interface2{
.......
}
接口与虚类的区别
- 接口支持多继承,而抽象类(包括具体类)只能继承一个父类。
- 接口中不能有实例成员变量,接口所声明的成员变量全部是静态常量,即便是变量不加public static final修饰符也是静态常量。抽象类与普通类一样各种形式的成员变量都可以声明。
- 接口中没有包含构造方法,由于没有实例成员变量,也就不需要构造方法了。抽象类中可以有实例成员变量,也需要构造方法。
- 抽象类中可以声明抽象方法和具体方法。Java 8之前接口中只有抽象方法,而Java 8之后接口中也可以声明具体方法,具体方法通过声明默认方法实现。