天天看点

Java编程思想精粹(九)-接口(上)

接口和抽象类提供了一种将接口与实现分离的更加结构化的方法。

这种机制在编程语言中并不常见

C++ 只对这种概念提供间接支持

Java 为它们提供了直接支持,关键字

尽管你的第一想法是创建接口,但对于构建具有属性和未实现方法的类来说,抽象类也是重要且必要的工具。毕竟你不可能总是使用纯粹的接口。

1 抽象类

创建通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口。通用接口建立了一个基本形式,以此表达所有派生类的共同部分。

有时把 Instrument 称为抽象基类,或简称抽象类。

对于抽象类,它的对象几乎总是没有意义。创建一个抽象类是为了通过通用接口操纵一系列类。因此只是表示接口,不是具体实现,所以创建一个抽象类的对象毫无意义,我们可能希望阻止用户这么做。通过让抽象类所有的方法产生错误,就可以达到这个目的,但是这么做会延迟到运行时才能得知错误信息,并且需要用户进行可靠、详尽的测试。最好能在编译时捕捉问题。

1.1 抽象方法

Java 提供了一个叫做抽象方法的机制,这个方法是不完整的:它只有声明没有方法体。包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,否则,编译器会报错。如果一个抽象类是不完整的,当试图创建这个类的对象时,Java 会怎么做呢?它不会创建抽象类的对象,所以我们只会得到编译器的错误信息。这样保证了抽象类的纯粹性,我们不用担心误用它。

如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义。如果不这么做(可以选择不做),新类仍然是一个抽象类,编译器会强制我们为新类加上 abstract 关键字。

可以将一个不包含任何抽象方法的类指明为 abstract,在类中的抽象方法没啥意义但想阻止创建类的对象时,这么做就很有用。

为了创建可初始化的类,就要继承抽象类,并提供所有抽象方法的定义。留意 @Override 的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。因此,你可能认为这里的 @Override 是多余的。但是,@Override 还提示了这个方法被覆写——我认为这是有用的,所以我会使用 @Override,即使在没有这个注解,编译器告诉我错误的时候。

接口只允许 public 方法,即使不加访问修饰符。然而,抽象类啥都允许。

private abstract 被禁止了是有意义的,因为你不可能在 AbstractAccess 的任何子类中合法地定义它。

创建抽象类和抽象方法是有意义的,因为它们使得类的抽象性很明确,并能告知用户和编译器使用意图。

抽象类同时也是一种有用的重构工具,使用它们使得我们很容易地将沿着继承层级结构上移公共方法。

2 接口

使用 interface 关键字创建接口。描述 Java 8 之前的接口更加容易,因为它们只允许抽象方法。

我们不用为方法加上 abstract 关键字

Java编程思想精粹(九)-接口(上)

因为方法在接口中,Java 知道这些方法不能有方法体(仍然可以为方法加上 abstract 关键字,但是看起来像是不明白接口的小白)。

因此,在 Java 8之前我们可以这么说:interface 关键字产生一个完全抽象的类,没有提供任何实现。我们只能描述类应该像什么,做什么,但不能描述怎么做,即只能决定方法名、参数列表和返回类型,但是无法确定方法体。接口只提供形式,通常来说没有实现,尽管在某些受限制的情况下可以有实现。

一个接口表示:所有实现了该接口的类看起来都像这样。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。所以,接口被用来建立类之间的协议。

Java 8 中接口稍微有些变化,因为 Java 8 允许接口包含默认方法和静态方法——基于某些重要原因,看到后面你会理解。

接口的基本概念仍然没变,介于类型之上、实现之下。

接口与抽象类最明显的区别可能就是使用上的惯用方式。

接口的典型使用是代表一个类的类型或一个形容词,如 Runnable 或 Serializable

而抽象类通常是类层次结构的一部分或一件事物的类型

和类一样,需要在关键字 interface 前加上 public 关键字(但只是在接口名与文件名相同的情况下),否则接口只有包访问权限,只能在接口相同的包下才能使用它。

Java编程思想精粹(九)-接口(上)

接口同样可以包含属性,这些属性被隐式指明为 static 和 final。这就很妙了,尤其适合存储常量,避免了一大堆重复的 public static final 修饰。

Java编程思想精粹(九)-接口(上)

让一个类遵循某个特定接口(或一组接口)使用 implements 关键字,它表示:接口就像它看起来一样,现在我要说明它是如何工作的。除此之外,它看起来像继承。

可以显式地声明接口中的方法为 public,但是即使你不这么做,它们也是 public 的。所以当实现一个接口时,来自接口中的方法必须被定义为 public。否则,它们只有包访问权限,这样在继承时,它们的可访问权限就被降低了,这是 Java 编译器所不允许的。