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 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 getPhone() {
		return Optional.ofNullable(this.phone);
	}
	
	public void setPhone(final String phone) {
		this.phone = phone;
	}
	
	public Optional 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;
	// @formatter:off
	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);
	// @formatter:on
	}
}
						
					

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.

 
email senden an Telefon: 07477/7904023   |  0163/3860364