接口
# 接口
一个类,如果实现了一个接口,那么它必须实现接口中定义的所有方法。一个类,继承一个抽象类,那么它必须重写抽象类中定义的所有抽象方法,可以不用重写抽象类中定义的普通方法。
# 定义接口
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。
如果一个抽象类没有字段,所有方法全部都是抽象方法:
abstract class Person {
public abstract void run();
public abstract String getName();
}
2
3
4
就可以把该抽象类改写为接口:interface
。
在 Java 中,使用 interface
可以声明一个接口:
interface Person {
void run();
String getName();
}
2
3
4
所谓 interface
,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是 public abstract
的,所以这两个修饰符不需要写出来(写不写效果都一样)。
注意:
interface 可以看成是一种特殊的类,源代码文件也是以
.java
结尾,编译时也是一个独立的字节码文件。所有类共享一个根类 Object,但是接口没有根
和抽象类相似,不能用 new 操作符创建 interface 的实例。
虽然 Interface 不能有字段,但是可以有常量。
接口中所有数据域都是
public static final
修饰的,所有方法都是public abstract
修饰的,因此可以忽略,如下两个接口定义是等价的:public interface T { public static final int K =1; public abstract void p(); } public interface T { int K = 1; void p(); }
1
2
3
4
5
6
7
8
9
# 实现接口
当一个具体的 class
去实现一个 interface
时,需要使用 implements
关键字。
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public String getName() {
return this.name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
注意,必须得实现接口中的方法,否则会报错的,例如当我们没有实现 getName
方法:
$ javac Student.java
Student.java:1: 错误: Student不是抽象的, 并且未覆盖Person中的抽象方法getName()
public class Student implements Person {
^
1 个错误
2
3
4
5
一个类实现一个接口,其实也是相当于继承了这个接口。一个接口类型的变量可以引用任何实现该接口的类的实例,这样我们就可以面向抽象编程了(参考上一节的抽象类)
在 Java 中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个 interface
:
class Student implements Person, Hello { // 实现了两个interface
...
}
2
3
# 接口继承
一个 interface
可以继承自另一个 interface
。interface
继承自 interface
使用 extends
,它相当于扩展了接口的方法。例如:
interface Person {
void run();
String getName();
}
public interface Hello extends Person {
}
2
3
4
5
6
7
8
此时,Person
接口继承自 Hello
接口,因此,Person
接口现在实际上有 3 个抽象方法签名,其中一个来自继承的 Hello
接口。
注意,接口可以继承其他接口,但不能继承其他类。
综上,一个类只能继承一个父类,但可以同时实现多个接口。
# 继承关系
合理设计 interface
和 abstract class
的继承关系,可以充分复用代码。一般来说,公共逻辑适合放在 abstract class
中,具体逻辑放到各个子类,而接口层次代表抽象程度。
举一个实际的例子,参考 Java 的集合类定义的一组接口、抽象类以及具体子类的继承关系:
┌───────────────┐
│ Iterable │
└───────────────┘
▲ ┌───────────────────┐
│ │ Object │
┌───────────────┐ └───────────────────┘
│ Collection │ ▲
└───────────────┘ │
▲ ▲ ┌───────────────────┐
│ └──────────│AbstractCollection │
┌───────────────┐ └───────────────────┘
│ List │ ▲
└───────────────┘ │
▲ ┌───────────────────┐
└──────────│ AbstractList │
└───────────────────┘
▲ ▲
│ │
│ │
┌────────────┐ ┌────────────┐
│ ArrayList │ │ LinkedList │
└────────────┘ └────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象:
List list = new ArrayList(); // 用List接口引用具体子类的实例
Collection coll = list; // 向上转型为Collection接口
Iterable it = coll; // 向上转型为Iterable接口
2
3
# default 方法
在接口中,可以定义 default
方法。例如,定义一个 run
方法:
public interface InterfaceWithDefault {
String getName();
default void run(){
System.out.println(getName() + " run ");
};
}
2
3
4
5
6
实现类可以不必覆写 default
方法。default
方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是 default
方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
然后实现该接口:
public class TestDefault {
public static void main(String[] args) {
InterfaceWithDefault stu = new Student("Peter JXL");
stu.run();
}
}
class Student implements InterfaceWithDefault{
private String name;
public Student(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
输出结果:
Peter JXL run
# 抽象类和接口的区别
抽象类和接口的对比如下:
abstract class | interface | |
---|---|---|
继承 | 只能 extends 一个 class | 可以 implements 多个 interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义 default 方法 |
还有一点,default
方法和抽象类的普通方法是有所不同的。因为 interface
没有字段,default
方法无法访问字段,而抽象类的普通方法可以访问实例字段。
# 什么时候使用接口?
一般情况下,我们会为类名取一个名词,为接口名取一个形容词或名词。
抽象类和接口都是用来明确多个对象的共同特征的。那么该如何确定在什么情况下应该使用接口,什么情况下应该使用类呢?
一般来说,清晰描述父.子关系的强的“是一种” 的关系( strong is-a relationship) 应该用类建模。例如,因为公历是一种日历,所以,类 java.util.GregorianCalendar 和 java.util.Calendar 是用类继承建模的。
弱的 “是一种” 的关系(weak is-a relationship) 也称为类属关系(is-kind-ofrelationship), 它表明对象拥有某种属性,可以用接口来建模。
例如,所有的字符串都是可比较的,而且数字也是可以比较的,因此,Java 定义了一个 Comparable 接口,String 类和数值类实现这个接口。
但是如果我们用抽象类的方式来实现,让数值类和字符串都继承某个抽象类,完全是没必要的,因为数字有数字的比较方法,字符串有字符串的比较方法,学生(自己定义的类)也有自己的比较方法,所以子类是必须要覆写父类的方法的,并且让这几个完全没有关联的类继承一个抽象类,从逻辑上不太适合。
统一标准的目的,是大家都知道这个是做什么的,但是具体不用知道具体怎么做。