1. Overview
1.概述
Abstract classes and constructors may not seem to be compatible. A constructor is a method called when a class is instantiated, and an abstract class cannot be instantiated. It sounds counterintuitive, right?
抽象类和构造函数似乎并不兼容。构造函数是类被实例化时调用的方法,而抽象类不能被实例化。这听起来很不合常理,对吗?
In this article, we’ll see why abstract classes can have constructors and how using them provides benefits in subclasses instantiation.
在这篇文章中,我们将看到为什么抽象类可以有构造函数,以及在子类的实例化中使用它们会有什么好处。
2. Default Constructor
2.默认构造函数
When a class doesn’t declare any constructor, the compiler creates a default constructor for us. This is also true for abstract classes. Even when there’s no explicit constructor, the abstract class will have a default constructor available.
当一个类没有声明任何构造函数时,编译器会为我们创建一个默认的构造函数。这对于抽象类也是如此。即使没有明确的构造函数,抽象类也会有一个默认的构造函数可用。
In an abstract class, its descendants can invoke the abstract default constructor using super():
在一个抽象类中,其后代可以使用super()调用抽象的默认构造函数。
public abstract class AbstractClass {
// compiler creates a default constructor
}
public class ConcreteClass extends AbstractClass {
public ConcreteClass() {
super();
}
}
3. No-Arguments Constructor
3.无参数构造器
We can declare a constructor with no arguments in an abstract class. It will override the default constructor, and any subclass creation will call it first in the construction chain.
我们可以在一个抽象类中声明一个没有参数的构造函数。它将覆盖默认的构造函数,任何子类的创建将在构造链中首先调用它。
Let’s verify this behavior with two subclasses of an abstract class:
让我们用一个抽象类的两个子类来验证这一行为。
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");
}
}
Let’s see the output we get when calling new ConcreateClassA():
让我们看看调用 new ConcreateClassA()时得到的输出。
Initializing AbstractClass
While the output for calling new ConcreteClassB() will be:
而调用new ConcreteClassB()的输出将是。
Initializing AbstractClass
Initializing ConcreteClassB
3.1. Safe Initialization
3.1.安全初始化
Declaring an abstract constructor with no arguments can be helpful for safe initialization.
声明一个没有参数的抽象构造函数对安全初始化有帮助。
The following Counter class is a superclass for counting natural numbers. We need its value to start from zero.
下面的Counter类是一个用于计算自然数的超类。我们需要它的值从零开始。
Let’s see how we can use a no-arguments constructor here to ensure a safe initialization:
让我们看看我们如何在这里使用一个无参数的构造函数来确保安全的初始化。
public abstract class Counter {
int value;
public Counter() {
this.value = 0;
}
abstract int increment();
}
Our SimpleCounter subclass implements the increment() method with the ++ operator. It increments the value by one on each invocation:
我们的SimpleCounter子类用++操作符实现了increment()方法。它在每次调用时将值增加1。
public class SimpleCounter extends Counter {
@Override
int increment() {
return ++value;
}
}
Notice that SimpleCounter does not declare any constructor. Its creation relies on the counter’s no-argument constructor to be invoked by default.
请注意,SimpleCounter没有声明任何构造函数。它的创建依赖于计数器的无参数构造函数被默认调用。
The following unit test demonstrates the value property being safely initialized by the constructor:
下面的单元测试演示了value属性被构造函数安全初始化。
@Test
void givenNoArgAbstractConstructor_whenSubclassCreation_thenCalled() {
Counter counter = new SimpleCounter();
assertNotNull(counter);
assertEquals(0, counter.value);
}
3.2. Preventing Access
3.2.防止访问
Our Counter initialization works fine, but let’s imagine we don’t want subclasses to override this safe initialization.
我们的Counter 初始化工作很好,但是让我们设想一下,我们不希望子类覆盖这个安全初始化。
First, we need to make the constructor private to prevent subclasses from having access:
首先,我们需要把构造函数变成私有的,以防止子类的访问。
private Counter() {
this.value = 0;
System.out.println("Counter No-Arguments constructor");
}
Second, let’s create another constructor for subclasses to call:
其次,让我们创建另一个构造函数供子类调用。
public Counter(int value) {
this.value = value;
System.out.println("Parametrized Counter constructor");
}
Finally, our SimpleCounter is required to override the parameterized constructor, otherwise, it won’t compile:
最后,我们的SimpleCounter需要覆盖参数化构造函数,否则,它将无法编译。
public class SimpleCounter extends Counter {
public SimpleCounter(int value) {
super(value);
}
// concrete methods
}
Notice how the compiler expects we call super(value) on this constructor, to restrict the access to our private no-arguments constructor.
请注意编译器希望我们在这个构造函数上调用super(value),以限制对我们的private no-arguments构造函数的访问。
4. Parametrized Constructors
4.Parametrized构造器
One of the most common uses for constructors in abstract classes is to avoid redundancy. Let’s create an example using cars to see how we can take advantage of parametrized constructors.
抽象类中构造函数最常见的用途之一是避免冗余。让我们用汽车创建一个例子,看看我们如何利用参数化的构造函数。
We begin with an abstract Car class to represent all types of cars. We also need a distance property to know how much it has traveled:
我们从一个抽象的Car类开始,代表所有类型的汽车。我们还需要一个distance属性来知道它走了多少路。
public abstract class Car {
int distance;
public Car(int distance) {
this.distance = distance;
}
}
Our superclass looks good, but we don’t want the distance property to be initialized with a non-zero value. We also want to prevent subclasses from changing the distance property or overriding the parameterized constructor.
我们的超类看起来不错,但是我们不希望distance属性被初始化为一个非零值。我们还想防止子类改变distance属性或重写参数化构造函数。
Let’s see how to restrict access to distance and use constructors to initialize it securely:
让我们看看如何限制对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
}
Now, our distance property and parameterized constructor are private. There’s a public default constructor Car() that delegates the private constructor to initialize distance.
现在,我们的distance属性和参数化构造函数是私有的。有一个公共的默认构造函数Car(),它委托私有构造函数来初始化distance。
To use our distance property, let’s add some behavior to get and display the car’s basic information:
为了使用我们的distance属性,让我们添加一些行为来获取和显示汽车的基本信息。
abstract String getInformation();
protected void display() {
String info = new StringBuilder(getInformation())
.append("\nDistance: " + getDistance())
.toString();
System.out.println(info);
}
All subclasses need to provide an implementation of getInformation(), and the display() method will use it to print all details.
所有的子类都需要提供一个getInformation()的实现,display()方法将使用它来打印所有细节。
Let’s now create ElectricCar and FuelCar subclasses:
现在让我们创建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();
}
}
Let’s see those subclasses in action:
让我们看看这些子类的运行情况。
ElectricCar electricCar = new ElectricCar(8);
electricCar.display();
FuelCar fuelCar = new FuelCar("Gasoline");
fuelCar.display();
The output produced looks like:
产生的输出看起来像。
Car default constructor
Electric Car
Charging Time: 8
Distance: 0
Car default constructor
Fuel Car
Fuel type: Gasoline
Distance: 0
5. Conclusion
5.总结
Like any other classes in Java, abstract classes can have constructors even when they are only called from their concrete subclasses.
像Java中的其他类一样,抽象类也可以有构造函数,即使它们只从其具体的子类中调用。
In this article, we went through each type of constructor from the perspective of abstract classes – how they’re related to concreate subclasses and how can we use them in practical use cases.
在这篇文章中,我们从抽象类的角度对每种类型的构造函数进行了梳理–它们与具体的子类有什么关系,以及我们如何在实际用例中使用它们。
As always, code samples can be found over on GitHub.
一如既往,代码样本可以在GitHub上找到over。