抽象类
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。这个抽象方法无法执行,因此这个类也必须申明为抽象类(abstract class)
抽象类本身被设计是只能用于被继承,强迫子类实现其定义的抽象方法。因此,抽象方法实际上相当于定义了“规范”。
//Person类定义了抽象方法run(),那么,在实现子类Student的时候,就必须覆写run()方法
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run();
}
}
abstract class Person {
public abstract void run();
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
注意:父类Person的run()方法没有实际意义,也不能去掉父类的run()方法。去掉父类的run()方法,就失去了多态的特性。
面向抽象编程
当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例,这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型。
这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
Person s = new Student();
Person t = new Teacher();
个人理解:将子类向上转型为父类,而父类中自定义 “规范”,具体的实现全都由子类覆写,由于“规范” 的存在,整个继承树中针对某一类型的方法签名完全相同,那么在具体运行过程中的,只需引用父类,而无需关心子类的具体实现。
接口
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。
如果一个抽象类没有字段,所有方法全部都是抽象方法,就可以把该抽象类改写为接口:interface。
abstract class Person {
public abstract void run();
public abstract String getName();
}
//改写为 接口interface
interface Person {
void run();
String getName();
}
所谓interface,就是比抽象类还要抽象的纯抽象接口,连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符都可以省略。
当一个具体的class去实现一个interface时,需要使用implements关键字,在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface。
class Student implements Person, Hello { // 实现了两个interface
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
}
术语
注意区分术语:
Java的接口特指interface的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。
抽象类和接口的对比如下:
--------------------------------------------------------------------------
abstract class interface
继承 只能extends一个class 可以implements多个interface
字段 可以定义实例字段 不能定义实例字段
抽象方法 可以定义抽象方法 可以定义抽象方法
非抽象方法 可以定义非抽象方法 可以定义default方法
---------------------------------------------------------------------------
接口继承
一个interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法。
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}//Person接口现在可以实现Hello接口的hello方法。
合理理设计interface和abstract class的继承关系,可以充分复用代码。一般来说,公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。
Java的集合类定义的一组接口、抽象类以及具体子类的继承关系:
┌───────────────┐
│ Iterable │接口
└───────────────┘
▲ ┌───────────────────┐
│ │ Object │父类
┌───────────────┐ └───────────────────┘
│ Collection │ ▲
└───────────────┘ │
▲ ▲ ┌───────────────────┐
│ └──────────│AbstractCollection │抽象类
┌───────────────┐ └───────────────────┘
│ List │ ▲
└───────────────┘ │
▲ ┌───────────────────┐
└──────────│ AbstractList │
└───────────────────┘
▲ ▲
│ │
│ │
┌────────────┐ ┌────────────┐
│ ArrayList │ │ LinkedList │具体子类
└────────────┘ └────────────┘
在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象(接口回调):
List list = new ArrayList(); // 用List接口引用具体子类的实例
Collection coll = list; // 向上转型为Collection接口
Iterable it = coll; // 向上转型为Iterable接口
注意:Iterable接口也可以调用具体子类。
default方法
在接口中,可以定义default方法。通常情况下,继承自某个接口的子类需要对接口中的所有方法覆写,但对于default方法,子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。
public class Main {
public static void main(String[] args) {
Person p = new Student("Xiao Ming");
p.run();
}
}
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}//无需覆写 run方法
}