Unser Tech-Blog – Aktuelle und interessante Themen rund um unsere Arbeit

In unserem Tech-Blog teilen wir spannende Einblicke in die Welt der IT, aktuelle Trends und hilfreiche Tipps aus unserer täglichen Arbeit. Ob es um innovative Softwarelösungen, Best Practices bei der Digitalisierung oder Erfahrungsberichte aus erfolgreichen Projekten geht – hier finden Sie regelmäßig neue Artikel, die Sie auf dem Laufenden halten. Schauen Sie vorbei und entdecken Sie, wie wir Technologien einsetzen, um Unternehmen effizienter und zukunftssicher zu machen.

Lombok vs. Java Records vs. Native Implementierung: Ein Vergleich

Einführung

In diesem Beitrag vergleichen wir Lombok, Java Records und die native Implementierung in Java, um Ihnen zu helfen, die beste Wahl für Ihre Programmieraufgaben zu treffen. Jeder Ansatz hat seine eigenen Stärken und Schwächen, die wir im Detail betrachten werden.

Lombok

Lombok ist eine Bibliothek, die darauf abzielt, den Boilerplate-Code in Java-Anwendungen zu reduzieren. Mit Lombok können Sie einfache Annotationen verwenden, um automatisch Getter, Setter, equals(), hashCode() und toString() Methoden zu generieren.

Weitere Details über Lombok und die zugehörige Implementierung

Im Folgenden finden Sie ein Beispiel für eine Implementierung eines DTO (DataTransferObject) mit Builder-Pattern in Lobmok:

                    
import com.fasterxml.jackson.annotation.JsonInclude;

import jakarta.annotation.Nullable;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Data
@EqualsAndHashCode
@ToString
@Builder
public class LombokCustomerDTO {

    //Dlombok

    @Nullable
    private Long id;
    private String firstName;
    private String lastName;
    private String street;
    private String zip;
    private String city;
    @Nullable
    private String phone;
    @Nullable
    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    private String mail;
}
                    
                

Lombok-Annotationen

AnnotationBeschreibung
@DataGeneriert Getter, Setter, toString(), equals() und hashCode() Methoden für alle Felder der Klasse.
@Getter / @SetterGeneriert Getter- und Setter-Methoden für die angegebenen Felder oder die ganze Klasse.
@NoArgsConstructorGeneriert einen standardmäßigen No-Arg-Konstruktor.
@AllArgsConstructorGeneriert einen All-Arg-Konstruktor (Konstruktor mit allen Feldern als Argumente).
@RequiredArgsConstructorGeneriert einen Konstruktor für alle finalen Felder und für Felder, die mit @NonNull markiert sind.
@BuilderImplementiert das Builder-Pattern für die Klasse.
@SneakyThrowsWird verwendet, um geprüfte Ausnahmen ohne Deklaration in der Methode oder Verwendung von try-catch zu werfen.
@Slf4jFügt der Klasse ein SLF4J-Logger-Feld hinzu.
@NonNullWird verwendet, um zu markieren, dass ein Feld nicht null sein darf; generiert entsprechende Überprüfungen in generierten Methoden.
@ValueMacht die Klasse unveränderlich (alle Felder final) und generiert entsprechende Methoden wie bei @Data.

Java Records

Java Records, eingeführt in Java 14, sind spezielle Klassen, die unveränderliche Datenstrukturen darstellen. Sie bieten eine vereinfachte Syntax und generieren automatisch Methoden wie equals() und hashCode().

Weitere Details über Java Records und ihre Implementierung

Im Folgenden finden Sie ein Beispiel für eine Java Records-Implementierung eines DTO (DataTransferObject) mit Builder-Pattern:

                    
import static com.google.common.base.Preconditions.checkNotNull;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.annotation.Nullable;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record RecordCustomerDTO(Long id, String firstName, String lastName, String street, String zip, String city,
    String phone, String mail) {

    public RecordCustomerDTO(final Long id, final String firstName, final String lastName, final String street,
            final String zip, final String city, final String phone, final String mail) {
        this.id = id;
        this.firstName = checkNotNull(firstName, "missing firstName");
        this.lastName = checkNotNull(lastName, "missing lastName");
        this.street = checkNotNull(street, "missing street");
        this.zip = checkNotNull(zip, "missing zip");
        this.city = checkNotNull(city, "missing city");
        this.phone = phone;
        this.mail = mail;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {

        @Nullable
        private Long id;
        private String firstName;
        private String lastName;
        private String street;
        private String zip;
        private String city;
        @Nullable
        private String phone;
        @Nullable
        private String mail;

        public RecordCustomerDTO build() {
            return new RecordCustomerDTO(this.id, this.firstName, this.lastName, this.street, this.zip, this.city,
                    this.phone, this.mail);
        }

        public Builder city(final String city) {
            this.city = city;
            return this;
        }

        public Builder firstName(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder id(@Nullable final Long id) {
            this.id = id;
            return this;
        }

        public Builder lastName(final String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder mail(@Nullable final String mail) {
            this.mail = mail;
            return this;
        }

        public Builder phone(@Nullable final String phone) {
            this.phone = phone;
            return this;
            }

        public Builder street(final String street) {
            this.street = street;
            return this;
         }

        public Builder zip(final String zip) {
            this.zip = zip;
            return this;
        }
    }
}
                    
                

Native Implementierung

Die native Implementierung in Java erfordert, dass Sie alle Methoden manuell implementieren. Dies gibt Ihnen volle Kontrolle, bedeutet aber auch mehr Schreibaufwand und erhöhte Fehleranfälligkeit.

Weitere Details über Native Implementierung

Im Folgenden finden Sie ein Beispiel für eine native Java-Implementierung eines DTO (DataTransferObject) mit Builder-Pattern:

                    
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.base.MoreObjects;
import jakarta.annotation.Nullable;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class NativeCustomerDTO {

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {

        @Nullable
        private Long id;
        private String firstName;
        private String lastName;
        private String street;
        private String zip;
        private String city;
        @Nullable
        private String phone;
        @Nullable
        private String mail;

        public Builder city(final String city) {
            this.city = city;
            return this;
        }

        public Builder firstName(final String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder id(@Nullable final Long id) {
            this.id = id;
            return this;
        }

        public Builder lastName(final String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder mail(@Nullable final String mail) {
            this.mail = mail;
            return this;
        }

        public Builder phone(@Nullable final String phone) {
            this.phone = phone;
            return this;
        }

        public Builder street(final String street) {
            this.street = street;
            return this;
        }

        public Builder zip(final String zip) {
            this.zip = zip;
            return this;
        }

        public NativeCustomerDTO build() {
            return new NativeCustomerDTO(this);
        }
    }

    @Nullable
    private Long id;
    private String firstName;
    private String lastName;
    private String street;
    private String zip;
    private String city;
    @Nullable
    private String phone;
    @Nullable
    private final String mail;

    NativeCustomerDTO() {
        this.id = null;
        this.firstName = null;
        this.lastName = null;
        this.street = null;
        this.zip = null;
        this.city = null;
        this.phone = null;
        this.mail = null;
    }

    NativeCustomerDTO(final Builder builder) {
        this.id = builder.id;
        this.firstName = checkNotNull(builder.firstName, "missing firstName in builder");
        this.lastName = checkNotNull(builder.lastName, "missing lastName in builder");
        this.street = checkNotNull(builder.street, "missing street in builder");
        this.zip = checkNotNull(builder.zip, "missing zip in builder");
        this.city = checkNotNull(builder.city, "missing city in builder");
        this.phone = builder.phone;
        this.mail = builder.mail;
    }

    public Optional<Long> getID() {
        return Optional.ofNullable(this.id);
    }

    public void setID(@Nullable final Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(final String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(final String lastName) {
        this.lastName = lastName;
    }

    public String getStreet() {
        return this.street;
    }

    public void setStreet(final String street) {
        this.street = street;
    }

    public String getZIP() {
        return this.zip;
    }

    public void setZIP(final String zip) {
        this.zip = zip;
    }

    public String getCity() {
        return this.city;
    }

    public void setCity(final String city) {
        this.city = city;
    }

    public Optional<String> getPhone() {
        return Optional.ofNullable(this.phone);
    }

    public void setPhone(final String phone) {
        this.phone = phone;
        }

    public Optional<String> getMail() {
        return Optional.ofNullable(this.mail);
    }

    @Override
    public String toString() {
        //@formatter:off
        return MoreObjects.toStringHelper(this)
                .add("id", this.id)
                .add("firstName", this.firstName)
                .add("lastName", this.lastName)
                .add("street", this.street)
                .add("zip", this.zip)
                .add("city", this.city)
                .add("phone", this.phone)
                .add("mail", this.mail)
                .toString();
        // @formatter:on
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.id, this.firstName, this.lastName, this.street, this.zip, this.city, this.phone, this.mail);
    }

    @Override
    public boolean equals(final Object other) {

        if (other == this) {
            return true;
        }
        if (!(other instanceof NativeCustomerDTO)) {
            return false;
        }
        final NativeCustomerDTO nativeCustomerDTO = (NativeCustomerDTO) other;

        return  Objects.equals(this.id, nativeCustomerDTO.id)
                && Objects.equals(this.firstName, nativeCustomerDTO.firstName)
                && Objects.equals(this.lastName, nativeCustomerDTO.lastName)
                && Objects.equals(this.street, nativeCustomerDTO.street)
                && Objects.equals(this.zip, nativeCustomerDTO.zip)
                && Objects.equals(this.city, nativeCustomerDTO.city)
                && Objects.equals(this.phone, nativeCustomerDTO.phone)
                && Objects.equals(this.mail, nativeCustomerDTO.mail);
    }
}
                    
                

Vergleichstabelle

KriteriumLombokJava RecordsNative Implementierung
Boilerplate-CodeReduziert erheblichAutomatische Generierung, sehr reduziertVielleicht manueller Code
FlexibilitätMittel (durch Annotationen anpassbar)Eingeschränkt (festgelegte Struktur)Sehr hoch (volle Kontrolle)
VerständlichkeitKann versteckte Funktionalität enthaltenKlar und einfachDirekt und transparent
ToolingBenötigt Lombok-Plugin und -KonfigurationTeil des JDK ab Java 16, keine Extras nötigKeine zusätzlichen Tools erforderlich
LernkurveErfordert Verständnis der AnnotationenEinfach bei Verständnis von UnveränderlichkeitKeine speziellen Kenntnisse erforderlich
UnveränderlichkeitNicht standardmäßigStandardmäßig unveränderlichAbhängig von der Implementierung
AnpassbarkeitEinige Anpassungen möglichKeine Anpassung von automatischen MethodenVollständig anpassbar
Thread-SicherheitAbhängig von der ImplementierungInherently thread-sicher durch UnveränderlichkeitAbhängig von der Implementierung
Zusätzliche AbhängigkeitenBenötigt Lombok-BibliothekKeine zusätzlichen AbhängigkeitenKeine zusätzlichen Abhängigkeiten
KompatibilitätKann mit anderen Tools kollidierenGute Kompatibilität in modernem JavaUniversell kompatibel
WartungWeniger WartungsaufwandMinimaler WartungsaufwandHöherer Wartungsaufwand bei Änderungen

Fazit

Zusammenfassend lässt sich sagen, dass die Wahl zwischen Lombok, Java Records und der nativen Implementierung von Ihren spezifischen Anforderungen und Vorlieben abhängt. Jeder Ansatz hat seine Vor- und Nachteile, die berücksichtigt werden sollten.