Java und die Sache mit dem Zugriff auf lokale Variablen umgebender Methoden

von Hubert Schmid vom 2012-06-17

In einer der letzten Podcast-Folgen von Java Posse fiel eine aus meiner Sicht merkwürdige Aussage. Ich bin mir nicht mehr sicher, ob es um die bereits seit Java 1.1 existierenden anonymen Klassen oder um die in Java 8 neu hinzukommenden anonymen Methoden ging. Aber auf jeden Fall ging es um die Frage, wie man aus dem inneren Block am Besten auf die lokalen Variablen der umgebenden Methode zugreift. Ich werde das am folgenden Beispiel mit einer anonymen Methode genauer betrachten:

BigInteger sum(List<BigInteger> values) { BigInteger total = BigInteger.ZERO; values.forEach(value -> { total = total.add(value); }); return total; }

Ein solcher Zugriff von einem inneren Block auf die lokalen Variablen der umgebenden Methode ist in Java 7 nicht zulässig, und er wird aller Voraussicht nach auch in Java 8 nicht zulässig sein. Eine einfache und häufig gesehene Lösung besteht in der Verwendung eines Arrays mit genau einem Element.

BigInteger sum(List<BigInteger> values) { BigInteger[] total = { BigInteger.ZERO }; values.forEach(value -> { total[0] = total[0].add(value); }); return total[0]; }

Die Variable total muss in Java 8 übrigens wie dargestellt nicht mehr explizit mit final deklariert werden, wenn die einzige Zuweisung bei der Initialisierung erfolgt und die Variable somit implizit final ist.

Es ist verständlich, dass viele Entwickler diesen Code nicht sonderlich elegant finden. So geht es auch den Machern des Java-Posse-Podcasts. In einer ihrer letzten Folgen haben sie daher empfohlen, statt dem Array eine Klasse aus der Standardbibliothek zu nehmen, die genau ein Objekt kapselt – und zwar konkret java.util.concurrent.atomic.AtomicReference, wobei die Indexzugriffe durch get und set ersetzt werden, wie im folgenden Code-Ausschnitt zu sehen:

BigInteger sum(List<BigInteger> values) { AtomicReference<BigInteger> total = new AtomicReference<>(BigInteger.ZERO); values.forEach(value -> { total.set(total.get().add(value)); }); return total.get(); }

Meiner Meinung nach geht das gar nicht. Über die Lesbarkeit des inneren Blocks kann man sicherlich diskutieren, und auch der Performance-Overhead der Serialisierung ist in den meisten Fällen unbedeutend. Aber: Eine Klasse zeichnet sich nicht nur durch ihre technische Funktionalität sondern auch durch ihre fachliche Bedeutung aus. Durch die Verwendung der Klasse AtomicReference wird ausgedrückt, dass entweder Data-Races auszuschließen sind, oder dass Serialisierungspunkte zwischen mehreren Threads benötigt werden. Doch keine dieser beiden Eigenschaften trifft auf dieses Beispiel zu.

Da ist mir das Array mit einem Element allemal lieber. Alternativ kann man sich auch selbst eine Klasse schreiben, die genau ein Element kapselt, falls diese Situation entsprechend häufig auftritt. Die Klasse selbst ist denkbar einfach:

public final class Holder<T> { private T object; public Holder(); public Holder(T object); public T get(); public void set(T object); }

Übrigens: Der Zugriff auf umgebende Variablen ist kein grundsätzliches Problem. Die meisten anderen Programmiersprachen machen es dem Entwickler an dieser Stelle wesentlich einfacher. So funktioniert beispielsweise das erste Code-Fragment seit Jahren in C# – nur dass die Methode forEach dort ForEach heißt.