OCA/OCP Java Note (10): Class Design (2)
3. Implementing Interfaces
3.1. Defining an Interface
- 接口不能直接实例化。
- 一个接口中可以不包含任何方法。
- 接口不能标记为final。
- 顶层接口默认具有public或default的访问限制和abstract修饰符,将接口标记为private/protected/final会导致编译错误。此条不适用于内部接口。
- 接口中的所有非default方法默认具有abstract和public修饰符,将方法标记为private/protected/final会导致编译错误。关于default方法,可以参考这里。
eg. 下面的两个接口的定义是等效的,编译器会把第一种自动转换为第二种形式。
public interface CanFly { void fly(int speed); abstract void takeoff(); public abstract double dive(); } public abstract interface CanFly { public abstract void fly(int speed); public abstract void takeoff(); public abstract double dive(); }
3.2. Inheriting an Interface
继承借口的规则为:
- 接口或抽象类可以继承接口,该接口或抽象子类会继承所有抽象方法,作为其自己的抽象方法。
- 实现接口(或继承了接口的抽象类)的直接具体子类,必须实现其继承的所有抽象方法。
接口可以使用extends继承多个接口。抽象类可以使用implements实现多个接口,而不必给出实际的实现。
eg.
public interface HasTail { public int getTailLength(); } public interface HasWhiskers { public int getNumberOfWhiskers(); } public interface Seal extends HasTail, HasWhiskers { } public abstract class HarborSeal implements HasTail, HasWhiskers { } public class LeopardSeal implements HasTail, HasWhiskers { // DOES NOT COMPILE } interface HarpSeal implements HasTail, HasWhiskers { // DOES NOT COMPILE }
3.2.1. Classes, Interfaces, and Keywords
类可以实现接口,不能继承接口。接口可以继承接口,不能实现接口。
3.2.2. Abstract Methods and Multiple Inheritance
如果一个具体类实现的两个接口中具有相同名称和签名的方法,具体类只需要给出一个实现。
eg.
public interface Herbivore { public void eatPlants(); } public interface Omnivore { public void eatPlants(); public void eatMeat(); } public class Bear implements Herbivore, Omnivore { public void eatMeat() { System.out.println("Eating meat"); } public void eatPlants() { System.out.println("Eating plants"); } }
接口Herbivore和Omnivore都定义了eatPlants() ,Bear实现了Herbivore和Omnivore,只需要给出一个eatPlants() 的实现即可。
如果一个具体类实现的两个接口中具有相同名称、不同签名的方法,具体类需要分别实现,相当于方法重载。
eg.
public interface Herbivore { public int eatPlants(int quantity); } public interface Omnivore { public void eatPlants(); } public class Bear implements Herbivore, Omnivore { public int eatPlants(int quantity) { System.out.println("Eating plants: "+quantity); return quantity; } public void eatPlants() { System.out.println("Eating plants"); } }
如果两个接口中具有相同名称和签名、不同返回类型的方法,具体类不能同时实现这两个接口。Java不允许定义两个名称和签名相同、而返回类型不同的方法。
eg.
public interface Herbivore { public int eatPlants(); } public interface Omnivore { public void eatPlants(); } public class Bear implements Herbivore, Omnivore { public int eatPlants() { // DOES NOT COMPILE System.out.println("Eating plants: 10"); return 10; } public void eatPlants() { // DOES NOT COMPILE System.out.println("Eating plants"); } }
3.3. Interface Variables
- 接口中的变量是public、static和final的,将其标记为private或protected会导致编译错误。
- 由于接口中的变量是final的,变量必须在定义时赋值。
eg. 下面的两种定义是等效的,编译器会把第一种自动转换为第二种的形式。
public interface CanSwim { int MAXIMUM_DEPTH = 100; final static boolean UNDERWATER = true; public static final String TYPE = "Submersible"; } public interface CanSwim { public static final int MAXIMUM_DEPTH = 100; public static final boolean UNDERWATER = true; public static final String TYPE = "Submersible"; }
3.4. Default Interface Methods
Java 8中引入了默认方法,可以在接口中使用default关键词为抽象方法提供默认实现。
- 默认方法只能在接口中声明,不能在类和抽象类中声明。
- 默认方法必须标记为default,且必须提供实现。
- 默认方法不是static、final、abstract的,实现接口的类可以直接使用或重写默认方法。
- 默认方法是public的,标记为private或protected将无法编译。
public interface IsWarmBlooded { boolean hasScales(); public default double getTemperature() { return 10.0; } }
3.4.1. Default Methods and Multiple Inheritance
由于一个类可以实现多个接口,通过引入默认方法,基本上相当于引入了多继承。如果一个类实现的两个接口中,具有相同名称和签名的默认方法,将无法编译,因为Java不知道应该调用那个方法。
eg.
public interface Walk { public default int getSpeed() { return 5; } } public interface Run { public default int getSpeed() { return 10; } } public class Cat implements Walk, Run { // DOES NOT COMPILE public static void main(String[] args) { System.out.println(new Cat().getSpeed()); } }
一个例外是,如果类重写了两个接口中具有相同名称和签名的默认方法,Java将使用该重写的方法,可以顺利编译。
eg.
public class Cat implements Walk, Run { public int getSpeed() { return 1; } public static void main(String[] args) { System.out.println(new Cat().getSpeed()); // 1 } }
3.5. Static Interface Methods
Java 8支持在接口中通过static定义静态方法,接口中的静态方法的特性和类中的静态方法基本一致,唯一的区别是,接口中的静态方法不会被任何实现该接口的类继承,由此避免了多继承的问题,即便一个类实现的两个接口具有相同名称和签名的静态方法,由于静态方法不会被继承,不会有编译错误。需要注意:
- 接口中静态方法是public的,使用private或protected将无法编译。
- 必须使用接口名来引用其中的静态方法。
public interface Hop { static int getJumpHeight() { return 8; } } public class Bunny implements Hop { public void printDetails() { System.out.println(Hop.getJumpHeight()); System.out.println(getJumpHeight()); // DOES NOT COMPILE } }
4. Understanding Polymorphism
4.1. Casting Objects
转换对象类型的规则为:
- 将对象从子类转换为父类,不需要显式地转换。
- 将对象从父类转换为子类,需要显式地转换。
- 编译器不允许转换到无关的类型。
- 如果被转换的对象不是目标类型的实例,运行时会抛出异常,尽管编译可以通过。
eg.
public class Rodent { } public class Capybara extends Rodent { public static void main(String[] args) { Rodent rodent = new Rodent(); Capybara capybara = (Capybara)rodent; // Throws ClassCastException at runtime } }
将对象rodent 转换为其子类Capybara,抛出ClassCastException异常。转换时应该使用如下的方法确保rodent 是Capybara的实例:
if(rodent instanceof Capybara) { Capybara capybara = (Capybara)rodent; }
4.2. Virtual Methods
Java中所有非final、非static和非private的方法都可以看做是虚方法。
eg.
public class Bird { public String getName() { return "Unknown"; } public void displayInformation() { System.out.println("The bird name is: "+getName()); } } public class Peacock extends Bird { public String getName() { return "Peacock"; } public static void main(String[] args) { Bird bird = new Peacock(); bird.displayInformation(); } }
输出为:
The bird name is: Peacock
4.3. Polymorphic Parameters
多态的一个重要应用是用来向方法传递子类或接口的实例。
eg.
public class Reptile { public String getName() { return "Reptile"; } } public class Alligator extends Reptile { public String getName() { return "Alligator"; } } public class Crocodile extends Reptile { public String getName() { return "Crocodile"; } } public class ZooWorker { public static void feed(Reptile reptile) { System.out.println("Feeding reptile "+reptile.getName()); } public static void main(String[] args) { feed(new Alligator()); feed(new Crocodile()); feed(new Reptile()); } }
输出为:
Feeding: Alligator Feeding: Crocodile Feeding: Reptile