Maps unter Java mit computeIfAbsent nutzen

Wenn eine Map unter Java etwas komplexer wird, wie z.B. folgende Map:

Map<String, List<String>> testMap = new HashMap<>();

wird das befüllen derselbigen interessant. In diesem Beispiel soll ein Key und ein neuer Value für die Liste vom Typ String hinzugefügt werden. Damit dies funktioniert muss überprüft werden ob der Key bereits existiert und wenn dies nicht der Fall ist, ein neuer Key angelegt werden. Im kompletten Beispiel sieht das Ganze so aus:

String key = "ABC";
String valueForList = "DEF";

Map<String, List<String>> testMap = new HashMap<>();

if(!testMap.containsKey(key)) {
    testMap.put(key, new ArrayList<>());
} 

testMap.get(key).add(valueForList);

Einfacher und unkomplizierter ist es die Methode computeIfAbsent zu nutzen, welche seit Java 8 in der Definition des Map-Interfaces enthalten ist. Mit der Nutzung der Methode verkürzt sich das Beispiel wie folgt:

String key = "ABC";
String valueForList = "DEF";

Map<String, List<String>> testMap = new HashMap<>();
testMap.computeIfAbsent(key, s -> new ArrayList<>()).add(valueForList);

Damit wird automatisch eine neue ArrayList angelegt, wenn der entsprechende Schlüssel noch nicht hinterlegt ist. Anschließend kann der entsprechende Wert für die Liste hinzugefügt werden.

Java Exceptions in Strings verpacken

Wenn in einem Java-Programm etwas schiefläuft so, wird in den meisten Fällen eine Exception erzeugt und diese nach oben im Stack durchgereicht, bis sie behandelt wird. Manchmal ist es notwendig die Exception bzw. Teile davon in einen String umzuwandeln. Natürlich kann die Exception nun mühsam von Hand auseinander genommen und in einen String konvertiert werden. Sinnvoller ist es die Hilfsmethoden aus der Utility-Bibliothek Guava zu nutzen:

String stacktrace = Throwables.getStackTraceAsString(e);

Bei der Methode getStackTraceAsString wird der Stacktrace der betreffenden Exception in einen String umgewandelt. Neben dieser Methode existieren weitere Methoden rund um das Exception-Handling in der Klasse Throwables. Einfacher wird das Ganze, wenn ein Logger genutzt wird und in diesem die Exception auftauchen soll:

Logger logger = LoggerFactory.getLogger(new Exception().fillInStackTrace().getStackTrace()[0].getClassName());
logger.error("Exception: ", e);

In diesem Fall bieten die meisten Logger die Möglichkeit an, das Throwable direkt als Parameter zu übergeben. Die weitere Verarbeitung der Exception wird anschließend vom Logger erledigt.

Probleme mit der Methode appendReplacement unter Java

Mit regulären Expressionen kann in einem String nach bestimmten Zeichenketten gesucht werden. Daneben ist es natürlich auch möglich diese Zeichenketten zu modifizieren und sie anschließend zu ersetzen. Unter Java würde das Ganze wie folgt aussehen:

private static String convertBlockquotes(String html) {

	Pattern patternBlockquote = Pattern.compile("<blockquote>(.+?)</blockquote>", Pattern.DOTALL);

	// Find all blockquote tags
	final Matcher matcher = patternBlockquote.matcher(html);
	StringBuffer stringBuffer = new StringBuffer(html.length());

	// Translate each tag
	while (matcher.find()) {

		// Get complete tag
		String group = matcher.group();

		// Remove tags
		group = group.replace("<blockquote>", "");
		group = group.replace("</blockquote>", "");

		// Replace old tag with markdown equivalent
		matcher.appendReplacement(stringBuffer, group);
	}

	matcher.appendTail(stringBuffer);
	return stringBuffer.toString();
}

Wenn nun in dem gefundenen String sich z.B. ein $-Zeichen (das Zeichen für Ersetzung im Kontext der Matcher-Klasse) befindet, wird spätestens bei der Zeile:

// Replace old tag with markdown equivalent
matcher.appendReplacement(stringBuffer, group);

ein Problem auftreten, welches sich in einer IllegalArgumentException äußert:

Exception in thread „main“ java.lang.IllegalArgumentException: Illegal group reference

Hintergrund ist das bestimmte Zeichen wie $ oder der Backslash maskiert werden müssen. Dies konnte manuell erledigt werden, bevor die Methode appendReplacement aufgerufen wird:

group = group.replace("$", "\\$");

Einfach ist es die integrierte Methode quoteReplacement zu nutzen:

group = Matcher.quoteReplacement(group);

Diese maskiert die entsprechenden Zeichen. Ein etwas andere Lösung ist es appendReplacement ohne neue Zeichenkette aufzurufen und diese anschließend an den StringBuffer anzufügen:

matcher.appendReplacement(stringBuffer, "");
stringBuffer.append(group);

Bei dieser Variante müssen keine Zeichen maskiert werden. Die vom JDK angedachte Variante ist allerdings die der Nutzung von quoteReplacement, so das diese bevorzugt werden sollte.

Thymeleaf ohne Servlet in Standalone-Anwendungen nutzen

Thymeleaf ist eine Template Engine, welche für die Nutzung mittels Java (oder anderer JVM-Sprachen) gedacht ist. Über diese können entsprechende Templates geschrieben werden, anschließend gefüllt und genutzt werden. Thymeleaf verfügt über eine Dokumentation, allerdings fehlt mir dort ein minimales Beispiel für Anwendungen, welche sich nicht im Web (sogenannte non-web environments) abspielen.

thymeleaf.org

Für ein minimales Beispiel wird natürlich ein Template benötigt. Dieses wird hier als HTML definiert:

<!DOCTYPE html>
<html>
<head>
    <title th:text="${title}"></title>
</head>
<body>
</body>
</html>

Interessant ist hierbei das Feld title, welches wir mit einem Titel füllen wollen. Im Java-Quellcode sieht die Implementation wie folgt aus:

ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("templates/");
resolver.setTemplateMode("HTML5");
resolver.setSuffix(".html");

TemplateEngine templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(resolver);

Context context = new Context(Locale.GERMAN);
context.setVariable("title", "Ein minimales Beispiel");

final String content = templateEngine.process("header.html", context);

Im ersten Schritt wird mit dem ClassLoaderTemplateResolver ein Resolver definiert, welcher schlussendlich dafür sorgt die Templates aus den Ressourcen (im resources-Ordner) bezogen werden. Daneben wird der Template-Modus eingestellt. Neben dem Modus HTML5 unterstützt Thymeleaf wie z.B. TEXT oder JAVASCRIPT. Danach wird die Template Engine erstellt und ihr der Resolver zugewiesen. Die Variablen, welche in diesem Fall im Template genutzt werden, werden über den Context definiert und gesetzt. Anschließend wird das Ganze an die process-Methode der Template Engine übergeben. Als Ergebnis erhalten wir im String content folgenden Inhalt:

<!DOCTYPE html>
<html>
<head>
    <title">Ein minimales Beispiel</title>
</head>
<body>
</body>
</html>

Damit wurde Thymeleaf in einer minimalen Variante angewendet. Die offizielle Seite der Template Engine ist unter thymeleaf.org zu finden. Lizenziert ist Thymeleaf unter der Apache License in der Version 2.0 und damit freie Software.

Java und die Speicherung von Unicode

Der Datentyp char ist unter Java 16 Bit, also zwei Byte groß. Laut dem Buch Java ist auch eine Insel, werden die Daten in 16-Bit-Unicode gespeichert. Doch was genau bedeutet das und ist das eine korrekte Aussage? Die grundlegende Aussage, dass ein char unter Java ein 16-Bit-Unicode Zeichen ist, stimmt. Wobei mittlerweile wird das ganze so betrachtet, dass ein char unter Java eine Unicode code unit repräsentiert. Echte Unicode-Zeichen benötigen mittlerweile bis zu 21 Bit und werden üblicherweise als 32 Bit gespeichert, was einer UCS-4 Kodierung entspricht.

Mit Unicode können nicht nur lateinische Buchstaben abgebildet werden

Mit Unicode können nicht nur lateinische Buchstaben abgebildet werden

Das bedeutet, dass in einem char nur Unicode-Zeichen gespeichert werden können, die in 16 Bit abgebildet werden können. Bei einem Array vom Typ char, einem StringBuffer und einem String sieht die Sache anders aus. Hier sind die Unicode-Zeichen in UTF-16 kodiert, damit können alle Zeichen des Unicode-Satzes kodiert werden. Das führt aber auch zu Problemen, so gibt die Methode length() einer String-Instanz die Anzahl der Unicode code units zurück, was gleichbedeutend ist mit der Anzahl Byte multipliziert mit zwei ergibt. Es bedeutet das die Methode nicht zwingend die Anzahl der Buchstaben zurück gibt, da manche Buchstaben mit zwei Unicode code units kodiert werden.