接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
例如,Comparable 接口:
java.lang.Comparable

  1. public interface Comparable {
  2. int compareTo(Object o); // parameter has type T
  3. }
  4. // since Java SE 5.0
  5. public interface Comparable<T> {
  6. int compareTo(T o); // parameter has type T
  7. // like to
  8. public int compareTo(T o); // public 自动省略,且只能为 public
  9. }

上述接口只有一个方法,而有些接口可以包含多个方法(Java SE 8 后,可以提供默认方法和静态方法),也可以定义常量。但是接口不能含有实例域。
提供实例域和方法实现的任务应该由实现接口的那个类来完成。因此,可以将接口看成是没有实例域的抽象类。当然,抽象类还是和接口是由区别的。
Arrays 类中的 sort 方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了 Comparable 接口。例如,让 Employee 实现 Comparable 接口(比较 salary):

  1. class Employee implements Comparable {
  2. ...
  3. // 这里修饰符必须为 public ,因为 接口中的该方法也是 public (默认为 public)
  4. public int compareTo(Object otherObject) {
  5. Employee other = (Employee) otherObject;
  6. return Double.compare(salary, other.salary);
  7. }
  8. }

当然还有泛型 Comparable 接口版本:

  1. class Employee implements Comparable<Employee> {
  2. ...
  3. public int compareTo(Employee other) {
  4. return Double.compare(salary, other.salary);
  5. }
  6. }

当然,目前最好使用泛型版本的。
在继承方面,可能会出现和 equals 的问题:

  1. class Manager extends Employee {
  2. ...
  3. // 因为 Employee 实现的是 Comparable<Employee>,所以子类实现 compareTo() 的参数也必须为 Employee
  4. public int compareTo(Employee other) {
  5. Manager other Manager = (Manager) other;
  6. ...
  7. }
  8. }

如果上述那样实现的话,就会违反「反对称」规则:e.compareTo(m) 没有问题,但 m.compareTo(e) 就会抛出 ClassCastException
解决方法与 equals 差不多,有两种:

  1. 如果比较方式不一样,就不运行不同类之间的比较,例如:
  1. if (getClass() != other.getClass()) throw new ClassCastException();
  1. 如果子类,父类比较方式相同,就应该在父类中提供通用算法,并将方法声明为 final。

接口的特性

接口不是类,不能使用new运算符实例化一个接口,然而,尽管不能构造接口的对象,却能声明接口的变量。接口变量必须引用实现了接口的类对象

  1. x = new Comparable(...); // ERROR
  2. Comparable x;
  3. x = new Employee(...);
  4. // or
  5. Comparable x = new Employee(...);
  6. if (x instanceof Comparable) // OK

当然,接口也可以继承:
假设有一个 Moveable 接口:

  1. public interface Moveable {
  2. void move(double x, double y);
  3. }

然后拓展一个叫 Powered 的接口:

  1. public interface Powered extends Moveable {
  2. double milesPerGallon();
  3. }

接口中不能包含实例域,但是可以包含常量,并且常量必须赋值:

  1. public public interface Powered extends Moveable {
  2. double milesPerGallon();
  3. double SPEED_LIMIT = 95; // 这里必须赋值
  4. // like
  5. public static final double SPEED_LIMIT = 95;
  6. }

尽管每个类只能够拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。

接口与抽象类

抽象类基本可以完成和接口一样的事情,但是为什么还要有接口呢。这是因为抽象类一次只能拓展一个类,限制了使用。但是每个类可以实现多个接口。

静态方法

从 Java SE 8 开始,运行在接口中增加静态方法。
在之前,通常的做法都是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和实用工具类,如 Collection/Collections 或 Path/Paths。
比如,在 Path 类中,其中只包含两个工厂方法。可以由一个字符串序列构造一个文件或目录的路径,如 Paths.get(“jdk1.8.0”,”jre”,”bin”)。在 Java SE 8 中,可以为 Path 接口增加以下方法:

  1. public interface Path {
  2. public static Path get(String first, String...more) {
  3. return FileSystem.getDefault().getPath(first, more);
  4. }
  5. ...
  6. }

这样一来,Paths 类就不再是必要的了。

默认方法

可以为接口方法提供一个默认实现。必须用 default 修饰符标记这样一个方法:

  1. public interface Comparable<T> {
  2. default int compareTo(T o) { return 0; }
  3. }

默认方法可以让你在实现该接口时,不用实现该方法,当然,也可以在实现该接口的类中,重新实现该默认方法。
默认方法还可以调用其他方法:

  1. public interface Collection {
  2. int size();
  3. default boolean isEmpty() {
  4. return size() == 0
  5. }
  6. ...
  7. }

解决默认方法冲突

如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,Java 是这样解决的:

  1. 超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
  2. 接口冲突。如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法来解决冲突。

比如有两个接口:

  1. interface Named {
  2. default String getName() {return getClass.getName() + "_" + HashCode();}
  3. }
  4. interface Person {
  5. default String getName() {return "Preson";}
  6. }

如果同时继承这两个接口,就必须覆盖 getName() 来解决二义性:

  1. class Student implements Person, Named {
  2. public String getName() {
  3. return Person.super.getName();
  4. }
  5. }

如果两个接口都没有为共享方法提供默认实现,那么这里就不存在冲突。
另一种情况,一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法:

  1. class Student extends Person implements Named { ... }

在这种情况下,只会考虑超类方法,接口的所有默认方法都会被忽略。这就是「类优先」规则。

千万不要让一个默认方法重新定义 Object 类中的某个方法。例如,不能为 toString 或 equals 定义默认方法,尽管对于 List 之类的接口这可能很有吸引力。由于「类优先」规则,这样的方法绝对无法超越 Object.toString 或 Objects.equals。