Formungen in Java
Eine Formung erlaubt es, den Typ eines Objektausdrucks unabhängig vom Typ seines Objektes einzustellen.
Dabei sind für den Typ eines Ausdrucks aber nur solche Typen erlaubt, die Obertypen des Typs des Objekts sind.
Syntax
Der Formungsoperator ist ein unärer Präfix-Operator, der aus einem eingeklammerten Typnamen besteht.
Formungen elementarer Typen (wie »( int )«) wurden schon vor dem Aufbaukurs behandelt.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println( ( int )2.3 ); }}- Protokoll
2
Der Formungsoperator für Referenztypen
Im folgenden steht »T « für einen Referenztyp und »A« für einen Referenzausdruck.
Formungsregel Der Ausdruck »( T )A« hat den Typ »T «.
Main.java
public final class Main
{public static void show( final java.lang.String s )
{ java.lang.System.out.println( "class java.lang.String" ); }public static void show( final java.lang.CharSequence cs )
{ java.lang.System.out.println( "class java.lang.CharSequence" ); }public static void show( final java.lang.Object o )
{ java.lang.System.out.println( "class java.lang.Object" ); }public static void main( final java.lang.String[] args )
{final java.lang.CharSequence cs = "abc";
show( ( java.lang.Object )cs );
show( ( java.lang.CharSequence )cs );
show( cs );
show( ( java.lang.String )cs ); }}
- Protokoll
class java.lang.Object
class java.lang.CharSequence
class java.lang.CharSequence
class java.lang.String
Der Typ des referenzierten Objekts ändert sich durch die Formung nicht.
Main.java
public final class Main
{public static void show( final java.lang.Object o )
{ java.lang.System.out.println( o.getClass() ); }public static void main( final java.lang.String[] args )
{final java.lang.CharSequence cs = "abc";
show( ( java.lang.Object )cs );
show( ( java.lang.CharSequence )cs );
show( cs );
show( ( java.lang.String )cs ); }}
- Protokoll
class java.lang.String
class java.lang.String
class java.lang.String
class java.lang.String
Insofern „wandelt“ der Formungsoperator bei Referenztypen nichts, sondern legt nur den Typ des Ausdrucks fest.
Verallgemeinerungen (“upcasts ”)
Im folgenden steht »T « für einen Referenztyp und »A« für einen Referenzausdruck.
Formungsanforderung 0 Die Verwendung von »( T )A« ist erlaubt, wenn »T « ein Obertyp des Typs von »A « ist (“upcast ”).
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.CharSequence cs = "abc";
java.lang.System.out.println( ( java.lang.Object )cs ); }}- Protokoll
abc
Der Begriff „Verallgemeinerung “ für einen upcast ist nicht allgemein üblich!
Spezialisierungen (“downcasts ”)
Im folgenden steht »T « für einen Referenztyp und »A« für einen Referenzausdruck.
Formungsanforderung 1 Die Verwendung von »( T )A« ist ebenfalls erlaubt, wenn »T « ein Untertyp des Typs von »A « ist und ein Obertyp des Typs des Objekts »A« ist (“downcast ”).
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.CharSequence cs = "abc";
java.lang.System.out.println( ( java.lang.String )cs ); }}- Protokoll
abc
Der Begriff „Spezialisierung “ für einen downcast ist nicht allgemein üblich!
Verschiebungen (“sidecasts ”)
Im folgenden steht »T « für einen Referenztyp und »A« für einen Referenzausdruck.
Formungsanforderung 1 Die Verwendung von »( T )A« ist ebenfalls erlaubt, wenn »T « weder Untertyp noch Obertyp des Typs von »A « ist (“sidecast ”). Es muß aber weiterhin ein Obertyp des Typs des Objekts »A« sein.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.io.Closeable c = java.lang.System.out;
java.lang.System.out.println( ( java.lang.Appendable )c ); }}- Protokoll
java.io.PrintStream@77468bd9
Der Begriff „Verschiebung “ für einen sidecast ist nicht allgemein üblich!
Prüfungen
ℛ Objektregel Der Typ eines Objektes muß ein Untertyp des Typs eines Ausdrucks (also auch eines Namens) für das Objekt sein.
Bei einer Verallgemeinerung ist die Objektregel automatisch erfüllt (warum?). Wenn der Compiler erkennt, daß die Formungsanforderung 0 erfüllt wurde, legt er einfach den Typ des Ausdrucks neu fest. Beim Ablauf des Programms geschieht an dieser Stelle nichts (es geht keine Zeit verloren, während das Programm läuft).
Bei einer Spezialisierung oder Verschiebung ist es nicht sicher, daß die Objektregel weiterhin erfüllt ist (warum?). Wenn der Compiler erkennt, daß die Formungsanforderung 0 nicht erfüllt ist, aber in einen Untertyp umgeformt wird, dann erzeugt er ein Programm, das beim Ablauf jedesmal prüft, ob die Objektregel erfüllt ist (kann Zeit kosten).
Verletzungen
Im folgenden Beispiel einer versuchten Spezialisierung ist die Objektregel verletzt, was erst zur Laufzeit erkannt wird.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.Object o = java.lang.System.out;
java.lang.System.out.println( ( java.lang.String )o ); }}- Protokoll
Exception in thread "main" java.lang.ClassCastException: java.base/java.io.PrintStream cannot be cast to java.base/java.lang.String
at Main.main(Main.java:4)
Eine Spezialisierung ist daher weniger eine Typwandlung als eine Prüfung, bei der geprüft wird, ob das Objekt einen Untertyp des angegebenen Typs hat und das Programm andernfalls mit einer Fehlermeldung abgebrochen wird.
Im folgenden Beispiel einer versuchten Umformung ist der eingeklammerte Typ gar kein Ober- oder Untertyp seines Operanden. Eine solche „Verschiebung“ (bei welcher der Operandentyp weder Unter- noch Obertyp des Operatortyps ist) kann erlaubt sein, wenn das Objekt sowohl ein Untertyp von »java.lang.CharSequence« als auch von »java.lang.PrintStream« ist. Zur Laufzeit wird dann jedoch festgestellt, daß dies nicht der Fall ist.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.CharSequence cs = "abc";
java.lang.System.out.println( ( java.io.PrintStream )cs ); }}- Protokoll
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.io.PrintStream
at Main.main(Main.java:4)
Zuweisungen und Spezialisierungen
Java erlaubt keine Initialisierung oder Zuweisung mit einem Ausdruck eines Obertyps auf der rechten Seite. Wenn man sich jedoch sicher ist, daß das Objekt tatsächlich ein Untertyp des erwarteten Typs hat, dann kann man eine Spezialisierung verwenden.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.CharSequence cs = "abc";
final java.lang.String s = cs;
java.lang.System.out.println( s ); }}- Protokoll
Main.java:4: error: incompatible types: CharSequence cannot be converted to String
final java.lang.String s = cs; }}
^
1 errorMain.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.CharSequence cs = "abc";
final java.lang.String s =( java.lang.String )cs;
java.lang.System.out.println( s ); }}- Protokoll
abc
Einsatzmöglichkeiten einer Verallgemeinerung
Der vorige Abschnitt zeigt eine Verwendung einer Spezialisierung.
Was aber könnte Anwendungen einer Verallgemeinerung sein?
Man kann damit beispielsweise steuern, welche Methode bei überladenen Methoden aufgerufen wird.
Main.java
public final class Main
{public static void m( final java.lang.String s )
{ java.lang.System.out.println( "A: " + s ); }public static void m( final java.lang.CharSequence cs )
{ java.lang.System.out.println( "B: " + cs ); }public static void main( final java.lang.String[] args )
{m( "abc" );
m( ( java.lang.CharSequence )"abc" ); }}
- Protokoll
A: abc
B: abc
Jedoch könnte man dies auch – etwas umständlicher – durch Einführung eines Namens erreichen.
Main.java
public final class Main
{public static void m( final java.lang.String s )
{ java.lang.System.out.println( "A: " + s ); }public static void m( final java.lang.CharSequence cs )
{ java.lang.System.out.println( "B: " + cs ); }public static void main( final java.lang.String[] args )
{m( "abc" );
{ final java.lang.CharSequence cs = "abc"; m( cs ); }}}
- Protokoll
A: abc
B: abc