【java基础(三十七)】接口(二)

接口的特性

接口不是类,尤其不能使用new运算符实例化一个接口:

x = new Comparable(...); // 这样是不可以的

然而,尽管不能构造接口的对象,却能声明接口的变量:

Comparable x; // 这样是可以的

接口变量必须引用实现了接口的类对象:

x = new Employee(...);

接下来,如同使用instanceof检查一个对象是否属于某个特定类一样,也可以使用instance检查一个对象是否实现了某个特定的接口:

if (anObject instanceof Comparable) ...

与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从具有较高通用性的接口到较高专用型的接口的链。如:假设有一个Moveable的接口:

public interface Moveable {
	void move(double x, double y);
}

然后,可以以它为基础扩展一个叫做Powered的接口:

public interface Powered extends Moveable {
	double milesPerGallon();
}

虽然在接口中不能包含实例域或静态方法,但却可以包含常量:

public interface Powered extends Moveable {
	double milesPerGallon();
	double SPEED_LIMIT = 95;
}

与接口中的方法都自动地设置为public一样,接口中的域将被自动设为public static final。

有些接口只定义了常量,而没有定义方法。例如,在标准库中有一个SwingConstants就是这样一个接口,其中只包含NORTH、SOUTH和HORIZONTAL等常量。任何实现SwingConstants接口的类都自动地继承了这些常量,并可以在方法中直接地引用NORTH,而不必采用SwingConstants.NORTH这样的繁琐书写形式。然而,这样应用接口似乎有点偏离了接口概念的初衷,最好不要这样使用。

尽管每个类只能够拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。例如,Java程序设计语言有一个非常重要的内置接口,称为Cloneable。如果某个类实现了这个Cloneable接口,Object类中的clone方法就可以创建类对象的一个拷贝。如果自己设计的类拥有克隆和比较的能力,只要实现这两个接口就可以了。使用逗号将实现的各个接口分隔开。

class Employee implements Cloneable, Comparable

接口与抽象类

为什么Java程序设计语言还要不辞辛苦地引入接口概念?为什么不降Comparable直接设计成如下所示的抽象类。

abstract class Comparable {
	public abstract int compareTo(Object other);
}

然后,Employee类再直接扩展这个抽象类,并提供compareTo方法的实现:

class Employee extends Comparable {
	public int compareTo(Object other){...};
}

非常遗憾,使用抽象类表示通用属性存在这样一个问题:每个类只能扩展一个类。假设Employee类已经扩展了一个类,例如Person,它就不能想下面这样扩展第二个类了。

class Employee extends Person, Comparable // 这样是不可以的

但每个类可以像下面这样实现多个接口:

class Employee extends Person implements Comparable // 这样是可以的

有些程序设计语言允许一个类有多个超类。如C++,我们将此特性称为多重继承(multiple inheritance)。而Java的设计者选择了不支持多继承,其主要原因是多继承会让语言本身变得非常复杂,效率也会降低。

实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。

静态方法

在Java SE 8中,允许在接口中增加静态方法。理论上讲,没有任何理由认为这是不合法的。只是这有违于将接口作为抽象规范的初衷。

目前为止,通常的做法都是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和实用工具类,如Collection/Collections或Paht/Paths。

下面来看Paths类,其中包含两个工厂方法。可以由一个字符串序列构造一个文件或目录的路径,如:Paths.get(“jdk1.8.1”, “jre”, “bin”)。在Java SE 8 中,可以为Path接口增加以下方法:

public interface Path {
	public static Path get(String first, String... more) {
		return FileSystems.getDefault().getPath(first, more);
	}
}

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

不过这个Java库都以这种方式重构是不太可能的,但是实现你自己的接口时,不再需要为实用工具方法另外提供一个伴随类。

默认方法

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

public interface Comparable<T> {
	default int comparaTo(T other) { return 0; }
}

当然,这并没有太大用处,因为Comparable的每一个实际实现都要覆盖这个方法。不过有些情况下,默认方法可能很有用。

默认方法可以调用任何其他方法。如:Collection接口可以定义一个便利方法:

public interface Collection {
	int size(); // 一个抽象方法
	default boolean isEmpty() {
		return size() == 0;
	}
}

这样实现Collection的程序员就不用操心实现isEmpty方法了。

默认方法的一个重要用法是“接口演化”(interfae evolution)。以Collection接口为例,这个接口作为Java的一部分已经有很多年了。假设很久以前你提供了这样一个类:

public class Bag implements Collection

后来,在Java SE 8中,又为这个接口增加了一个stream方法。

假设stream方法不是一个默认方法。那么Bag类将不能编译,因为它没有实现这个新方法。为接口增加一个非默认方法不能保证“源代码兼容(source compatible)”

不过,假设不能编译这个类,而只是使用原先一个包含这个类的JAR文件。这个类仍能正常加载,尽管没有这个新方法。程序仍然可以正常构造Bag实例,不会发生意外。不过,如果程序在一个Bag实例上调用stream方法,就会出现一个AbstractMethodError。

将方法实现为一个默认方法就可以解决这两个问题。Bag类又能正常编译了。另外如果没有重新编译而直接加载这个类,并在一个Bag实例上调用stream方法,将调用Collection.stream方法。

捐赠

若你感觉读到这篇文章对你有启发,能引起你的思考。请不要吝啬你的钱包,你的任何打赏或者捐赠都是对我莫大的鼓励。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章