Java : mit „Projekt Lombok“ gegen Boilerplatecode

Mit dem „Projekt Lombok“ kann man den Boilerplatecode seines Projektes auf ein Minimum reduzieren.

Aber was ist Boilerplatecode?

Boilerplatecode ist Quellcode welcher für die Verwendung von Klassen (und APIs) benötigt wird und nur ein geringes Abstraktionsniveau hat. D.h. es geht hier um Methoden wie Getter, Setter, Konstuktoren, equals, hascode und toString. Diese Methoden werden Bsp. Benötigt um POJOs, Entitys oder andere Objekte zu erzeugen, aber sonst haben diese keinen besonderen Mehrwert für die Anwendung.

Wie richtet man Projekt Lombok ein?

Um die Funktionen von Projekt Lombok (Im nachfolgenden nur noch als Lombok bezeichnet.) zu nutzen muss man ein Plugin installieren.

Dieses Plugin ist derzeit (stand 25.09.2018) leider nicht per Marketplace installierbar. 

Das Plugin muss somit von der Herstellerseite https://projectlombok.org/ heruntergeladen werden und kann danach mit einem doppelklick gestartet werden.

Das Plugin ist ein JavaArchiv, einige Browser werden beim Download eine Warnung anzeigen welche man Aktzeptieren muss.

Installieren des Plugins

Nachdem die Datei „lombok.jar“ mit einem doppelklick gestartet wurde, wird nach Eclipse Installationen gesucht.

Projekt Lombok - suchen nach Eclipse installationen
Projekt Lombok – suchen nach Eclipse installationen

Die Entwicklungsumgebung Eclipse kann als ZIP entpackt und in ein beliebiges Verzeichnis abgelegt werden, daher kann die Suche etwas länger dauern.

Wenn die Eclipse Installation nicht gefunden wurde, so kann diese mit der Schaltfläche „Specify location…“ hinzugefügt werden.
Im nächsten Schritt wird nun die Eclipse Installation durch das Setzen der Checkbox ausgewählt (1) (per default sind alle gewählt) und mit der Schaltfläche „Install / Update“ (2) wird die Installation gestartet.

Projekt Lombok - zusätzlich hinzugefügte Eclipse installation
Projekt Lombok – zusätzlich hinzugefügte Eclipse installation

Wurde das Plugin korrekt installiert, so wird der folgende Dialog angezeigt.

Projekt Lombok - installation des Plugins abgeschlossen
Projekt Lombok – installation des Plugins abgeschlossen

Wenn die Eclipse IDE mit eigenen Parametern für die virtuelle Maschine (-vm parameter) gestartet wird, so muss zusätzlich das JavaArchiv eingebunden werden. In diesem speziellen Fall wird die Zeile „-vmargs -javaagent:lombok.jar“ den Parametern angehängt.

Prüfen ob Projekt Lombok installiert wurde

Auch wenn die Installation des Frameworks „Projekt Lombok“ vermeldet, dass die Installation erfolgreich verlaufen ist, wollen wir prüfen, ob dieses wirklich so ist.
Dazu startet man Eclipse und öffnet über das Menü „Help“ > „About Eclipse IDE“ den Infodialog. Dort sollte es nun eine zusätzliche Zeile geben, wo auf das Plugin hingewiesen wird.

About Eclipse - validieren der Installation
About Eclipse – validieren der Installation

Beispielprojekt

Nehmen wir uns ein kleines einfaches Beispielprojekt „Greetings“.

public class Greeting {

	private String text;
	
	public Greeting(String text) {
		super();
		this.text = text;
	}

	public String getText() { return text; }

	public void setText(String text) { this.text = text; }

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((text == null) ? 0 : text.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Greeting other = (Greeting) obj;
		if (text == null) {
			if (other.text != null)
				return false;
		} else if (!text.equals(other.text))
			return false;
		return true;
	}

	@Override
	public String toString() { return "Greeting [text=" + text + "]"; }
}

In diesem Quellcode sieht man nun welchen Overhead man hat, wenn man nur eine kleine Klasse mit nur einem Text erzeugen möchte. Okay auf die Methoden equals, hashCode und toString kann verzichtet werden, jedoch ist es eine gängige Praxis diese Methoden zu implementieren.

In Eclipse lassen sich diese Methoden einfach über das Kontextmenü erzeugen und somit ist die „arbeit“ nur sehr gering. 

JUnit & Ecplise EclEmma Plugin

In jedem guten Java Projekt gibt es Testfälle für den Quellcode.

Hier nun ein kleiner JUnit Testfall für die Klasse „Greeting“

public class TestGreeting {

   private static Greeting greetingGerman;
   private static Greeting greetingEnglish;

   @BeforeAll
   static void beforeAllTestcases() {
     greetingGerman = new Greeting("Hallo Welt!");
     greetingEnglish = new Greeting("Hello World!");
   }

   @Test
   void testGreetingShouldBeNotNull() {
     assertNotNull(greetingGerman);
     assertNotNull(greetingEnglish);
   }

   @Test
   void testGreetingTextShouldBeNotEmpty() {
     assertFalse(isBlank(greetingGerman.getText()));
     assertFalse(isBlank(greetingEnglish.getText()));
   }

   private boolean isBlank(String text) { return text == null || text.trim().length() == 0; }
}

Führen wir die oben stehenden Testfälle einmal aus und nutzen zusätzlich das Testcoverage Plugin EclEmma zur Analyse.

Testcoverageanalyse mit Eclemma
Testcoverageanalyse mit Eclemma

Das Plugin zeigt nun an, das die Methoden equals, hashCode, toString sowie setText nicht getestet wurde und somit nur eine Testabdeckung von 39,2 % besteht. Um auf 100 % Testabdeckung zu kommen, muss man nun alle rot markierten Stellen mit einem JUnit Test abdecken. Und genau hier entstehen Aufwände, welche man sich sparen kann.

@Test
void testEquals() {
   Greeting g2 = new Greeting("Hallo Welt!");
   assertTrue(g2.equals(greetingGerman));

   assertTrue(g2.equals(g2));
   assertFalse(g2.equals(null));
   assertFalse(g2.equals(new Integer(1337)));
		
   Greeting g3 = new Greeting(null);
   assertFalse(g2.equals(g3));
   assertFalse(g3.equals(g2));
		
   Greeting g4 = new Greeting(null);
   assertTrue(g3.equals(g4));
}
	
@Test
void testSetText(){
   Greeting g4 = new Greeting("test");
   assertTrue(g4.getText().equalsIgnoreCase("test"));
		
   g4.setText("Hallo Welt!");
   assertTrue(g4.getText().equalsIgnoreCase("Hallo Welt!"));
}

@Test
void testHashCode() {
   Greeting g2 = new Greeting("Hallo Welt!");
   assertTrue(g2.hashCode() == greetingGerman.hashCode());
		
   Greeting g3 = new Greeting(null);
   assertFalse(g3.hashCode() == 0);
		
   Greeting g4 = new Greeting("test");
   assertTrue(g4.hashCode() == 3556529);		
}

@Test
void testToString() {
   Greeting g2 = new Greeting("Hallo Welt!");
   assertTrue(g2.toString().equalsIgnoreCase("Greeting [text=Hallo Welt!]"));
}

private boolean isBlank(String text) {
   return text == null || text.trim().length() == 0;
}

Wir haben es nun geschafft eine 100%ige Testabdeckung zu erstellen, okay das war jetzt bei der Klasse nicht die größte Herausforderung.

Projekt Lombok gegen Boilerplatecode

Nun wollen wir die Klasse „Greeting“ mit Annotations versehen, damit die unnötigen Methoden entfallen können. Dazu müssen wir dem Eclipseprojekt das JavaArchiv „lombok.jar“ hinzufügen. 

Im folgenden werde ich zeigen wie die Klasse „Greeting“ mit Hilfe von Lombok und Annotationen verkürzt werden kann.

Konstruktor

Widmen wir uns zuerst dem Konstruktor der Klasse, dieser nimmt den Wert für die Membervariable „Text“ entgegen.
Den Konstruktor können wir mit der Annotation „@RequiredArgsConstructor“ an der Klasse sowie der Annotation „@NonNull“ an der Membervariable „text“ ersetzen. Dieses wird uns auch angezeigt das dieser nun doppelt vorhanden ist.

Compilermeldung das die Methode doppelt vorhanden ist
Compilermeldung das die Methode doppelt vorhanden ist

Hier hat Lombok den Konstruktor bereits erzeugt nur das wir diesen nicht sehen können. Also entfernen wir den Konstrukur, man sieht in dem Tab „Outline“ das der Konstruktor trotzdem erhalten bleibt.

Getter & Setter

Als nächstes geht es dem Getter & Setter an den Kragen, diese werden mit der Annotation „@Getter“ & „@Setter“ überflüssig.
Der Annotation kann zusätzlich noch die Sichtbarkeit mitgegeben werden, d.h. soll der Getter bzw. Setter private, protected oder public sein. 

@NonNull
@Getter(value=AccessLevel.PRIVATE)
@Setter(value=AccessLevel.PROTECTED)
private String text;

Equals, hashCode & toString

Wie eingangs erwähnt, ist es gute Praxis die Methoden equals, hashCode und toString zu überschreiben. Dieses wird mit der Annotation „@EqualsAndHashCode“ & „@ToString“ an der Klasse erledigt. 

@EqualsAndHashCode
@ToString
public class Greeting {}

Das Ergebnis & ein Fazit

Haben wir nun alle Annotationen an der Klasse versehen, so sieht diese nun wie folgt aus:

package de.draegerit.greetings;

import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@RequiredArgsConstructor
@EqualsAndHashCode
@ToString
public class Greeting {

   @NonNull
   @Getter(value=AccessLevel.PRIVATE)
   @Setter(value=AccessLevel.PROTECTED)
   private String text;
}

Und der Tab „Outline“ zeigt nun die Methoden, an welche wir durch die Annotationen ersetzt haben.

Eclipse - Tab "Outline"
Eclipse – Tab „Outline“

In dem Tab „Outline“ sieht man nun:

  • den Konstruktor,
  • die private Methode „getText();“,
  • die protected Methode „setText(String);“,

Des Weiteren sind die Methoden equals, hashCode und toString mit einem kleinen Dreieck nach oben versehen, das bedeutet, dass die Methode überschrieben wurden.

Jetzt könnten wir unseren JUnit Testfall ausdünnen und die Testfälle für equals, hashCode, toString und setText entfernen, aber weit gefehlt, denn diese Methoden existieren ja trotzdem noch. D.h. alles, was wir getan haben ist, die Methoden vor unseren Augen zu verstecken der Compiler sieht diese trotzdem noch. 

Die Testfälle für equals, hashCode und toString werden nun durch Lombok implementiert und dieses weicht von der Eclipse Version ab daher sind nun die Testfälle nicht mehr valide. Die toString Methode kann man sich ja noch auf der Konsole ausgeben aber beim Rest wird es dann doch schon etwas schwieriger. Hier hilft ein decompiler Plugin weiter.

Binäre Datei Greeting.class decompilieren

Für das decompilieren verwende ich das Eclipse Plugin „Enhanced Class Decompiler“ welches bequem über den Marketplace installiert werden kann.

Eclipse Plugin - Enhanced Class Decompiler
Eclipse Plugin – Enhanced Class Decompiler

decompilierter Code

Mit dem Plugin können wir uns nun den Quellcode anzeigen lassen und sieht die Implementierung von equals und hashCode.

package de.draegerit.greetings;

import lombok.NonNull;

public class Greeting {
	@NonNull
	private String text;

	@NonNull
	public String getText() {
		return this.text;
	}

	public void setText(@NonNull String text) {
		if (text == null) {
			throw new NullPointerException("text is marked @NonNull but is null");
		} else {
			this.text = text;
		}
	}

	public Greeting(@NonNull String text) {
		if (text == null) {
			throw new NullPointerException("text is marked @NonNull but is null");
		} else {
			this.text = text;
		}
	}

	public boolean equals(Object o) {
		if (o == this) {
			return true;
		} else if (!(o instanceof Greeting)) {
			return false;
		} else {
			Greeting other = (Greeting) o;
			if (!other.canEqual(this)) {
				return false;
			} else {
				Object this$text = this.getText();
				Object other$text = other.getText();
				if (this$text == null) {
					if (other$text != null) {
						return false;
					}
				} else if (!this$text.equals(other$text)) {
					return false;
				}

				return true;
			}
		}
	}

	protected boolean canEqual(Object other) {
		return other instanceof Greeting;
	}

	public int hashCode() {
		int PRIME = true;
		int result = 1;
		Object $text = this.getText();
		int result = result * 59 + ($text == null ? 43 : $text.hashCode());
		return result;
	}

	public String toString() {
		return "Greeting(text=" + this.getText() + ")";
	}
}

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert