wake-up-neo.net

Der beste Weg, um mehrere Konstruktoren in Java

Ich habe mich gefragt, wie ich mehrere Konstruktoren am besten (d. H. Am saubersten/sichersten/effizientesten) in Java) handhaben kann. Insbesondere, 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 keine Felder angegeben sind? Bisher habe ich in der Klasse Standardwerte verwendet, damit ein Feld nie null ist. Ist das jedoch eine "gute" Vorgehensweise?

72
Peter

Eine etwas vereinfachte Antwort:

public class Book
{
    private final String title;

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

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

    ...
}
136
Craig P. Motlin

Ziehen Sie die Verwendung des Builder-Musters in Betracht. Hiermit können Sie Standardwerte für Ihre Parameter festlegen und die Initialisierung übersichtlich und präzise durchführen. Beispielsweise:


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

Bearbeiten: Es sind nicht mehr mehrere Konstruktoren mit unterschiedlichen Signaturen erforderlich, und die Informationen sind besser lesbar.

34
kgrad

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

Diese Invarianten sollten während der Erstellung erstellt und während der gesamten Lebensdauer des Objekts beibehalten werden. Dies bedeutet, dass Methoden die Invarianten nicht zerstören dürfen. Die Konstruktoren können diese Invarianten festlegen, indem sie über obligatorische Argumente verfügen oder indem sie Standardwerte 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 Ihre Frage nicht endgültig beantwortet werden kann.

19
Luc Touraille

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

Zur Frage der Konstruktorverwendung: Ich versuche immer, einen Basiskonstruktor zu haben, auf den sich alle anderen beziehen, indem ich mit "ausgelassenen" Parametern zum nächsten logischen Konstruktor durchkettete und am Basiskonstruktor endete. 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 sich alle Konstruktoren beziehen.

Halten Sie die Anzahl der Konstruktoren und Parameter klein - jeweils maximal 5 als Richtlinie.

11
Lawrence Dol

Einige allgemeine Konstruktortipps:

  • Versuchen Sie, alle Initialisierungen in einem einzigen Konstruktor zu fokussieren und von den anderen Konstruktoren aufzurufen
    • Dies funktioniert gut, wenn mehrere Konstruktoren vorhanden sind, um Standardparameter zu simulieren
  • Niemals eine nicht endgültige Methode eines Konstruktors aufrufen
    • Private Methoden sind per Definition endgültig
    • Polymorphismus kann dich hier töten; Sie können am Ende eine Unterklassenimplementierung aufrufen, bevor die Unterklasse initialisiert wurde
    • Wenn Sie "Helfer" -Methoden benötigen, stellen Sie sicher, dass diese privat oder endgültig sind
  • Seien Sie ausdrücklich in Ihren Aufrufen zu super ()
    • Sie werden überrascht sein, wie viele Java Programmierer nicht erkennen, dass super () aufgerufen wird, auch wenn Sie es nicht explizit schreiben (vorausgesetzt, Sie haben keinen Aufruf dazu). ..))
  • Kennen Sie die Reihenfolge der Initialisierungsregeln für Konstruktoren. Es ist im Grunde:

    1. diese (...) falls vorhanden (nur in einen anderen Konstruktor verschieben)
    2. call super (...) [falls nicht explizit, call super () implizit]
    3. (Konstruiere Superklasse unter Verwendung dieser Regeln rekursiv)
    4. felder über ihre Deklarationen initialisieren
    5. führen Sie den Body des aktuellen Konstruktors aus
    6. zu vorherigen Konstruktoren zurückkehren (wenn Sie auf diese (...) Aufrufe gestoßen sind)

Der Gesamtfluss endet mit:

  • verschieben Sie die Oberklassenhierarchie ganz nach oben zu Object
  • solange nicht erledigt
    • init-Felder
    • konstruktorkörper ausführen
    • dropdown zur Unterklasse

Versuchen Sie für ein schönes Beispiel des Bösen herauszufinden, was das Folgende drucken 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 ... Einige davon mögen offensichtlich sein; manche ist nicht ...

6

Eine weitere Überlegung, ob ein Feld erforderlich ist oder einen begrenzten Bereich aufweist, ist die Überprüfung im Konstruktor:

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

Es kann sinnvoll sein, statt des Konstruktors eine statische Factory-Methode zu verwenden.

Ich sage statt , aber offensichtlich können Sie nicht ersetzen Konstrukteur. Sie können jedoch den Konstruktor hinter einer statischen Factory-Methode verbergen. Auf diese Weise veröffentlichen wir die statische Factory-Methode als Teil der Klassen-API, aber gleichzeitig verbergen wir den Konstruktor, indem wir ihn privat oder das Paket privat machen.

Dies ist eine relativ einfache Lösung, insbesondere im Vergleich zum Builder-Muster (wie in Joshua Blochs Effective Java 2nd Edition ) zu sehen. - Vorsicht, die Entwurfsmuster von Gang of Four definieren ein völlig anderes Entwurfsmuster mit demselben Namen, so dass dies möglicherweise etwas verwirrend ist), was das Erstellen einer verschachtelten Klasse impliziert , ein Builder-Objekt usw.

Dieser Ansatz fügt eine zusätzliche Abstraktionsebene zwischen Ihnen und Ihrem Kunden hinzu, wodurch die Kapselung gestärkt und Änderungen später vereinfacht werden. Sie können damit auch die Instanz steuern - da die Objekte innerhalb der Klasse instanziiert werden, entscheiden Sie und nicht der Client, wann und wie diese Objekte erstellt werden.

Schließlich wird das Testen einfacher - wenn Sie einen dummen Konstruktor bereitstellen, der den Feldern nur die Werte zuweist, ohne Logik oder Validierung durchzuführen, können Sie einen ungültigen Status in Ihr System einfügen, um zu testen, wie es sich verhält und auf diesen reagiert. Dies ist nicht möglich, wenn Sie Daten im Konstruktor validieren.

Sie können viel mehr darüber lesen in (bereits erwähnt) Joshua Blochs Effective Java 2nd Edition - es ist ein wichtiges Werkzeug in allen Toolboxen von Entwicklern und kein Wunder, dass es das Thema des ersten Kapitels des Buches ist. ;-)

Folgen Sie Ihrem 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);
    }

    ...

}

Wie auch immer Sie sich entscheiden, es empfiehlt sich, einen main -Konstruktor zu haben, der alle Werte blind zuweist, auch wenn er nur von einem anderen Konstruktor verwendet wird.

3

Ich würde folgendes tun:

 public class Book 
 {
 private final string title; 
 private final string isbn; 
 
 public book (final string t , final String i) 
 {
 if (t == null) 
 {
 throw new IllegalArgumentException ("t kann nicht null sein"); 
} 
 
 if (i == null) 
 {
 throw new IllegalArgumentException ("ich kann nicht null sein"); 
} 
 
 title = t; 
 isbn = i; 
} 
} 

Ich gehe hier davon aus, dass:

1) der titel wird sich niemals ändern (daher ist der titel endgültig) 2) der isbn wird sich niemals ändern (daher ist der isbn endgültig) 3) dass es nicht gültig ist, ein buch ohne titel und ohne isbn zu haben.

Betrachten Sie eine Studentenklasse:

 Student der öffentlichen Klasse 
 {
 private endgültige StudentID-ID; 
 privater String-Vorname; 
 privater String-Nachname; 
 
 public Student (endgültige StudentID i, 
 letzte Zeichenfolge zuerst, 
 letzte Zeichenfolge zuletzt) ​​
 {
 if (i == null) 
 {
 throw new IllegalArgumentException ("Ich kann nicht null sein"); 
} 
 
 if (first == null) 
 {
 throw new IllegalArgumentException ("first kann nicht null sein"); 
} 
 
 if (last == null) 
 {
 throw new IllegalArgumentException ("last kann nicht null sein"); 
} 
 
 id = i; 
 Vorname = zuerst; 
 Nachname = zuletzt; 
} 
} 

Dort muss ein Student mit einer ID, einem Vornamen und einem Nachnamen erstellt werden. Der Studentenausweis kann sich nie ändern, aber der Nachname und der Vorname einer Person können sich ändern (heiraten, den Namen ändern, weil eine Wette verloren geht usw.).

Wenn Sie sich für einen Konstrukteur entscheiden, müssen Sie sich überlegen, was Sinn macht. Allzu oft fügen die Leute set/get-Methoden hinzu, weil ihnen das beigebracht wird - aber sehr oft ist es eine schlechte Idee.

Unveränderliche Klassen sind viel besser zu haben (dh Klassen mit endgültigen Variablen) als veränderliche. Dieses Buch: http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1 (Effective Java) hat eine gute Diskussion über Unveränderlichkeit. Sehen Sie sich die Punkte 12 und 13 an.

0
TofuBeer

Mehrere Personen haben empfohlen, einen Null-Check hinzuzufügen. Manchmal ist das das Richtige, aber nicht immer. Schauen Sie sich diesen hervorragenden Artikel an, der zeigt, warum Sie ihn überspringen würden.

http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/

0
Craig P. Motlin