CodeGym /Java-Blog /Germany /Konstruktoren in Java
Autor
Jesse Haniel
Lead Software Architect at Tribunal de Justiça da Paraíba

Konstruktoren in Java

Veröffentlicht in der Gruppe Germany
Hallo! Heute werden wir uns mit einem sehr wichtigen Thema befassen, das unsere Objekte betrifft. Ohne Übertreibung können wir sagen, dass dich dieses Thema im echten Leben jeden Tag begleiten wird! Wir sprechen von Konstruktoren. Vielleicht hörst du diesen Begriff zum ersten Mal, aber tatsächlich hast du den Konstruktor in Java bereits verwendet. Du hast es nur nicht gemerkt :) Davon überzeugen wir uns später. Konstruktoren in Java - 1

Was ist ein Konstruktor in Java und warum brauchen wir sowas?

Sehen wir uns ein Beispiel für einen Konstruktor in Java an. Oder besser zwei.

public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 378;

   }
}
Wir haben unser Auto erstellt und sein Modell und seine Höchstgeschwindigkeit festgelegt. Aber das Objekt Auto hätte in einem realen Projekt offensichtlich nicht nur 2 Felder. Es könnte zum Beispiel 16 Felder haben!

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

   }

}
Wir haben ein neues Car-Objekt erstellt. Es gibt ein Problem: Es gibt 16 Felder, aber wir haben nur 12 initialisiert! Sieh dir den Code an und versuche, die Felder zu finden, die wir vergessen haben! Nicht so einfach, was? In dieser Situation kann ein Programmierer leicht einen Fehler machen und vergessen, ein Feld zu initialisieren. Dies führt dann zu einem falschen Verhalten des Programms:

public class Car {

   String model;// model
   int maxSpeed;// maximum speed
   int wheels;// wheel width
   double engineVolume;// engine volume
   String color;// color
   int productionYear;// production year
   String ownerFirstName;// first name of owner
   String ownerLastName;// last name of owner
   long price;// price
   boolean isNew;// flag indicating whether car is new
   int seatsInTheCar;// number of seats in the car
   String cabinMaterial;// interior material
   boolean insurance;// flag indicating whether car is insured
   String manufacturerCountry;// manufacturer country
   int trunkVolume;// size of the trunk
   int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.productionYear = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.seatsInTheCar = 2;
       bugatti.maxSpeed = 378;
       bugatti.model = "Bugatti Veyron";

       System.out.println("Model: Bugatti Veyron. Engine volume: " + bugatti.engineVolume + ". Trunk volume: " + bugatti.trunkVolume + ". Cabin material: " + bugatti.cabinMaterial +
       ". Wheel width: " + bugatti.wheels + ". Purchased in 2018 by Mr. " + bugatti.ownerLastName);

   }

}
Konsolenausgabe:
Modell: Bugatti Veyron. Hubraum: 6.3. Kofferraumvolumen: 0. Karosseriematerial: null. Radbreite: 0. Gekauft im Jahr 2018 von Herrn null
Dein Käufer, der 2 Millionen Dollar für das Auto ausgegeben hat, wird es wohl nicht so toll finden, wenn man ihn „Herr null“ nennt! Aber im Ernst, unser Programm hat ein Objekt ganz einfach falsch erstellt: ein Auto mit einer Radbreite von 0 (d. h. ohne Räder), einem fehlenden Kofferraum, einer Karosserie aus unbekanntem Material und vor allem einem unbestimmten Besitzer. Man will sich gar nicht vorstellen, welche Folgen solch ein Fehler haben kann, wenn das Programm läuft! Wir müssen solche Situationen irgendwie verhindern. Wir müssen unser Programm einschränken: Wenn wir ein neues Car-Objekt erstellen, wollen wir, dass die Felder, wie z. B. das Modell und die Höchstgeschwindigkeit, immer angegeben werden. Andernfalls wollen wir die Erstellung des Objekts verhindern. Konstruktoren meistern diese Aufgabe mit Leichtigkeit. Sie haben ihren Namen genau aus diesem Grund erhalten. Der Konstruktor erstellt eine Art „Skelett“ der Klasse, dem jedes neue Objekt entsprechen muss. Der Einfachheit halber gehen wir noch einmal zur einfachere Version der Car-Klasse mit zwei Feldern zurück. Unter Berücksichtigung unserer Anforderungen wird der Konstruktor der Car-Klasse so aussehen:

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}

// And creating an object now looks like this:

public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 378);
}
Sieh dir an, wie ein Konstruktor deklariert wird. Er ähnelt einer gewöhnlichen Methode, hat aber keinen Rückgabetyp. Außerdem wird der Konstruktor mit dem Klassennamen (Car) benannt, beginnend mit einem Großbuchstaben. Zusätzlich wird der Konstruktor mit einem für dich neuen Schlüsselwort verwendet this. this ist das Schlüsselwort zur Angabe eines bestimmten Objekts. Der Code im Konstruktor

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
kann fast wörtlich interpretiert werden: „Das Modell (model) für dieses Auto (das wir jetzt gerade erstellen) ist das model-Argument, das dem Konstruktor übergeben wird. Die Höchtsgeschwindigkeit (maxSpeed) für dieses Auto (das wir erstellen) ist das maxSpeed-Argument, das an den Konstruktor übergeben wird.“ Und das ist genau das, was passiert:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car("Bugatti Veyron", 378);
       System.out.println(bugatti.model);
       System.out.println(bugatti.maxSpeed);
   }

}
Konsolenausgabe:
Bugatti Veyron 378
Der Konstruktor hat die nötigen Werte korrekt zugewiesen. Du hast vielleicht bemerkt, dass ein Konstruktor einer gewöhnlichen Methode sehr ähnlich ist! So ist es. Ein Konstruktor ist eigentlich eine Methode, aber mit spezifischen Merkmalen :) Genau wie bei den Methoden haben wir Argumente an unseren Konstruktor weitergegeben. Und genau wie der Aufruf einer Methode funktioniert auch der Aufruf eines Konstruktors nur dann, wenn du sie angibst:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car(); // Error!
   }
  
}
Wie du siehst, erreichen wir mit dem Konstruktor genau das, was wir wollten. Wir können jetzt kein Auto mehr ohne Geschwindigkeit und Modell erstellen! Aber die Ähnlichkeit zwischen Konstruktoren und Methoden ist damit noch nicht beendet. Genau wie Methoden können auch Konstruktoren überladen werden. Stell dir vor, du hast 2 Hauskatzen zu Hause. Eine davon hast du als kleines Kätzchen bekommen. Aber die zweite hast du von der Straße adoptiert, als sie schon erwachsen war, und du weiß nicht genau, wie alt sie ist. In diesem Fall möchten wir, dass unser Programm in der Lage ist, zwei Arten von Katzen zu erstellen: solche mit einem Namen und einem Alter (für die erste Katze) und solche mit lediglich einem Namen (für die zweite Katze). Dafür werden wir den Konstruktor überladen:

public class Cat {

   String name;
   int age;

   // For the first cat
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For the second cat
   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
Zusätzlich zu dem ursprünglichen Konstruktor mit den Parametern „Name“ und „Alter“ haben wir einen weiteren mit nur einem Name-Parameter hinzugefügt. Genau so, wie wir in früheren Lektionen die Methoden überladen haben. Jetzt können wir beide Arten von Katzen erstellen :) Erinnerst du dich, dass wir zu Beginn der Lektion gesagt haben, dass du bereits Konstruktoren eingesetzt hast, ohne es zu merken? Und genau so ist es tatsächlich. Tatsache ist, dass jede Klasse in Java über einen so genannten Standardkonstruktor verfügt. Er benötigt keine Argumente, aber er wird jedes Mal aufgerufen, wenn du ein Objekt einer beliebigen Klasse erstellst.

public class Cat {

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
Auf den ersten Blick ist er unsichtbar. Wir haben ein Objekt erstellt, und jetzt? Wo macht der Konstruktor denn etwas? Um das zu sehen, schreiben wir explizit einen leeren Konstruktor für die Klasse Cat. Wir werden darin einen Satz anzeigen. Wenn der Satz angezeigt wird, dann wurde der Konstruktor aufgerufen.

public class Cat {

   public Cat() {
       System.out.println("A cat has been created!");
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); // The default constructor is invoked here
   }
}
Konsolenausgabe:
Eine Katze wurde erstellt!
Da ist die Bestätigung! Der Standardkonstruktor ist in deinen Klassen immer unsichtbar vorhanden. Aber du musst noch etwas darüber wissen. Der Standardkonstruktor wird aus einer Klasse entfernt, sobald du einen Konstruktor mit Argumenten erstellst. Tatsächlich haben wir den Beweis dafür bereits oben gesehen. Er ist in diesem Code zu finden:

public class Cat {

   String name;
   int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat(); //Error!
   }
}
Wir konnten kein Cat-Objekt ohne Namen und Alter erstellen, weil wir einen Cat-Konstruktor mit String- und int-Parametern deklariert haben. Dies führte dazu, dass der Standardkonstruktor sofort aus der Klasse verschwand. Vergiss also nicht, dass du, wenn du mehrere Konstruktoren in deiner Klasse benötigst, einschließlich eines Konstruktors ohne Argumente, diesen separat deklarieren musst. Nehmen wir zum Beispiel an, wir erstellen ein Programm für eine Tierklinik. Unsere Klinik möchte Gutes tun und obdachlosen Kätzchen helfen, deren Namen und Alter unbekannt sind. Dann sollte unser Code so aussehen:

public class Cat {

   String name;
   int age;

   // For cats with owners
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   // For street cats
   public Cat() {
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 5);
       Cat streetCat = new Cat();
   }
}
Nun, da wir einen expliziten Standardkonstruktor geschrieben haben, können wir beide Arten von Katzen erstellen :) Wie bei jeder Methode ist die Reihenfolge der an einen Konstruktor übergebenen Argumente sehr wichtig. Wir wollen einmal die name- und age-Argumente in unserem Konstruktor austauschen.

public class Cat {

   String name;
   int age;

   public Cat(int age, String name) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat smudge = new Cat("Smudge", 10); // Error!
   }
}
Ein Fehler! Der Konstruktor legt eindeutig fest, dass beim Erstellen eines Cat-Objekts eine Zahl und eine Zeichenfolge in dieser Reihenfolge übergeben werden müssen. Unser Code funktioniert also nicht. Vergiss das nicht und denk daran, wenn du deine eigenen Klassen deklarierst:

public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}

public Cat(int age, String name) {
   this.age = age;
   this.name = name;
}
Das sind zwei völlig unterschiedliche Konstruktoren! Würden wir in einem einzigen Satz die Antwort auf die Frage „Warum brauche ich einen Konstruktor?“ geben, könnten wir sagen: „Um sicherzustellen, dass Objekte immer einen gültigen Zustand haben“. Wenn du Konstruktoren verwendest, werden alle deine Variablen korrekt initialisiert. Deine Programme werden keine Autos mit einer Geschwindigkeit von 0 oder andere „ungültige“ Objekte haben. Sie bieten vor allem dem Programmierer Vorteile. Wenn du Felder manuell initialisierst (nach dem Erstellen eines Objekts), besteht ein hohes Risiko, dass du etwas übersiehst und einen Fehler in den Code einführst. Aber mit einem Konstruktor wird dies nicht passieren: Wenn du nicht alle erforderlichen Argumente oder die falschen Typen von Argumenten übergibst, wird der Compiler sofort einen Fehler melden. Wir müssen noch darauf hinweisen, dass du die Logik deines Programms nicht in einen Konstruktor packen solltest. Dazu sind Methoden da. Methoden sind die Orte, an denen du die gesamte erforderliche Funktionalität definieren solltest. Sehen wir uns mal an, warum es eine schlechte Idee ist, Logik in einen Konstruktor zu integrieren:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factory is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
Wir haben eine CarFactory-Klasse, welche die Autofabrik beschreibt. Innerhalb des Konstruktors initialisieren wir alle Felder und fügen ein wenig Logik ein: Wir zeigen einige Informationen über die Fabrik an. Das scheint gar nicht schlimm zu sein. Das Programm funktioniert gut. Konsolenausgabe:
Unsere Autofabrik heißt Ford Sie wurde vor 115 Jahren gegründet Seither hat sie 50000000 Autos produziert Durchschnittlich produziert sie 434782 Autos pro Jahr
Aber wir haben tatsächlich eine Mine mit Zeitzünder gelegt. Und diese Art von Code kann sehr leicht zu Fehlern führen. Angenommen, wir sprechen jetzt nicht über Ford, sondern über eine neue Fabrik namens „Amigo Motors“, die seit weniger als einem Jahr besteht und 1000 Autos produziert hat:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Our car factor is called " + this.name);
   System.out.println("It was founded " + this.age + " years ago" );
   System.out.println("Since that time, it has produced " + this.carsCount +  " cars");
   System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
Konsolenausgabe:
Unsere Autofabrik heißt Amigo Motors Exception in thread "main" java.lang.ArithmeticException: / by zero Sie wurde vor 0 Jahren gegründet Seither hat sie 1000 Autos produziert at CarFactory.(CarFactory.java:15) at CarFactory.main(CarFactory.java:23) Process finished with exit code 1
Bumm! Das Programm endet mit einem ziemlich unverständlichen Fehler. Kannst du die Ursache herausfinden? Das Problem liegt in der Logik, die wir in den Konstruktor integriert haben. Es geht um diese Zeile:

System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
Hier führen wir eine Berechnung durch und teilen die Anzahl der produzierten Autos durch das Alter der Fabrik. Und da unsere Fabrik neu ist (d. h. sie ist 0 Jahre alt), dividieren wir durch 0, was wir in der Mathematik nicht dürfen. Folglich bricht das Programm mit einem Fehler ab. Was hätten wir stattdessen tun sollen? Wir müssen die gesamte Logik in eine eigene Methode setzen. Nennen wir sie printFactoryInfo(). Du kannst ihr ein CarFactory-Objekt als Argument übergeben. Hier können wir die gesamte Logik unterbringen und gleichzeitig potenzielle Fehler (wie eine Division durch 0) abfangen. Alles hat seinen Platz. Konstruktoren werden benötigt, um einen gültigen Objektzustand festzulegen. Für die Programmlogik haben wir Methoden. Das darf man nicht vermischen.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION