Beste Möglichkeit, mehrere Konstruktoren in Java zu handhaben

Lesezeit: 8 Minuten

Ich habe mich gefragt, was der beste (dh sauberste/sicherste/effizienteste) Weg zum Umgang mit mehreren Konstruktoren in Java ist. Besonders wenn in einem oder mehreren Konstruktoren nicht alle Felder angegeben sind:

public class Book
{

    private String title;
    private String isbn;

    public Book()
    {
      //nothing specified!
    }

    public Book(String title)
    {
      //only title!
    }

    ...     

}

Was soll ich tun, wenn Felder nicht angegeben sind? Ich habe bisher Standardwerte in der Klasse verwendet, damit ein Feld niemals null ist, aber ist das eine “gute” Vorgehensweise?

  • Es hängt davon ab, ob alle Felder einen Wert enthalten müssen?

    – CodeMonkey

    24. Februar 2009 um 14:16 Uhr

  • Ich mag es nicht, wenn Leute Fragen stellen und dann keine Antworten akzeptieren.

    – Anton Krug

    19. November 2015 um 0:49 Uhr

Benutzer-Avatar
Craig P. Motlin

Eine etwas vereinfachte Antwort:

public class Book
{
    private final String title;

    public Book(String title)
    {
      this.title = title;
    }

    public Book()
    {
      this("Default Title");
    }

    ...
}

  • +1 Ich finde es eine gute Faustregel, dass alle Ihre Konstrukteure einen gemeinsamen “Choke Point” durchlaufen sollten.

    – Kletus

    24. Februar 2009 um 14:25 Uhr

  • Außerdem ist es immer ratsam, jeden Konstruktor mit this() oder super() zu beginnen =8-)

    – Yuwal

    24. Februar 2009 um 14:44 Uhr

  • +1 für Konstruktorverkettung – für zusätzliche Markierungen kann “Titel” wahrscheinlich als endgültig deklariert werden, da es unwahrscheinlich ist, dass er sich während der Lebensdauer eines Buchobjekts ändert (Unveränderlichkeit ist wirklich gut!).

    – Andrzej Doyle

    24. Februar 2009 um 15:45 Uhr

  • Sie sollten wahrscheinlich auch eine Ausnahme auslösen, wenn der Titel im Konstruktor null ist, auf diese Weise können Sie garantieren, dass er in einer Methode NIEMALS null ist (was äußerst hilfreich sein kann). Dies ist eine gute Angewohnheit, wann immer möglich (final + not-null).

    – Bill K

    24. Februar 2009 um 17:11 Uhr

  • Seien Sie vorsichtig mit den Nullprüfungen. misko.hevery.com/2009/02/09/to-assert-or-not-to-assert

    – Craig P. Motlin

    24. Februar 2009 um 17:51 Uhr

Benutzer-Avatar
kggrad

Erwägen Sie die Verwendung des Builder-Musters. Es ermöglicht Ihnen, Standardwerte für Ihre Parameter festzulegen und auf klare und präzise Weise zu initialisieren. Zum Beispiel:


    Book b = new Book.Builder("Catcher in the Rye").Isbn("12345")
       .Weight("5 pounds").build();

Bearbeiten: Es beseitigt auch die Notwendigkeit mehrerer Konstruktoren mit unterschiedlichen Signaturen und ist viel besser lesbar.

  • Dies ist die beste Idee, wenn Sie mehrere Konstruktoren haben, die Sie leicht für neue Typen erweitern können und weniger fehleranfällig sind

    – düster

    24. Februar 2009 um 14:29 Uhr

  • @Dinesh – Ich stimme zu, ich verwende Builders in meinem gesamten Code. Ich liebe das Muster!

    – kggrad

    24. Februar 2009 um 14:35 Uhr

  • In meinen Augen ist es wie ein Fluent Interface (codemonkeyism.com/archives/2007/10/10/…)

    – Alepuzio

    24. Februar 2009 um 16:51 Uhr

  • Der Builder kann jedoch etwas lästig sein, wenn Sie ihn erweitern möchten (stackoverflow.com/questions/313729/…).

    – cdmckay

    24. Februar 2009 um 16:59 Uhr

Benutzer-Avatar
Luca Touraille

Sie müssen angeben, was die Klasseninvarianten sind, dh Eigenschaften, die für eine Instanz der Klasse immer wahr sind (z. B. wird der Titel eines Buches niemals null sein oder die Größe eines Hundes wird immer > 0 sein).

Diese Invarianten sollten während der Konstruktion festgelegt und während der Lebensdauer des Objekts beibehalten werden, was bedeutet, dass Methoden die Invarianten nicht brechen dürfen. Die Konstruktoren können diese Invarianten entweder durch obligatorische Argumente oder durch Festlegen von Standardwerten festlegen:

class Book {
    private String title; // not nullable
    private String isbn;  // nullable

    // Here we provide a default value, but we could also skip the 
    // parameterless constructor entirely, to force users of the class to
    // provide a title
    public Book()
    {
        this("Untitled"); 
    }

    public Book(String title) throws IllegalArgumentException
    {
        if (title == null) 
            throw new IllegalArgumentException("Book title can't be null");
        this.title = title;
        // leave isbn without value
    }
    // Constructor with title and isbn
}

Die Wahl dieser Invarianten hängt jedoch stark von der Klasse ab, die Sie schreiben, wie Sie sie verwenden usw., sodass es keine endgültige Antwort auf Ihre Frage gibt.

  • Sie möchten überprüfen, ob null nicht an diesen zweiten Konstruktor übergeben wird.

    – Tom Hawtin – Angelleine

    24. Februar 2009 um 15:09 Uhr

Benutzer-Avatar
Lawrence Dol

Sie sollten immer ein gültiges und legitimes Objekt konstruieren; und wenn Sie keine Konstruktorparameter verwenden können, sollten Sie ein Builder-Objekt verwenden, um eines zu erstellen, und das Objekt erst dann aus dem Builder freigeben, wenn das Objekt vollständig ist.

Zur Frage der Verwendung von Konstruktoren: Ich versuche immer, einen Basiskonstruktor zu haben, auf den sich alle anderen beziehen, der sich mit “weggelassenen” Parametern zum nächsten logischen Konstruktor durchkettet und am Basiskonstruktor endet. So:

class SomeClass
{
SomeClass() {
    this("DefaultA");
    }

SomeClass(String a) {
    this(a,"DefaultB");
    }

SomeClass(String a, String b) {
    myA=a;
    myB=b;
    }
...
}

Wenn dies nicht möglich ist, versuche ich, eine private init() -Methode zu haben, auf die alle Konstruktoren zurückgreifen.

Und halten Sie die Anzahl der Konstruktoren und Parameter klein – maximal 5 von jedem als Richtlinie.

Es könnte sich lohnen, die Verwendung einer statischen Factory-Methode anstelle von Konstruktor in Betracht zu ziehen.

ich sage stattdessenaber offensichtlich kannst du das nicht ersetzen der Konstrukteur. Was Sie jedoch tun können, ist, den Konstruktor hinter einer statischen Factory-Methode zu verstecken. Auf diese Weise veröffentlichen wir die statische Factory-Methode als Teil der Klassen-API, verstecken aber gleichzeitig den Konstruktor und machen ihn privat oder paketieren privat.

Es ist eine relativ einfache Lösung, insbesondere im Vergleich zum Builder-Muster (wie in Joshua Blochs Effektive Java 2. Auflage – Vorsicht, Gang of Four’s Designmuster ein völlig anderes Entwurfsmuster mit demselben Namen definieren, was etwas verwirrend sein könnte), was das Erstellen einer verschachtelten Klasse, eines Builder-Objekts usw. impliziert.

Dieser Ansatz fügt eine zusätzliche Abstraktionsebene zwischen Ihnen und Ihrem Kunden hinzu, stärkt die Kapselung und erleichtert spätere Änderungen. Es gibt Ihnen auch Instanzkontrolle – da die Objekte innerhalb der Klasse instanziiert werden, entscheiden Sie und nicht der Client, wann und wie diese Objekte erstellt werden.

Schließlich erleichtert es das Testen – indem es einen dummen Konstruktor bereitstellt, der den Feldern nur die Werte zuweist, ohne Logik oder Validierung durchzuführen, ermöglicht es Ihnen, einen ungültigen Zustand in Ihr System einzuführen, um zu testen, wie es sich verhält und darauf reagiert. Das ist nicht möglich, wenn Sie Daten im Konstruktor validieren.

Viel mehr darüber können Sie im (bereits erwähnten) Joshua Bloch nachlesen Effektive Java 2. Auflage – es ist ein wichtiges Werkzeug in der Werkzeugkiste aller Entwickler und kein Wunder, dass es das Thema des 1. Kapitels des Buches ist. 😉

Nach deinem Beispiel:

public class Book {

    private static final String DEFAULT_TITLE = "The Importance of Being Ernest";

    private final String title;
    private final String isbn;

    private Book(String title, String isbn) {
        this.title = title;
        this.isbn = isbn;
    }

    public static Book createBook(String title, String isbn) {
        return new Book(title, isbn);
    }

    public static Book createBookWithDefaultTitle(String isbn) {
        return new Book(DEFAULT_TITLE, isbn);
    }

    ...

}

Welchen Weg Sie auch wählen, es ist eine gute Praxis, einen zu haben hauptsächlich Konstruktor, der einfach blind alle Werte zuweist, auch wenn er nur von anderen Konstruktoren verwendet wird.

Benutzer-Avatar
Scott Stanchfield

Einige allgemeine Tipps für Konstrukteure:

  • Versuchen Sie, die gesamte Initialisierung auf einen einzelnen Konstruktor zu konzentrieren und ihn von den anderen Konstruktoren aufzurufen
    • Dies funktioniert gut, wenn mehrere Konstruktoren vorhanden sind, um Standardparameter zu simulieren
  • Rufen Sie niemals eine nicht finale Methode von einem Konstruktor aus auf
    • Private Methoden sind per Definition final
    • Polymorphismus kann Sie hier töten; Sie können am Ende eine Unterklassenimplementierung aufrufen, bevor die Unterklasse initialisiert wurde
    • Wenn Sie “Hilfsmethoden” benötigen, achten Sie darauf, sie privat oder final zu machen
  • Seien Sie in Ihren Aufrufen von super() explizit
    • Sie wären überrascht, wie viele Java-Programmierer nicht erkennen, dass super() aufgerufen wird, auch wenn Sie es nicht explizit schreiben (vorausgesetzt, Sie haben keinen Aufruf für this(…) ).
  • Kennen Sie die Reihenfolge der Initialisierungsregeln für Konstruktoren. Es ist im Grunde:

    1. dies(…) falls vorhanden (Nur zu einem anderen Konstrukteur wechseln)
    2. Anruf super (…) [if not explicit, call super() implicitly]
    3. (Unter Verwendung dieser Regeln rekursiv Oberklasse konstruieren)
    4. Felder über ihre Deklarationen initialisieren
    5. Rumpf des aktuellen Konstruktors ausführen
    6. Rückkehr zu vorherigen Konstruktoren (wenn Sie auf diese (…) Aufrufe gestoßen sind)

Der Gesamtfluss ergibt sich wie folgt:

  • Bewegen Sie sich in der Oberklassenhierarchie ganz nach oben zu Objekt
  • während nicht getan
    • init-Felder
    • Ausführen von Konstruktorkörpern
    • auf die Unterklasse herunterfallen

Versuchen Sie als nettes Beispiel für das Böse herauszufinden, was das Folgende ausgeben wird, und führen Sie es dann aus

package com.javadude.sample;

/** THIS IS REALLY EVIL CODE! BEWARE!!! */
class A {
    private int x = 10;
    public A() {
        init();
    }
    protected void init() {
        x = 20;
    }
    public int getX() {
        return x;
    }
}

class B extends A {
    private int y = 42;
    protected void init() {
        y = getX();
    }
    public int getY() {
        return y;
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        System.out.println("x=" + b.getX());
        System.out.println("y=" + b.getY());
    }
}

Ich werde Kommentare hinzufügen, die beschreiben, warum das obige so funktioniert, wie es funktioniert … Einiges davon mag offensichtlich sein; manches nicht…

Benutzer-Avatar
Steve Kuo

Eine weitere Überlegung: Wenn ein Feld erforderlich ist oder einen begrenzten Bereich hat, führen Sie die Überprüfung im Konstruktor durch:

public Book(String title)
{
    if (title==null)
        throw new IllegalArgumentException("title can't be null");
    this.title = title;
}

  • Sie sollten dies für alle Ihre öffentlichen Methoden tun. Überprüfen Sie immer die Argumente, das erspart Ihnen später einige Kopfschmerzen.

    – cdmckay

    24. Februar 2009 um 17:04 Uhr

1014140cookie-checkBeste Möglichkeit, mehrere Konstruktoren in Java zu handhaben

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy