Casts mit zusätzlichen Bounds in Java 8

Immer wieder beschert mir Java wundervolle WTF-Momente, so auch heute. Eine Syntaxerweiterung in Java 8, die komplett an mir vorbeigegangen ist, sind Casts mit zusätzlichen Bounds:

LinkedList<String> list = new LinkedList<>();
List<String> list1 = (List & Queue) list; //OK
List<String> list2 = (List & RandomAccess) list; //ClassCastException

Die JLS schreibt dazu recht lakonisch :

If the cast operator contains a list of types – that is, a ReferenceType followed by one or more AdditionalBound terms – then all of the following must be true, or a compile-time error occurs.

Stellt sich die Frage ist, wozu das Ganze gut sein soll. Der einzige sinnvolle Anwendungsfall, den ich gefunden habe, ist die Spezifizierung zusätzlicher Interfaces bei Lambdas:

Runnable r = (Runnable & Serializable) () -> System.out.println("Serializable!");

Lambdas haben ja eigentlich keinen Typ, sie sind ein wenig wie Schrödingers Katze: Erst wenn man etwas mit ihnen anstellt – etwa die Zuweisung zu einer Variablen – entscheidet sich, was ihr Typ ist. Durch den vorgelagerten Cast wird diese Typbestimmung vorgezogen, so dass das Objekt r nicht nur Runnable, sondern auch Serializable ist, und später auch problemlos serialisiert werden kann.

Wie findet ihr dieses etwas obskure Feature? Seht ihr noch andere sinnvolle Anwendungsmöglichkeiten?

Werbeanzeigen

Überraschung programmiert mit asInstanceOf

Wer ein wenig Scala geschrieben hat, merkt schnell, dass man – insbesondere dank Pattern Matching – viel weniger casten muss als in Java. Die Methode zur Typkonvertierung hat absichtlich einen abschreckenden Namen bekommen: asInstanceOf. Und das zu Recht, denn Casts in Scala sind nicht sicherer als in Java.

Allerdings könnte man meinen, man sei sicher, nachdem der Cast „gelungen“ ist. Natürlich ist das schon in Java nicht der Fall, wenn man z.B. ein Objekt zu List<String> castet, das in Wahrheit eine List<Integer> ist. Der Cast gelingt, aber beim Zugriff auf ein Listen-Element gibt es einen lauten Knall. Aber wer Type Erasure in Java verstanden hat, sollte sich über dieses Verhalten eigentlich nicht wundern, denn wenn zur Laufzeit kein Parametertyp mehr existiert, kann man ihn auch nicht testen.

Neulich bin ich auf Stackoverflow auf ein anderes Problem gestoßen, dass es nur in Scala gibt: Man kann an eine Klasse mit asInstanceOf ungestraft andere Klassen oder Traits „anhexen“, ohne dass das beanstandet wird – natürlich nur, solange man nicht auf irgendwelche Member zugreifen möchte. Dabei kann die verhexte Klasse sogar final sein, wie z.B. Int:

trait Funny{ 
   def test { println("funny!") } 
}

def doubleIt(z: Int with Funny):Int = 2*z

val x = 5.asInstanceOf[Int with Funny] 

print(doubleIt(x)) // 10

print(x.test) // ClassCastException

Wir definieren hier ein Trait namens Funny, und eine Funktion namens doubleIt, die ein Int with Funny erwartet. Natürlich können wir diesen Typ nie wirklich konstruieren, denn Int ist final. Der Versuch, doubleIt mit einem normalen Int aufzurufen, würde übrigens schon beim Compilieren scheitern.

Als nächstes kreieren wir mit asInstanceOf ein Frankenstein-Monster namens x, das den gewünschten Typ besitzt, und ES IST LEBENDIG, ES LEBT!!!… Ähm, ich meine, man kann damit problemlos doubleIt aufrufen, und erhält das gewünschte Ergebnis.

Die letzte Zeile zeigt die dunkle Seite unseres Monsters: Es ist in Wirklichkeit gar nicht Funny, und wenn wir eine Methode des Traits aufzurufen versuchen, macht es puff und explodiert in einer ClassCastException.

Das gezeigte Verhalten ist natürlich ziemlich gefährlich, und man fragt sich, warum asInstanceOf-Aufrufe nicht testen, was da eigentlich für Typen von ihnen verlangt werden. Prinzipiell wäre das mit isInstanceOf möglich, aber vermutlich wären derartige Laufzeittests ziemliche Performance-Killer.

Was lernen wir daraus? Zum einen, dass man mit asInstanceOf den Typchecker wirklich „abschaltet“ und allen möglichen Unsinn anstellen kann – etwa Klassen vorzugaukeln, die es prinzipiell nicht geben kann. Zum anderen, dass man um asInstanceOf einen großen Bogen machen sollte, wenn man es nicht unbedingt braucht und nicht ganz genau weiß was man tut.