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
Annotation | Beschreibung |
---|---|
@Data | Generiert Getter, Setter, toString() , equals() und hashCode() Methoden für alle Felder der Klasse. |
@Getter / @Setter | Generiert Getter- und Setter-Methoden für die angegebenen Felder oder die ganze Klasse. |
@NoArgsConstructor | Generiert einen standardmäßigen No-Arg-Konstruktor. |
@AllArgsConstructor | Generiert einen All-Arg-Konstruktor (Konstruktor mit allen Feldern als Argumente). |
@RequiredArgsConstructor | Generiert einen Konstruktor für alle finalen Felder und für Felder, die mit @NonNull markiert sind. |
@Builder | Implementiert das Builder-Pattern für die Klasse. |
@SneakyThrows | Wird verwendet, um geprüfte Ausnahmen ohne Deklaration in der Methode oder Verwendung von try-catch zu werfen. |
@Slf4j | Fügt der Klasse ein SLF4J-Logger-Feld hinzu. |
@NonNull | Wird verwendet, um zu markieren, dass ein Feld nicht null sein darf; generiert entsprechende Überprüfungen in generierten Methoden. |
@Value | Macht 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
Kriterium | Lombok | Java Records | Native Implementierung |
---|---|---|---|
Boilerplate-Code | Reduziert erheblich | Automatische Generierung, sehr reduziert | Vielleicht manueller Code |
Flexibilität | Mittel (durch Annotationen anpassbar) | Eingeschränkt (festgelegte Struktur) | Sehr hoch (volle Kontrolle) |
Verständlichkeit | Kann versteckte Funktionalität enthalten | Klar und einfach | Direkt und transparent |
Tooling | Benötigt Lombok-Plugin und -Konfiguration | Teil des JDK ab Java 16, keine Extras nötig | Keine zusätzlichen Tools erforderlich |
Lernkurve | Erfordert Verständnis der Annotationen | Einfach bei Verständnis von Unveränderlichkeit | Keine speziellen Kenntnisse erforderlich |
Unveränderlichkeit | Nicht standardmäßig | Standardmäßig unveränderlich | Abhängig von der Implementierung |
Anpassbarkeit | Einige Anpassungen möglich | Keine Anpassung von automatischen Methoden | Vollständig anpassbar |
Thread-Sicherheit | Abhängig von der Implementierung | Inherently thread-sicher durch Unveränderlichkeit | Abhängig von der Implementierung |
Zusätzliche Abhängigkeiten | Benötigt Lombok-Bibliothek | Keine zusätzlichen Abhängigkeiten | Keine zusätzlichen Abhängigkeiten |
Kompatibilität | Kann mit anderen Tools kollidieren | Gute Kompatibilität in modernem Java | Universell kompatibel |
Wartung | Weniger Wartungsaufwand | Minimaler Wartungsaufwand | Hö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.