Die statische Aufrufprüfung in Java
Das nicht-statische Aufrufprinzip
Das nicht-statische Aufrufprinzip haben wir schon am Anfang des Aufbaukurses kennengelernt.
ℛ nicht-statisches Aufrufprinzip Ein Ausdruck als Kontext erlaubt einen Verbaufruf, wenn es eine Methode im Referenztyp dieses Ausdrucks gibt, die zu jenem Verbaufruf paßt.
Wir erinnern an zwei Beispiele dazu:
- Der Typ des Kontexts enthält eine zum Aufruf passende Methode
java.lang.System.out.print( 2 )
- Der Typ des Kontexts enthält keine zum Aufruf passende Methode
java.lang.System.out.print()
Die Typfilterung
Das Objekt »"abc"« enthält eine zum Verbaufruf »length()« passende Methode. Der Aufruf ist aber nur möglich, wenn auch der als Kontext verwendete Ausdruck (hier: »s«) eine solche Methode enthält. (Genauer gesagt muß die Methode als nicht-statische Methode im Typ des Kontexts enthalten sein.)
Main.java
public final class Main
{ public final static void main( final java.lang.String[] args )
{ { final java.lang.String s = "abc"; java.lang.System.out.println( s.length() ); }
{ final java.lang.CharSequence s = "abc"; java.lang.System.out.println( s.length() ); }
{ final java.lang.Object s = "abc"; java.lang.System.out.println( s.length() ); }}}- Konsole
Main.java:40: error: cannot find symbol
{ final java.lang.Object s = "abc"; java.lang.System.out.println( s.length() ); }}}
^
symbol: method length()
location: variable s of type Object
1 error
ℛ nicht-statisches Aufrufprinzip Ein Ausdruck als Kontext erlaubt einen Verbaufruf, wenn es eine Methode im Referenztyp dieses Ausdrucks gibt, die zu jenem Verbaufruf paßt.
ℛ Objektregel Der Typ eines Objektes muß ein Untertyp des Typs eines Ausdrucks (also auch eines Namens) für das Objekt sein.
{ final java.lang.String s = "abc"; java.lang.System.out.println( s.length() ); }
Übersetzung Ausführung
Ausdruck Objekts "abc"
| |
| |
------------------------------------------------>|
|.length() |.length()
| |
java.lang.String java.lang.String{ final java.lang.CharSequence s = "abc"; java.lang.System.out.println( s.length() ); }
Übersetzung Ausführung
Ausdruck Objekts "abc"
| |
| |
------------------------------------------------>|
|.length() |.length()
| |
java.lang.CharSequence java.lang.String{ final java.lang.Object s = "abc"; java.lang.System.out.println( s.length() ); }
Übersetzung Ausführung
Ausdruck Objekts "abc"
| |
| |
----------------------->| |
| |.length()
| |
java.lang.Object java.lang.String
Ein Aufrufausdruck wie »s.length()« bedeutet:
- Der Compiler soll prüfen, ob eine nicht-statische Methode »length()« im Typ des Ausdrucks »s« vorhanden ist.
- Bei der Auswertung dieses Ausdrucks soll geprüft werden, ob eine Methode »length()« im Objekt, welches der momentane Wert des Ausdrucks »s« ist, vorhanden ist.
- Wenn beide Prüfungen bestanden wurden, dann soll der Wert dieses Ausdrucks »s.length()« durch Ausführung der im Objekt gefundenen Methode »length()« ermittelt werden. (Beziehungsweise: bei einer void-Methode soll diese ausgeführt werden.)
Man möchte es manchmal einschränken, welche Methoden eines Objektes aufgerufen werden können, und kann dazu dann einen Ausdruck eines Obertyps verwenden.
Warum wird manchmal ein echter Obertyp verwendet?
Als Typ einer Variablen wird manchmal gerne ein Oberttyp des Objekttyps verwendet, weil dies dann mehr verschiedene Initialisierungsausdrücke erlaubt.
Wir betrachten beispielsweise das folgende Programm mit einem Obertyp.
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( o ); }}Protokoll
java.io.PrintStream@15db9742
Wir können nun als Initialisierungsausdruck auch einen Ausdruck eines anderen Typs verwenden, ohne daß am Rest des Programmes etwas geändert werden muß.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.Object o = "abc";
java.lang.System.out.println( o ); }}Protokoll
abc
Wenn wir keinen Obertyp verwenden, so ist diese einfache Änderung des Typs des Ininitialisierungsausdrucks nicht mehr möglich. Dafür können nun aber mehr Methoden aufgerufen werden.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.io.PrintStream o = java.lang.System.out;
o.println( o ); }}Protokoll
java.io.PrintStream@15db9742
Die Verwendung eines Obertyps erlaubt mehr Initialisierungsausdrücke, die Verwendung des gleichen Typs erlaubt mehr Aufrufe.
Man sollte daher im besten Fall einen Typ verwenden, der gerade alle Aufrufe erlaubt, aber nicht unnötig speziell ist. Beispielsweise erlaubt der Typ »java.lang.CharSequence« im folgenden Programm den darin benötigten Aufruf »length()«.
Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ final java.lang.CharSequence s = "abc";
java.lang.System.out.println( s );
java.lang.System.out.println( s.length() ); }}Protokoll
abc
3
Der Typ »java.lang.Object« wäre zu allgemein und würde den »length()«-Aufruf nicht mehr gestatten.
Der Typ »java.lang.String« wäre unnötig speziell, da er andere Untertypen von »java.lang.CharSequence« als Initialisierungsausdrücke ausschließen würde. (Ausdrücke solcher anderer Untertypen von »java.lang.CharSequence« werden aber erst später im Kurs vorgestellt.)
Übungsfragen
? Übungsfrage
Kann der folgende Block voraussichtlich ohne Fehlermeldung übersetzt werden?
- Quelltext
{ final java.lang.String s = "abcdefg"; s.subSequence( 2, 4 ); }
? Übungsfrage
Kann der folgende Block voraussichtlich ohne Fehlermeldung übersetzt werden?
- Quelltext
{ final java.lang.CharSequence c = "abcdefg"; c.subSequence( 0, 2 ); }
? Übungsfrage
Kann der folgende Block voraussichtlich ohne Fehlermeldung übersetzt werden?
- Quelltext
{ final java.lang.Object o = "abcdefg"; o.subSequence( 0, 2 ); }
? Übungsfrage ⃗
Kann der folgende Block voraussichtlich ohne Fehlermeldung übersetzt werden?
- Quelltext
{ final java.io.PrintStream o = "abcdefg"; o.subSequence( 0, 2 ); }