Resource Bundles und Properties in Java

Was ist von folgendem Codestück zu halten?

public void init() {
  JFrame frame = new JFrame();
  frame.setTitle("Toller Fenstertitel");
}

Der fortgeschrittene Java-Entwickler würde das Snippet vermutlich zunächst einmal wie folgt refactoren:

private static final String WINDOW_TITLE = "Toller Fenstertitel"
public void init() {
  JFrame frame = new JFrame();
  frame.setTitle(WINDOW_TITLE);
}

Diese Änderung hilft schon mal, falls der String an mehreren Stellen im Code verwendet wird. Nun muss er bei einer Änderung nämlich nur noch an einer Stelle geändert werden.

Was aber, wenn das Programm nun internationalisiert werden soll?

In so einem Fall kommt man kaum darum herum, die fixen Strings (bei uns etwa den tollen Fenstertitel) in eine externe Datei auszulagern. Da es aufwändig wäre, jedes Mal ein eigenes Dateiformat dafür zu entwerfen und der Fall recht häufig auftritt, gibt es im JDK die Klasse ResourceBundle, welche sich genau darum kümmert.

Diese ResourceBundles funktionieren über ein einfaches Key-Value-Mapping. Die Properties-Datei, welche die Zuordnungen enthält, wird dabei im Textformat gespeichert.

Resource Bundles

Zunächst nehmen wir für unser Projekt einmal diese Ordnerstruktur an:
src/
├── de/
│ . └── oio/
│ . . . └── properties/
│ . . . . . └── Main.java
└── bundle.properties

Die Main-Klasse sieht hierbei wie folgt aus:

public class Main {
	public static void main(String[] args) {
		ResourceBundle rb = ResourceBundle.getBundle("bundle");
		String windowTitle = rb.getString("window-title");
		JFrame jframe = new JFrame();
		jframe.setTitle(windowTitle);
		jframe.setSize(300, 100);
		jframe.setVisible(true);
	}
}

Die bundle.properties hat den folgenden Inhalt:

window-title=Toller Fenstertitel

Wie in Zeile 3 zu sehen ist, wird mit ResourceBundle.getBundle zunächst die bundle.properties-Datei eingelesen. Aus dieser wird anschließend der String ausgelesen, welcher als Schlüssel window-title hinterlegt hat. Das Resultat wird in der Variable windowTitle gespeichert. Anschließend wird ein JFrame erzeugt und der Titel auf den aus der Datei eingelesenen String gesetzt.

Das Resultat ist in diesem Screenshot zu sehen:

JFrame mit tollem Fenstertitel

JFrame mit tollem Fenstertitel

Der String für den Fenstertitel aus der Properties-Datei wurde also korrekt ausgelesen und in den JFrame eingesetzt.

Resource Bundles aus externer Datei

Natürlich lässt sich die Properties-Datei auch aus einer Datei außerhalb des Projektes einladen:

URL fileUrl = new File("C:/Temp/").toURI().toURL();
ClassLoader loader = new URLClassLoader(new URL[] { fileUrl });
ResourceBundle rb = ResourceBundle.getBundle(
    "weitweg", Locale.getDefault(), loader);	
...

Hier wird eine Datei namens weitweg.properties aus dem Ordner C:\Temp\ geladen. Der Inhalt von weitweg.properties lautet dabei:

window-title=Supertoller Fenstertitel

Das Resultat:

JFrame mit supertollem Fenstertitel

JFrame mit supertollem Fenstertitel


Im Kontrast zum ersten Beispiel muss für das Einladen externer Properties-Dateien ein URLClassLoader erstellt werden, der die URL zu dem Ordner enthält, in dem sich die Properties-Datei befindet.

Internationalisierung

Um unseren Fenstertitel zu internationalisieren, legen wir nun eine weitere Datei bundle_en_US.properties neben der bereits bestehenden bundle.properties in unserem Projekt an. Dieser Vorgang lässt sich auch für den Fall, dass die Datei extern eingeladen wird, replizieren.

In der neuen Datei können wir nun unseren Fenstertitel auf Englisch übersetzen:

window-title=Super Cool Window Title

Um diese Übersetzung auszuwählen, verwenden wir folgenden Code:

Locale us = new Locale("en", "US");
ResourceBundle rb = ResourceBundle.getBundle("bundle", us);
String windowTitle = rb.getString("window-title");
...		

Zusätzlich zu dem Namen der Properties-Datei wird ein Objekt vom Typ Locale an die getBundle-Methode übergeben. Das Locale-Objekt erhält als ersten Parameter die Sprache und als zweiten das Land. Das Kürzel für Englisch ist en und das Kürzel für die Vereinigten Staaten US. Daher heißt die Properties-Datei oben auch bundle_en_US.

Und siehe da:

JFrame mit tollem Titel - Jetzt auf Englisch

JFrame mit tollem Fenstertitel – Jetzt auf Englisch

Neuerung in Java 9

In Java 9 wurde nun das Dateiformat der Properties-Dateien von ISO-8859-1 auf UTF-8 umgestellt. Vorher konnten die Werte allerdings auch schon Unicode-Zeichen enthalten, nur mussten diese eben mit \u escaped werden. Dieses Escaping fällt nun weg. Außerdem gibt es einen Kompatibilitätsmodus für den alten ISO-8859-1 Standard. Dieser kann über die System Property java.util.PropertyResourceBundle.encoding aktiviert werden.

Während alle ASCII-Zeichen in ISO-8859-1 und UTF-8 identisch dargestellt wurden, kann es bei den erweiterten ISO-8859-1-Zeichen zu Lesefehlern kommen. Dies ist allerdings innerhalb der getBundle-Methode bereits abgefangen. Sollte ein in UTF-8 ungültiges Zeichen in einer Properties-Datei gefunden werden, wird die Datei erneut als ISO-8859-1 eingelesen. Die Abwärtskompatibilität mit alten ISO-8859-1-Properties-Dateien ist damit gewährleistet.

Der Vollständigkeit halber hier noch ein Beispiel mit einigen Unicode-Emojis im Fenstertitel, die aus der bundle.properties-Datei eingelesen wurden:

JFrame mit Emojis

JFrame mit Emojis

Resource Bundle vs. Properties

Neben der java.util.ResourceBundle-Klasse bietet Java noch die java.util.Properties-Klasse. Der Unterschied zwischen Resource Bundle und Properties ist, dass über das Resource Bundle nur Daten gelesen werden können, während mittels Properties auch Daten geschrieben werden können.

Ein Beispiel hierfür sind Konfigurationseinstellungen, bei denen der Benutzer später einfach Änderungen an der Konfiguration ändern können soll, ohne selbst den Quellcode verändern zu müssen.

Hier ein Stück Beispielcode für die Verwendung eines Properties-Objekts:

Properties properties = new Properties();
File file = new File("C:/Temp/settings.properties");
properties.load(new FileInputStream(file));

System.out.printf("Some Setting: %s\n", 
    properties.getProperty("some-setting"));

String rnd = UUID.randomUUID().toString();
System.out.println("Setting some setting to " + rnd);
properties.setProperty("some-setting", rnd);
		
properties.store(new FileOutputStream(file), null);

Zunächst wird das Properties-Objekt aus der Datei C:/Temp/settings.properties geladen. Anschließend wird der Wert zum Schlüssel some-setting ausgegeben und mit einem neuen Wert überschrieben. Schließlich wird die Properties-Datei gespeichert.

Wenn man das Programm mehrfach ausführt, erhält man eine Konsolenausgabe wie etwa

Some Setting: 2a1be6fa-aade-4241-8aa2-8d084e138d98
Setting some setting to 5a70bce2-dd41-4968-96a0-4b9dbd72734c

Some Setting: 5a70bce2-dd41-4968-96a0-4b9dbd72734c
Setting some setting to d42912d9-6467-464d-a506-b2cff86432b8

Some Setting: d42912d9-6467-464d-a506-b2cff86432b8
Setting some setting to db2f23f2-6e13-4ec4-afe0-d8153144568d

Das Properties-Objekt eignet sich sehr gut dafür, beispielsweise Benutzereinstellungen abzubilden, die sich zur Laufzeit ändern können und am Ende persistiert werden sollen.

Fazit

Resource Bundles bieten in Java eine hervorragende Möglichkeit, Zeichenketten oder internationalisierte Texte in externe Dateien auszulagern. Allerdings besteht hier nicht die Möglichkeit, Daten zur Laufzeit zu ändern und das Ergebnis wieder zurück in die Properties-Datei zu persistieren.

Wenn doch einmal Daten verändert und gespeichert werden sollen, kann stattdessen ein Properties-Objekt verwendet werden.

Short URL for this post: https://wp.me/p4nxik-33e
This entry was posted in Java Basics and tagged , , , , , . Bookmark the permalink.

Leave a Reply