OCA/OCP Java Note (10): Class Design (2)

3. Implementing Interfaces

3.1. Defining an Interface

  1. 接口不能直接实例化。
  2. 一个接口中可以不包含任何方法。
  3. 接口不能标记为final。
  4. 顶层接口默认具有public或default的访问限制和abstract修饰符,将接口标记为private/protected/final会导致编译错误。此条不适用于内部接口。
  5. 接口中的所有非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

  继承借口的规则为:

  1. 接口或抽象类可以继承接口,该接口或抽象子类会继承所有抽象方法,作为其自己的抽象方法。
  2. 实现接口(或继承了接口的抽象类)的直接具体子类,必须实现其继承的所有抽象方法。

  接口可以使用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

  1. 接口中的变量是public、static和final的,将其标记为private或protected会导致编译错误。
  2. 由于接口中的变量是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关键词为抽象方法提供默认实现。

  1. 默认方法只能在接口中声明,不能在类和抽象类中声明。
  2. 默认方法必须标记为default,且必须提供实现。
  3. 默认方法不是static、final、abstract的,实现接口的类可以直接使用或重写默认方法。
  4. 默认方法是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定义静态方法,接口中的静态方法的特性和类中的静态方法基本一致,唯一的区别是,接口中的静态方法不会被任何实现该接口的类继承,由此避免了多继承的问题,即便一个类实现的两个接口具有相同名称和签名的静态方法,由于静态方法不会被继承,不会有编译错误。需要注意:

  1. 接口中静态方法是public的,使用private或protected将无法编译。
  2. 必须使用接口名来引用其中的静态方法。

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

  转换对象类型的规则为:

  1. 将对象从子类转换为父类,不需要显式地转换。
  2. 将对象从父类转换为子类,需要显式地转换。
  3. 编译器不允许转换到无关的类型。
  4. 如果被转换的对象不是目标类型的实例,运行时会抛出异常,尽管编译可以通过。

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