接口与抽象类(深入多态)
什么是抽象类?
用abstract关键字声明抽象类,抽象类不能用new 关键字进行实例化。在设计继承结构时,必须决定清楚什么类是抽象类,什么类是具体类。编译器不会让你初始化一个抽象类。抽象类,除了被继承以外,是没有其它任何用途的。抽象类中,必须包含有抽象方法,还可以包含非抽象方法。
什么是抽象方法?
即用 abstract关键字声明的方法,抽象方法没有方法实体,即没有具体的实现过程。拥有抽象方法的类,必须声明为抽象类。抽象类中的抽象方法,用于规定一组子类共同的协议。
- abstract class Animal {
- // 抽象方法,没有方法体
- public abstract void eat();
- }
在继承过程中,具体类必须实现抽象父类的所有抽象方法
抽象方法没有具体的方法体,它只是为了标记出多态而存在。在覆写抽象父类的抽象方法时,方法名、参数列表必须相同,返回值类型必须兼容。Java很在乎你是否实现了抽象类的抽象方法。
- public class Canine extends Animal {
- // 覆写抽象类的抽象方法
- public void eat() {
- System.out.println("Canine,会吃食物!!");
- }
- // 非继承的方法
- public void roam() {
- }
- }
多态的使用
在Java中,所有类都是从Object这个类继承而来的,Object是所有类的源头,它是所有类的父类。Object有很有用的方法,如 equals(), getClass(), hashCode(), toString()等。
- Object类,是抽象类吗? 答:不是,它没有抽象方法。
- 是否可以覆写Object中的方法? 答:Object类中带有 final关键字的方法,不能被覆写。
- Object类有什么用? 答:用途一,它作为多态可以让方法应付多种类型的机制,以及提供Java在执行期对任何对象都需要的方法实现。另一个用途,它提供了一部分用于线程的方法。
- 既然多态类型这么有用,为什么不把所有的参数类型、返回值类型都设定为Object? 答:因为Java是强类型语言,编译器会检查你调用的是否是该对象确实可以响应的方法。即,你只能从确实有该方法的类中去调用。
- Object dog = new Dog();
- dog.toString(); // 这可以通过编译,因为toString()是Object类中自有的方法。
- dog.eat(); // 这将无法通过编译,因为dog是Object类型,它调用的eat()方法在Object类中没有。
在使用多态时,要注意对象多种类型之间的差异。如下代码:
- Dog dog1 = new Dog();
- Animal dog2 = new Dog();
- Object dog3 = new Dog();
- 注意这三个dog对象的区别: dog1 拥有 Dog / Animal / Object中所有的方法。dog2 拥有 Animal / Object 中的方法,不能调用 Dog 类特有的方法。 dog3 只拥有Object 中的方法,不能调用 Animal / Dog类中的方法。这就是在使用多态过程中,需要特别注意的问题。
- 那么该如何把 Object 类型的 dog转化成真正的 Dog 类型呢?
- if (dog2 instanceof Dog) {
- Dog dog4 = (Dog)dog2;
- }
- if (dog3 instanceof Dog) {
- Dog dog5 = (Dog)dog3;
- }
- // 此时,dog4 / dog5 就是真正的 Dog类型了。
什么是接口?
接口,是一种100%纯抽象的类。接口中的所有方法,都是未实现的抽象方法。
为什么需要接口?
接口存在的意义,就是为了解决Java多重继承带来的致命方块问题。为什么接口可以解决致命方块的问题呢?因为在接口中,所有方法都是抽象的,如此一来,子类在实现接口时就必须实现这些抽象方法,因此Java虚拟机在执行期间就不会搞不清楚要用哪一个继承版本了。
- // interface关键字,用于定义接口
- public interface Pet {
- public abstract void beFriendly();
- public abstract void play();
- }
- // 继承抽象父类 Animal类, 实现 Pet接口
- public class Dog extends Animal implements Pet {
- // 实现接口中的抽象方法
- public void beFriendly() {
- System.out.println("实现 Pet接口中的 beFriendly()方法");
- }
- // 实现接口中的抽象方法
- public void play() {
- System.out.println("实现 Pet接口中的 play()方法");
- }
- // 覆写抽象父类中的抽象方法
- public void eat() {
- System.out.println("覆写抽象父类中的eat()抽象方法");
- }
- }
- 同一个类,可以实现多个接口!
- public class Dog extends Animal implements Pet, Saveable, Paintable { ... }
如何判断应该是设计 类、子类、抽象类、还是接口呢?
- 如果新的类无法对其它的类通过 IS-A 测试时,就设计成不继承任何类的类。
- 只有在需要某个类的特殊化版本时,以覆写或增加新的方法来继承现有的类,得到子类。
- 当你需要定义一群子类的模板,又不想让程序员初始化该模板时,就设计出抽象类。
- 如果希望类可以扮演多态的角色,就设计出完全抽象的接口。
super关键字代表什么?
super代表父类,在子类中使用 super关键字指代父类,通过super还可以调用父类的方法。
- // 抽象父类
- abstract class Animal {
- void run () {}
- }
- // 继承父类
- class Dog extends Animal {
- void run () {
- super.run(); // 这里,调用并执行父类的 run() 方法
- // do other things
- }
- }
- Dog d = new Dog();
- d.run(); // 这调用的是子类Dog对象的 run()方法。