Java >> Java tutorial >  >> Tag >> class

Konstruktører i Java abstrakt klasser

1. Oversigt

Abstrakte klasser og konstruktører ser måske ikke ud til at være kompatible. En konstruktør er en metode, der kaldes, når en klasse instansieres , og en abstrakt klasse kan ikke instansieres . Det lyder kontraintuitivt, ikke?

I denne artikel vil vi se, hvorfor abstrakte klasser kan have konstruktører, og hvordan brugen af ​​dem giver fordele ved instansiering af underklasser.

2. Standardkonstruktør

Når en klasse ikke erklærer nogen konstruktør, opretter compileren en standardkonstruktør for os . Dette gælder også for abstrakte klasser. Selv når der ikke er nogen eksplicit konstruktør, vil den abstrakte klasse have en standard konstruktør tilgængelig.

I en abstrakt klasse kan dens efterkommere kalde den abstrakte standardkonstruktør ved hjælp af super() :

public abstract class AbstractClass {
    // compiler creates a default constructor
}

public class ConcreteClass extends AbstractClass {

    public ConcreteClass() {
        super();
    }
}

3. No-Arguments Constructor

Vi kan erklære en konstruktør uden argumenter i en abstrakt klasse. Det vil tilsidesætte standardkonstruktøren, og enhver underklasseoprettelse kalder den først i konstruktionskæden.

Lad os verificere denne adfærd med to underklasser af en abstrakt klasse:

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");
    }
}

Lad os se det output, vi får, når vi kalder new ConcreateClassA() :

Initializing AbstractClass

Mens outputtet til at kalde new ConcreteClassB() vil være:

Initializing AbstractClass
Initializing ConcreteClassB

3.1. Sikker initialisering

At erklære en abstrakt konstruktør uden argumenter kan være nyttig for sikker initialisering.

Følgende tæller klasse er en superklasse til at tælle naturlige tal. Vi har brug for, at dens værdi starter fra nul.

Lad os se, hvordan vi kan bruge en no-arguments constructor her for at sikre en sikker initialisering:

public abstract class Counter {

    int value;

    public Counter() {
        this.value = 0;
    }

    abstract int increment();
}

Vores SimpleCounter underklassen implementerer increment() metode med ++ operatør. Det øger værdien af én på hver påkaldelse:

public class SimpleCounter extends Counter {

    @Override
    int increment() {
        return ++value;
    }
}

Bemærk, at SimpleCounter  erklærer ikke nogen konstruktør. Dens oprettelse er afhængig af, at tællerens no-argument constructor påkaldes som standard.

Den følgende enhedstest demonstrerer værdien egenskaben bliver sikkert initialiseret af konstruktøren:

@Test
void givenNoArgAbstractConstructor_whenSubclassCreation_thenCalled() {
    Counter counter = new SimpleCounter();

    assertNotNull(counter);
    assertEquals(0, counter.value);
}

3.2. Forhindring af adgang

Vores tæller  initialisering fungerer fint, men lad os forestille os, at vi ikke vil have underklasser til at tilsidesætte denne sikre initialisering.

Først skal vi gøre konstruktøren privat for at forhindre underklasser i at have adgang:

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

For det andet, lad os oprette en anden konstruktør, som underklasser kan kalde:

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

Endelig vores SimpleCounter er påkrævet for at tilsidesætte den parametriserede konstruktør, ellers vil den ikke kompilere:

public class SimpleCounter extends Counter {

    public SimpleCounter(int value) {
        super(value);
    }

    // concrete methods
}

Læg mærke til, hvordan compileren forventer, at vi kalder super(værdi) på denne konstruktør for at begrænse adgangen til vores private no-arguments constructor.

4. Parametriserede konstruktører

En af de mest almindelige anvendelser for konstruktører i abstrakte klasser er at undgå redundans . Lad os skabe et eksempel ved hjælp af biler for at se, hvordan vi kan drage fordel af parametriserede konstruktører.

Vi begynder med en abstrakt Bil klasse til at repræsentere alle typer biler. Vi har også brug for en afstand ejendom for at vide, hvor meget den har rejst:

public abstract class Car {

    int distance;

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

Vores superklasse ser godt ud, men vi vil ikke have afstanden egenskab, der skal initialiseres med en værdi, der ikke er nul. Vi ønsker også at forhindre underklasser i at ændre afstanden egenskab eller tilsidesættelse af den parametriserede konstruktør.

Lad os se, hvordan du begrænser adgangen til afstand og brug konstruktører til at initialisere det sikkert:

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
}

Nu, vores afstand ejendom og parameteriseret konstruktør er private. Der er en offentlig standardkonstruktør Car() der uddelegerer den private konstruktør til at initialisere afstand .

For at bruge vores afstand ejendom, lad os tilføje noget adfærd for at få og vise bilens grundlæggende oplysninger:

abstract String getInformation();

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

Alle underklasser skal levere en implementering af getInformation() , og display() metoden vil bruge den til at udskrive alle detaljer.

Lad os nu oprette ElectricCar og FuelCar underklasser:

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();
    }
}

Lad os se disse underklasser i aktion:

ElectricCar electricCar = new ElectricCar(8);
electricCar.display();

FuelCar fuelCar = new FuelCar("Gasoline");
fuelCar.display();

Det producerede output ser sådan ud:

Car default constructor
Electric Car
Charging Time: 8
Distance: 0

Car default constructor
Fuel Car
Fuel type: Gasoline
Distance: 0

5. Konklusion

Som alle andre klasser i Java kan abstrakte klasser have konstruktører, selv når de kun kaldes fra deres konkrete underklasser.

I denne artikel gennemgik vi hver type konstruktør ud fra abstrakte klassers perspektiv – hvordan de er relateret til konkrete underklasser, og hvordan kan vi bruge dem i praktiske use cases.

Som altid kan kodeeksempler findes på GitHub.


Java tag