JavaRush /Java-Blog /Random-DE /Kaffeepause Nr. 128. Leitfaden für Java-Datensätze

Kaffeepause Nr. 128. Leitfaden für Java-Datensätze

Veröffentlicht in der Gruppe Random-DE
Quelle: abhinavpandey.dev In diesem Tutorial behandeln wir die Grundlagen der Verwendung von Records in Java. Datensätze wurden in Java 14 eingeführt, um Boilerplate-Code rund um die Erstellung von Value-Objekten zu entfernen und gleichzeitig die Vorteile unveränderlicher Objekte zu nutzen. Kaffeepause Nr. 128.  Java Records Guide – 1

1. Grundkonzepte

Bevor wir auf die Einträge selbst eingehen, werfen wir einen Blick auf das Problem, das sie lösen. Dazu müssen wir uns daran erinnern, wie Wertobjekte vor Java 14 erstellt wurden.

1.1. Wertobjekte

Wertobjekte sind ein integraler Bestandteil von Java-Anwendungen. Sie speichern Daten, die zwischen Anwendungsschichten übertragen werden müssen. Ein Wertobjekt enthält Felder, Konstruktoren und Methoden für den Zugriff auf diese Felder. Nachfolgend finden Sie ein Beispiel für ein Wertobjekt:
public class Contact {
    private final String name;
    private final String email;

    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

1.2. Gleichheit zwischen Wertobjekten

Wertobjekte können auch eine Möglichkeit bieten, sie auf Gleichheit zu vergleichen. Standardmäßig vergleicht Java die Gleichheit von Objekten, indem es ihre Speicheradresse vergleicht. In einigen Fällen können jedoch Objekte, die dieselben Daten enthalten, als gleich betrachtet werden. Um dies zu implementieren, können wir die Methoden equal und .hashCode überschreiben . Implementieren wir sie für die Contact- Klasse :
public class Contact {

    // ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Contact contact = (Contact) o;
        return Object.equals(email, contact.email) &&
                Objects.equals(name, contact.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email);
    }
}

1.3. Unveränderlichkeit von Wertobjekten

Wertobjekte müssen unveränderlich sein. Das bedeutet, dass wir die Möglichkeiten, die Felder eines Objekts zu ändern, einschränken müssen. Dies ist aus folgenden Gründen ratsam:
  • Um das Risiko einer versehentlichen Änderung des Feldwerts zu vermeiden.
  • Um sicherzustellen, dass gleiche Objekte ein Leben lang gleich bleiben.
Da die Contact- Klasse bereits unveränderlich ist, gehen wir jetzt wie folgt vor:
  1. machte die Felder privat und endgültig .
  2. Für jedes Feld wurde nur ein Getter bereitgestellt (keine Setter ).

1.4. Registrieren von Wertobjekten

Oft müssen wir die in Objekten enthaltenen Werte registrieren. Dies erfolgt durch die Bereitstellung einer toString -Methode . Immer wenn ein Objekt registriert oder gedruckt wird, wird die toString- Methode aufgerufen . Der einfachste Weg besteht darin, den Wert jedes Felds auszudrucken. Hier ist ein Beispiel:
public class Contact {
    // ...
    @Override
    public String toString() {
        return "Contact[" +
                "name='" + name + '\'' +
                ", email=" + email +
                ']';
    }
}

2. Reduzieren Sie Vorlagen mit Datensätzen

Da die meisten Wertobjekte die gleichen Anforderungen und Funktionen haben, wäre es schön, den Prozess ihrer Erstellung zu vereinfachen. Schauen wir uns an, wie Aufnahmen dazu beitragen, dies zu erreichen.

2.1. Konvertieren der Person-Klasse in Record

Erstellen wir einen Kontaktklasseneintrag , der die gleiche Funktionalität wie die oben definierte Kontaktklasse hat .
public record Contact(String name, String email) {}
Das Schlüsselwort „record“ wird zum Erstellen einer Record- Klasse verwendet . Datensätze können vom Aufrufer wie eine Klasse verarbeitet werden. Um beispielsweise eine neue Eintragsinstanz zu erstellen, können wir das Schlüsselwort new verwenden .
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");

2.2. Standardverhalten

Wir haben den Code auf eine Zeile reduziert. Lassen Sie uns auflisten, was es beinhaltet:
  1. Die Felder „Name“ und „E-Mail“ sind standardmäßig privat und endgültig.

  2. Der Code definiert einen „kanonischen Konstruktor“, der Felder als Parameter akzeptiert.

  3. Auf Felder kann über Getter-ähnliche Methoden zugegriffen werden – name() und email() . Es gibt keinen Setter für Felder, sodass die Daten im Objekt unveränderlich werden.

  4. Wir haben die toString- Methode implementiert , um die Felder genau so zu drucken, wie wir es für die Contact- Klasse getan haben .

  5. Implementierte Equals- und .hashCode- Methoden . Sie umfassen alle Felder, genau wie die Contact- Klasse .

2.3 Kanonischer Konstruktor

Der Standardkonstruktor nimmt alle Felder als Eingabeparameter und setzt sie auf Felder. Der standardmäßige kanonische Konstruktor ist beispielsweise unten dargestellt:
public Contact(String name, String email) {
    this.name = name;
    this.email = email;
}
Wenn wir in der Aufnahmeklasse einen Konstruktor mit derselben Signatur definieren, wird dieser anstelle des kanonischen Konstruktors verwendet.

3. Arbeiten mit Datensätzen

Wir können das Verhalten des Eintrags auf verschiedene Arten ändern. Schauen wir uns einige Anwendungsfälle an und wie man sie erreicht.

3.1. Überschreiben von Standardimplementierungen

Jede Standardimplementierung kann durch Überschreiben geändert werden. Wenn wir beispielsweise das Verhalten der toString- Methode ändern möchten , können wir sie zwischen den geschweiften Klammern {} überschreiben .
public record Contact(String name, String email) {
    @Override
    public String toString() {
        return "Contact[" +
                "name is '" + name + '\'' +
                ", email is" + email +
                ']';
    }
}
Ebenso können wir die Methoden equal und hashCode überschreiben .

3.2. Kompakte Bausätze

Manchmal möchten wir, dass Konstruktoren mehr tun, als nur Felder zu initialisieren. Dazu können wir unserem Eintrag im Compact Constructor die notwendigen Operationen hinzufügen. Es wird als kompakt bezeichnet, da keine Feldinitialisierung oder Parameterliste definiert werden muss.
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}
Beachten Sie, dass es keine Liste mit Parametern gibt und die Initialisierung von Name und E-Mail im Hintergrund erfolgt, bevor die Prüfung durchgeführt wird.

3.3. Konstruktoren hinzufügen

Sie können einem Datensatz mehrere Konstruktoren hinzufügen. Schauen wir uns einige Beispiele und Einschränkungen an. Fügen wir zunächst neue gültige Konstruktoren hinzu:
public record Contact(String name, String email) {
    public Contact(String email) {
        this("John Doe", email);
    }

    // replaces the default constructor
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}
Im ersten Fall wird mit dem Schlüsselwort this auf den Standardkonstruktor zugegriffen . Der zweite Konstruktor überschreibt den Standardkonstruktor, da er über dieselbe Parameterliste verfügt. In diesem Fall erstellt der Eintrag selbst keinen Standardkonstruktor. Es gibt verschiedene Einschränkungen für Konstruktoren.

1. Der Standardkonstruktor sollte immer von jedem anderen Konstruktor aufgerufen werden.

Der folgende Code lässt sich beispielsweise nicht kompilieren:
public record Contact(String name, String email) {
    public Contact(String name) {
        this.name = "John Doe";
        this.email = null;
    }
}
Diese Regel stellt sicher, dass Felder immer initialisiert werden. Außerdem ist gewährleistet, dass die im Kompaktkonstruktor definierten Operationen immer ausgeführt werden.

2. Es ist nicht möglich, den Standardkonstruktor zu überschreiben, wenn ein kompakter Konstruktor definiert ist.

Wenn ein kompakter Konstruktor definiert wird, wird automatisch ein Standardkonstruktor mit Initialisierung und kompakter Konstruktorlogik erstellt. In diesem Fall erlaubt uns der Compiler nicht, einen Konstruktor mit denselben Argumenten wie der Standardkonstruktor zu definieren. In diesem Code findet die Kompilierung beispielsweise nicht statt:
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

3.4. Schnittstellen implementieren

Wie bei jeder Klasse können wir Schnittstellen in Datensätzen implementieren.
public record Contact(String name, String email) implements Comparable<Contact> {
    @Override
    public int compareTo(Contact o) {
        return name.compareTo(o.name);
    }
}
Wichtiger Hinweis. Um vollständige Unveränderlichkeit zu gewährleisten, können Datensätze nicht vererbt werden. Die Einträge sind endgültig und können nicht erweitert werden. Sie können auch keine anderen Klassen erweitern.

3.5. Methoden hinzufügen

Neben Konstruktoren, die Methoden und Schnittstellenimplementierungen überschreiben, können wir auch beliebige Methoden hinzufügen. Zum Beispiel:
public record Contact(String name, String email) {
    String printName() {
        return "My name is:" + this.name;
    }
}
Wir können auch statische Methoden hinzufügen. Wenn wir beispielsweise eine statische Methode haben möchten, die einen regulären Ausdruck zurückgibt, anhand dessen wir E-Mails prüfen können, können wir sie wie folgt definieren:
public record Contact(String name, String email) {
    static Pattern emailRegex() {
        return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
    }
}

3.6. Felder hinzufügen

Wir können einem Datensatz keine Instanzfelder hinzufügen. Wir können jedoch statische Felder hinzufügen.
public record Contact(String name, String email) {
    private static final Pattern EMAIL_REGEX_PATTERN = Pattern
            .compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);

    static Pattern emailRegex() {
        return EMAIL_REGEX_PATTERN;
    }
}
Beachten Sie, dass es in statischen Feldern keine impliziten Einschränkungen gibt. Bei Bedarf sind sie möglicherweise öffentlich zugänglich und nicht endgültig.

Abschluss

Datensätze sind eine großartige Möglichkeit, Datenklassen zu definieren. Sie sind viel praktischer und leistungsfähiger als der JavaBeans/POJO-Ansatz. Da sie einfach zu implementieren sind, sollten sie anderen Möglichkeiten zur Schaffung von Wertobjekten vorgezogen werden.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION