接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
例如,Comparable 接口:
java.lang.Comparable
public interface Comparable {int compareTo(Object o); // parameter has type T}// since Java SE 5.0public interface Comparable<T> {int compareTo(T o); // parameter has type T// like topublic int compareTo(T o); // public 自动省略,且只能为 public}
上述接口只有一个方法,而有些接口可以包含多个方法(Java SE 8 后,可以提供默认方法和静态方法),也可以定义常量。但是接口不能含有实例域。
提供实例域和方法实现的任务应该由实现接口的那个类来完成。因此,可以将接口看成是没有实例域的抽象类。当然,抽象类还是和接口是由区别的。
Arrays 类中的 sort 方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了 Comparable 接口。例如,让 Employee 实现 Comparable 接口(比较 salary):
class Employee implements Comparable {...// 这里修饰符必须为 public ,因为 接口中的该方法也是 public (默认为 public)public int compareTo(Object otherObject) {Employee other = (Employee) otherObject;return Double.compare(salary, other.salary);}}
当然还有泛型 Comparable 接口版本:
class Employee implements Comparable<Employee> {...public int compareTo(Employee other) {return Double.compare(salary, other.salary);}}
当然,目前最好使用泛型版本的。
在继承方面,可能会出现和 equals 的问题:
class Manager extends Employee {...// 因为 Employee 实现的是 Comparable<Employee>,所以子类实现 compareTo() 的参数也必须为 Employeepublic int compareTo(Employee other) {Manager other Manager = (Manager) other;...}}
如果上述那样实现的话,就会违反「反对称」规则:e.compareTo(m) 没有问题,但 m.compareTo(e) 就会抛出 ClassCastException
解决方法与 equals 差不多,有两种:
- 如果比较方式不一样,就不运行不同类之间的比较,例如:
if (getClass() != other.getClass()) throw new ClassCastException();
- 如果子类,父类比较方式相同,就应该在父类中提供通用算法,并将方法声明为 final。
接口的特性
接口不是类,不能使用new运算符实例化一个接口,然而,尽管不能构造接口的对象,却能声明接口的变量。接口变量必须引用实现了接口的类对象。
x = new Comparable(...); // ERRORComparable x;x = new Employee(...);// orComparable x = new Employee(...);if (x instanceof Comparable) // OK
当然,接口也可以继承:
假设有一个 Moveable 接口:
public interface Moveable {void move(double x, double y);}
然后拓展一个叫 Powered 的接口:
public interface Powered extends Moveable {double milesPerGallon();}
接口中不能包含实例域,但是可以包含常量,并且常量必须赋值:
public public interface Powered extends Moveable {double milesPerGallon();double SPEED_LIMIT = 95; // 这里必须赋值// likepublic static final double SPEED_LIMIT = 95;}
尽管每个类只能够拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。
接口与抽象类
抽象类基本可以完成和接口一样的事情,但是为什么还要有接口呢。这是因为抽象类一次只能拓展一个类,限制了使用。但是每个类可以实现多个接口。
静态方法
从 Java SE 8 开始,运行在接口中增加静态方法。
在之前,通常的做法都是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和实用工具类,如 Collection/Collections 或 Path/Paths。
比如,在 Path 类中,其中只包含两个工厂方法。可以由一个字符串序列构造一个文件或目录的路径,如 Paths.get(“jdk1.8.0”,”jre”,”bin”)。在 Java SE 8 中,可以为 Path 接口增加以下方法:
public interface Path {public static Path get(String first, String...more) {return FileSystem.getDefault().getPath(first, more);}...}
这样一来,Paths 类就不再是必要的了。
默认方法
可以为接口方法提供一个默认实现。必须用 default 修饰符标记这样一个方法:
public interface Comparable<T> {default int compareTo(T o) { return 0; }}
默认方法可以让你在实现该接口时,不用实现该方法,当然,也可以在实现该接口的类中,重新实现该默认方法。
默认方法还可以调用其他方法:
public interface Collection {int size();default boolean isEmpty() {return size() == 0}...}
解决默认方法冲突
如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,Java 是这样解决的:
- 超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
- 接口冲突。如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法来解决冲突。
比如有两个接口:
interface Named {default String getName() {return getClass.getName() + "_" + HashCode();}}interface Person {default String getName() {return "Preson";}}
如果同时继承这两个接口,就必须覆盖 getName() 来解决二义性:
class Student implements Person, Named {public String getName() {return Person.super.getName();}}
如果两个接口都没有为共享方法提供默认实现,那么这里就不存在冲突。
另一种情况,一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法:
class Student extends Person implements Named { ... }
在这种情况下,只会考虑超类方法,接口的所有默认方法都会被忽略。这就是「类优先」规则。
千万不要让一个默认方法重新定义 Object 类中的某个方法。例如,不能为 toString 或 equals 定义默认方法,尽管对于 List 之类的接口这可能很有吸引力。由于「类优先」规则,这样的方法绝对无法超越 Object.toString 或 Objects.equals。
