抽象方法和类

抽象方法和类

抽象类是一种声明为抽象的类——它可能包含抽象方法,也可能不包含。抽象类不能被实例化,但可以被继承。
抽象方法是指声明时未提供实现(不带大括号且后跟分号)的方法,例如:

abstract void moveTo(double deltaX, double deltaY);

如果类包含抽象方法,则该类本身必须声明为抽象类,例如:

public abstract class GraphicObject {
   // declare fields
   // declare nonabstract methods
   abstract void draw();
}

当抽象类被派生时,子类通常会为父类中的所有抽象方法提供实现。然而,如果子类未提供实现,则该子类也必须声明为抽象类。

注意:接口(参见“接口”章节)中未声明为默认或静态的方法默认即为抽象方法,因此接口方法无需显式使用抽象修饰符(虽然可以使用,但并非必要)。

抽象方法与接口的对比

抽象类与接口相似。它们无法被实例化,且可能包含声明时带或不带实现的方法混合体。但抽象类允许声明非静态非最终的字段,并定义 publicprotectedprivate 具体方法。而接口中所有字段均自动成为 publicstaticfinal,所有声明或定义的方法(包括默认方法)均为 public 。此外,无论是否为抽象类,类最多只能继承一个父类;而接口则可实现任意数量。

你应该使用抽象类还是接口?

  • 如果以下任何情况适用于您的场景,请考虑使用抽象类:
    • 你希望在几个密切相关的类之间共享代码。
    • 你期望继承自抽象类的子类具有许多共同的方法或字段,或者需要使用除 public 之外的访问修饰符(如 protectedprivate )。
    • 你需要声明 non-staticnon-final 字段。这使你能够定义方法,这些方法能够访问并修改其所属对象的状态。
  • 如果以下任何情况适用于您的场景,请考虑使用接口:
    • 你期望无关联的类会实现你的接口。例如,Comparable 和 Cloneable 接口就被许多无关联的类所实现。
    • 你希望指定特定数据类型的行为,但并不关心由谁来实现该行为。
    • 你想要利用类型的多重继承特性。

JDK中抽象类的示例是AbstractMap,它属于集合框架的一部分。其子类(包括HashMapTreeMapConcurrentHashMap)共享AbstractMap定义的许多方法(包括get()put()isEmpty()containsKey()containsValue())。

JDK 中实现多个接口的类示例是 HashMap,它实现了 SerializableCloneableMap<K, V> 接口。通过阅读这些接口列表,可以推断出HashMap的实例(无论由哪个开发者或公司实现该类)都具备可克隆性、可序列化(即能转换为字节流,详见”可序列化对象”章节)以及映射功能。此外,Map<K, V>接口已通过merge()forEach()等众多默认方法得到增强,这些方法在早期实现该接口的类中无需额外定义。

请注意,许多软件库同时使用抽象类和接口;HashMap 类实现了多个接口,同时也继承了抽象类 AbstractMap。

一个抽象类的例子

在面向对象的绘图应用程序中,您可以绘制圆形、矩形、直线、贝塞尔曲线以及许多其他图形对象。这些对象都具有某些共有的状态(例如:位置、方向、线条颜色、填充颜色)和行为(例如:移动到、旋转、调整大小、绘制)。其中部分状态与行为适用于所有图形对象(例如:位置、填充颜色、moveTo),而另一些则需要不同实现(例如:resize或draw)。

所有图形对象都必须能够绘制自身或调整自身大小,它们只是实现方式不同。这种情况正是抽象超类的理想应用场景。你可以利用它们的共同特性,声明所有图形对象都继承自同一个抽象父对象,例如图形对象(GraphicObject)。

首先,声明一个抽象类 GraphicObject,用于提供所有子类完全共享的成员变量和方法,例如当前位置和 moveTo() 方法。GraphicObject 还声明了抽象方法,如 draw()resize(),这些方法需要由所有子类实现,但必须以不同方式实现。GraphicObject 类可以如下所示:

abstract class GraphicObject {
    int x, y;
    ...
    void moveTo(int newX, int newY) {
        ...
    }
    abstract void draw();
    abstract void resize();
}

GraphicObject 的每个非抽象子类(如 CircleRectangle)都必须为 draw()resize() 方法提供实现:

class Circle extends GraphicObject {
    void draw() {
        ...
    }
    void resize() {
        ...
    }
}
class Rectangle extends GraphicObject {
    void draw() {
        ...
    }
    void resize() {
        ...
    }
}

当抽象类实现接口时

在接口章节中提到,实现接口的类必须实现该接口的所有方法。然而,只要声明该类为抽象类,就可以定义一个不实现接口所有方法的类。例如:

abstract class X implements Y {
  // implements all but one method of Y
}

class XX extends X {
  // implements the remaining method in Y
}

在此情况下,类 X 必须是抽象类,因为它并未完整实现 Y,但类 XX 实际上确实实现了 Y

类成员

抽象类可以包含静态字段和静态方法。你可以像使用其他类一样,通过类引用调用这些静态成员(例如:AbstractClass.staticMethod())。