Contents

Java抽象类中的构造函数

1. 概述

抽象类和构造函数似乎不兼容。构造函数是类实例化时调用的方法抽象类不能实例化。这听起来违反直觉,对吧? 在本文中,我们将了解为什么抽象类可以具有构造函数,以及使用它们如何为子类实例化带来好处。

2. 默认构造函数

当一个类没有声明任何构造函数时,编译器会为我们创建一个默认构造函数。对于抽象类也是如此。即使没有显式构造函数,抽象类也会有一个可用的默认构造函数。 在抽象类中,其后代可以使用 *super()*调用抽象默认构造函数:

public abstract class AbstractClass {
    // compiler creates a default constructor
}
public class ConcreteClass extends AbstractClass {
    public ConcreteClass() {
        super();
    }
}

3. 无参数构造函数

我们可以在抽象类中声明一个不带参数的构造函数。它将覆盖默认构造函数,任何子类创建都会在构造链中首先调用它。

让我们用抽象类的两个子类来验证这种行为:

public abstract class AbstractClass {
    public AbstractClass() {
        System.out.println("Initializing AbstractClass");
    }
}
public class ConcreteClassA extends AbstractClass {
}
public class ConcreteClassB extends AbstractClass {
    public ConcreteClassB() {
        System.out.println("Initializing ConcreteClassB");
    }
}

让我们看看调用*new ConcreateClassA()*时得到的输出:

Initializing AbstractClass

而调用*new ConcreteClassB()*的输出将是:

Initializing AbstractClass
Initializing ConcreteClassB

3.1. 安全初始化

声明不带参数的抽象构造函数有助于安全初始化。 下面的 Counter类是计算自然数的超类。我们需要它的值从零开始。

让我们看看如何在这里使用无参数构造函数来确保安全初始化:

public abstract class Counter {
    int value;
    public Counter() {
        this.value = 0;
    }
    abstract int increment();
}

我们的SimpleCounter子类使用*++运算符实现了increment()*方法。它在每次调用时将值加一:

public class SimpleCounter extends Counter {
    @Override
    int increment() {
        return ++value;
    }
}

请注意,SimpleCounter没有声明任何构造函数。它的创建依赖于默认调用的计数器的无参数构造函数。 以下单元测试演示了构造函数安全初始化的value属性:

@Test
void givenNoArgAbstractConstructor_whenSubclassCreation_thenCalled() {
    Counter counter = new SimpleCounter();
    assertNotNull(counter);
    assertEquals(0, counter.value);
}

3.2. 阻止访问

我们的 Counter初始化工作正常,但假设我们不希望子类覆盖这个安全初始化。

首先,我们需要将构造函数设为私有以防止子类访问:

private Counter() {
    this.value = 0;
    System.out.println("Counter No-Arguments constructor");
}

其次,让我们为子类创建另一个构造函数来调用:

public Counter(int value) {
    this.value = value;
    System.out.println("Parametrized Counter constructor");
}

最后,我们的 SimpleCounter需要覆盖参数化的构造函数,否则将无法编译:

public class SimpleCounter extends Counter {
    public SimpleCounter(int value) {
        super(value);
    }
    // concrete methods
}

注意编译器如何期望我们在这个构造函数上调用super(value)来限制对我们私有的无参数构造函数的访问。

4. 参数化构造函数

抽象类中构造函数的最常见用途之一是避免冗余。让我们使用汽车创建一个示例,看看我们如何利用参数化构造函数。 我们从一个抽象的Car类开始来表示所有类型的汽车。我们还需要一个distance属性来知道它已经走了多少:

public abstract class Car {
    int distance;
    public Car(int distance) {
        this.distance = distance;
    }
}

我们的超类看起来不错,但我们不希望将distance属性初始化为非零值。我们还希望防止子类更改distance属性或覆盖参数化构造函数。

让我们看看如何限制对 distance的访问并使用构造函数安全地对其进行初始化:

public abstract class Car {
    private int distance;
    private Car(int distance) {
        this.distance = distance;
    }
    public Car() {
        this(0);
        System.out.println("Car default constructor");
    }
    // getters
}

现在,我们的distance属性和参数化构造函数是私有的。有一个公共默认构造函数Car()委托私有构造函数来初始化distance。 为了使用我们的 distance属性,让我们添加一些行为来获取和显示汽车的基本信息:

abstract String getInformation();
protected void display() {
    String info = new StringBuilder(getInformation())
      .append("\nDistance: " + getDistance())
      .toString();
    System.out.println(info);
}

所有子类都需要提供*getInformation()*的实现,*display()*方法将使用它来打印所有详细信息。 现在让我们创建 ElectricCar和 FuelCar子类:

public class ElectricCar extends Car {
    int chargingTime;
    public ElectricCar(int chargingTime) {
        this.chargingTime = chargingTime;
    }
    @Override
    String getInformation() {
        return new StringBuilder("Electric Car")
          .append("\nCharging Time: " + chargingTime)
          .toString();
    }
}
public class FuelCar extends Car {
    String fuel;
    public FuelCar(String fuel) {
        this.fuel = fuel;
    }
    @Override
    String getInformation() {
        return new StringBuilder("Fuel Car")
          .append("\nFuel type: " + fuel)
          .toString();
    }
}

让我们看看这些子类的实际作用:

ElectricCar electricCar = new ElectricCar(8);
electricCar.display();
FuelCar fuelCar = new FuelCar("Gasoline");
fuelCar.display();

产生的输出如下所示:

Car default constructor
Electric Car
Charging Time: 8
Distance: 0
Car default constructor
Fuel Car
Fuel type: Gasoline
Distance: 0