🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## [抽象类和方法](https://lingcoder.gitee.io/onjava8/#/book/10-Interfaces?id=%e6%8a%bd%e8%b1%a1%e7%b1%bb%e5%92%8c%e6%96%b9%e6%b3%95) 在上一章的乐器例子中,基类**Instrument**中的方法往往是“哑”方法。如果调用了这些方法,就会出现一些错误。这是因为接口的目的是为它的派生类创建一个通用接口。 在那些例子中,创建这个通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口。通用接口建立了一个基本形式,以此表达所有派生类的共同部分。另一种说法把**Instrument**称为抽象基类,或简称抽象类。 对于像**Instrument**那样的抽象类来说,它的对象几乎总是没有意义的。创建一个抽象类是为了通过通用接口操纵一系列类。因此,**Instrument**只是表示接口,不是具体实现,所以创建一个**Instrument**的对象毫无意义,我们可能希望阻止用户这么做。通过让**Instrument**所有的方法产生错误,就可以达到这个目的,但是这么做会延迟到运行时才能得知错误信息,并且需要用户进行可靠、详尽的测试。最好能在编译时捕捉问题。 Java 提供了一个叫做*抽象方法*的机制,这个方法是不完整的:它只有声明没有方法体。下面是抽象方法的声明语法: ~~~ abstract void f(); ~~~ 包含抽象方法的类叫做*抽象类*。如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,否则,编译器会报错。 ~~~ // interface/Basic.java abstract class Basic { abstract void unimplemented(); } ~~~ 如果一个抽象类是不完整的,当试图创建这个类的对象时,Java 会怎么做呢?它不会创建抽象类的对象,所以我们只会得到编译器的错误信息。这样保证了抽象类的纯粹性,我们不用担心误用它。 ~~~ // interfaces/AttemptToUseBasic.java // {WillNotCompile} public class AttemptToUseBasic { Basic b = new Basic(); // error: Basic is abstract; cannot be instantiated } ~~~ 如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义。如果不这么做(可以选择不做),新类仍然是一个抽象类,编译器会强制我们为新类加上**abstract**关键字。 ~~~ // interfaces/Basic2.java abstract class Basic2 extends Basic { int f() { return 111; } abstract void g() { // unimplemented() still not implemented } } ~~~ 可以将一个不包含任何抽象方法的类指明为**abstract**,在类中的抽象方法没啥意义但想阻止创建类的对象时,这么做就很有用。 ~~~ // interfaces/AbstractWithoutAbstracts.java abstract class Basic3 { int f() { return 111; } // No abstract methods } public class AbstractWithoutAbstracts { // Basic3 b3 = new Basic3(); // error: Basic3 is abstract; cannot be instantiated } ~~~ 为了创建可初始化的类,就要继承抽象类,并提供所有抽象方法的定义: ~~~ // interfaces/Instantiable.java abstract class Uninstantiable { abstract void f(); abstract int g(); } public class Instantiable extends Uninstantiable { @Override void f() { System.out.println("f()"); } @Override int g() { return 22; } public static void main(String[] args) { Uninstantiable ui = new Instantiable(); } } ~~~ 留意`@Override`的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。因此,你可能认为这里的`@Override`是多余的。但是,`@Override`还提示了这个方法被覆写——我认为这是有用的,所以我会使用`@Override`,即使在没有这个注解,编译器告诉我错误的时候。 记住,事实上的访问权限是“friendly”。你很快会看到接口自动将其方法指明为**public**。事实上,接口只允许**public**方法,如果不加访问修饰符的话,接口的方法不是**friendly**而是**public**。然而,抽象类允许每件事: ~~~ // interfaces/AbstractAccess.java abstract class AbstractAccess { private void m1() {} // private abstract void m1a(); // illegal protected void m2() {} protected abstract void m2a(); void m3() {} abstract void m3a(); public void m4() {} public abstract void m4a(); } ~~~ **private abstract**被禁止了是有意义的,因为你不可能在**AbstractAccess**的任何子类中合法地定义它。 上一章的**Instrument**类可以很轻易地转换为一个抽象类。只需要部分方法是**abstract**即可。将一个类指明为**abstract**并不强制类中的所有方法必须都是抽象方法。如下图所示: ![类图](https://lingcoder.gitee.io/onjava8/images/1562653648586.png) 下面是修改成使用抽象类和抽象方法的管弦乐器的例子: ~~~ // interfaces/music4/Music4.java // Abstract classes and methods // {java interfaces.music4.Music4} package interfaces.music4; import polymorphism.music.Note; abstract class Instrument { private int i; // Storage allocated for each public abstract void play(Note n); public String what() { return "Instrument"; } public abstract void adjust(); } class Wind extends Instrument { @Override public void play(Note n) { System.out.println("Wind.play() " + n); } @Override public String what() { return "Wind"; } @Override public void adjust() { System.out.println("Adjusting Wind"); } } class Percussion extends Instrument { @Override public void play(Note n) { System.out.println("Percussion.play() " + n); } @Override public String what() { return "Percussion"; } @Override public void adjust() { System.out.println("Adjusting Percussion"); } } class Stringed extends Instrument { @Override public void play(Note n) { System.out.println("Stringed.play() " + n); } @Override public String what() { return "Stringed"; } @Override public void adjust() { System.out.println("Adjusting Stringed"); } } class Brass extends Wind { @Override public void play(Note n) { System.out.println("Brass.play() " + n); } @Override public void adjust() { System.out.println("Adjusting Brass"); } } class Woodwind extends Wind { @Override public void play(Note n) { System.out.println("Woodwind.play() " + n); } @Override public String what() { return "Woodwind"; } } public class Music4 { // Doesn't care about type, so new types // added to system still work right: static void tune(Instrument i) { // ... i.play(Note.MIDDLE_C); } static void tuneAll(Instrument[] e) { for (Instrument i: e) { tune(i); } } public static void main(String[] args) { // Upcasting during addition to the array: Instrument[] orchestra = { new Wind(), new Percussion(), new Stringed(), new Brass(), new Woodwind() }; tuneAll(orchestra); } } ~~~ 输出: ~~~ Wind.play() MIDDLE_C Percussion.play() MIDDLE_C Stringed.play() MIDDLE_C Brass.play() MIDDLE_C Woodwind.play() MIDDLE_C ~~~ 除了**Instrument**,基本没区别。 创建抽象类和抽象方法是有帮助的,因为它们使得类的抽象性很明确,并能告知用户和编译器使用意图。抽象类同时也是一种有用的重构工具,使用它们使得我们很容易地将沿着继承层级结构上移公共方法。