Braucht Java literale Ausdrücke für List, Set und Map?
Mir ist bereits von mehreren Seiten zu Ohren gekommen, dass mit Java 8 die Syntax der Sprache erweitert werden soll, um Lists, Sets und Maps direkt initialisieren zu können. Dafür wird häufig der Begriff Literale
zur Instantiierung dieser Datenstrukturen benutzt. Das finde ich ein wenig unglücklich. Denn mit Literalen ist in Programmiersprachen eigentlich etwas anderes gemeint.
Ich habe zu diesem neuen Feature keine genaueren Aussagen gefunden. Und ich bezweifle auch, dass an diesem Gerücht überhaupt etwas dran ist. Denn auch mit den bereits existierenden Sprachmitteln ist der syntaktische Overhead der Initialisierung aus meiner Sicht vernachlässigbar. Und im Folgenden werde ich diese Möglichkeiten für die typischen Vertreter kurz aufzeigen.
Array
Die Initialisierung einer Array-Variablen unterstützt Java bereits seit der ersten Version.
String[] array = { "foo", "bar", "baz", };
Leider ist diese Form der Initialisierung spezifisch für die Deklaration einer Variablen. Ein Methoden-Parameter lässt sich auf diese Weise hingegen nicht initialisieren. Dafür gibt es eine weitere Syntax, die allerdings ein wenig gesprächiger ist.
abstract void process(String[] values);
// ERROR
process({ "foo", "bar", "baz", });
// OK
process(new String[] { "foo", "bar", "baz", });
Helfen kann an dieser Stelle eine statische Hilfsfunktion, die über ein static import
eingebunden wird. Damit sind zumindest drei syntaktische Symbole weniger notwendig.
import static org.example.ArrayBuilder.arrayOf;
process(arrayOf("foo", "bar", "baz"));
Dabei nutze ich das mit Java 5 eingeführte Feature der Static-Imports, mit dem Klassen-lose Funktionen nachgebildet werden können. Außerdem verwendet die Methode die ebenfalls mit Java 5 eingeführten Varargs, so dass die Funktion nur einmal für eine beliebige Anzahl Parameter implementiert werden muss. Die Implementierung selbst ist relativ trivial und ich werde sie weiter unten am Beispiel der List
zeigen.
List
Im Gegensatz zu Arrays bietet Java für Listen überhaupt keine Möglichkeit der direkten Initialisierung mit Sprachmitteln. Daher findet man in diesem Fall häufig eine Liste von Einzeloperationen.
abstract void process(List<String> values);
List<String> list = new ArrayList<String>();
list.add("foo");
list.add("bar");
list.add("baz");
process(list);
Eine einfache Hilfsfunktion schafft dabei Abhilfe.
import static org.example.ListBuilder.listOf;
process(listOf("foo", "bar", "baz"));
Wie bereits oben beschrieben ist die Implementierung sehr einfach. Sie verwendet die Varargs aus Java 5 und erzeugt damit eine neue List-Instanz. Und sofern mindestens ein Argument angegeben wird, funktioniert auch die automatische Inferenz des Typparameters.
public abstract class ListBuilder {
public static <T> List<T> listOf(T... values) {
List<T> result = new ArrayList<T>(values.length);
for (T value : values) {
result.add(value);
}
return result;
}
}
Man kann sich viele Varianten dieser Funktion vorstellen. Beispielsweise könnte es für bestimmte Anwendungsfälle besser sein, wenn die zurückgegebene Liste unveränderlich ist. Und wenn Performance für diese Funktion besonders wichtig ist, dann kann man auch eine Implementierung zurückgegeben, die direkt auf dem Array-Parameter basiert. Eine solche Funktion existiert sogar bereits in der Standardbibliothek in Form von java.util.Arrays.asList
.
Set
Die Situation zu Set sieht praktisch identisch zur List aus. Die Initialisierung mit Einzelanweisungen lässt sich genau auf die gleiche Weise ersetzen.
abstract void process(Set<String> values);
Set<String> set = new HashSet<String>();
set.add("foo");
set.add("bar");
set.add("baz");
process(set);
import static org.example.SetBuilder.setOf;
process(setOf("foo", "bar", "baz"));
Und auch in diesem Fall spare ich es mir, die Implementierung der Hilfsklasse zu zeigen. Interessant finde ich allerdings, dass sich im Gegensatz zur List keine entsprechende Funktion in der Standardbibliothek findet.
Map
Interessanter wird die Situation für eine Map. Der Ausgangscode mit Einzeloperationen sieht wiederum identisch aus wie in den vorherigen beiden Fällen.
abstract void process(Map<Integer, String> values);
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "foo");
map.put(2, "bar");
map.put(3, "baz");
process(map);
Das obige Pattern lässt sich auch hier wieder anwenden. Im Gegensatz zu List und Set müssen in diesem Fall zwar zusammengesetzte Objekte übergeben werden, aber auch das lässt sich mit einer weiteren Hilfsfunktion einfach bewerkstelligen.
import static org.example.MapBuilder.mapOf;
import static org.example.MapBuilder.entry;
process(mapOf(entry(1, "foo"), entry(2, "bar"), entry(3, "baz")));
Alternativ findet man auch häufig Lösungen der folgenden Form. Sie basieren auf einer Abwandlung des Builder-Patterns, und können den syntaktischen Overhead fast vollständig reduzieren. Intuitiv sieht aber anders aus.
import static org.example.MapBuilder.mapOf;
process(mapOf(1, "foo")._(2, "bar")._(3, "baz")._());
Die Implementierung eines solchen Builders ist erstaunlich einfach, wie man an folgender Realisierung erkennen kann.
public abstract class MapBuilder<K, V> {
// creates a map builder and adds the first element
public static <K, V> MapBuilder<K, V> mapOf(K key, V value) {
return new MapBuilder<K, V>() {
private final Map<K, V> map = new HashMap<K, V>();
@Override
public MapBuilder<K, V> _(K key, V value) {
map.put(key, value);
return this;
}
@Override
public Map<K, V> _() {
return new HashMap<K, V>(map);
}
}._(key, value);
}
// adds an element to the map
public abstract MapBuilder<K, V> _(K key, V value);
// returns map containing the elements added until now
public abstract Map<K, V> _();
}
Fazit
Für die Initialisierung einfacher Datenstrukturen wie Array, List und Set sehe ich in Java keinen Bedarf für ein zusätzliches Sprachkonstrukt. Denn diese Aufgabe lässt sich auch mit den bereits vorhandenen Mitteln und praktisch ohne syntaktischen Overhead realisieren. Darüber hinaus bieten sie mehr Flexibilität, weil man zwischen verschiedenen Ausprägungen der Implementierungen (Datenstruktur, Veränderbarkeit, Synchronisierung, ...) wählen kann. Ich wünsche mir lediglich, dass diese Funktionalität bereits durch die Standardbibliothek bereitgestellt wird.
Ein wenig anders sieht die Situation bei der Initialisierung einer Map aus. An dieser Stelle könnte ein zusätzliches Sprachkonstrukt tatsächlich zu einer gewissen Vereinfachung führen. Aber aus meiner Sicht ist sie nicht so gravierend, um eine erweiterte Syntax zu rechtfertigen.