Das Substitutionsprinzip in Java
ℛ Substitutionsprinzip Wird ein Ausdruck eines Typs erwartet, so kann ein Ausdruck eines Untertyps angegeben werden.
Wir hatte dieses Prinzip bereits im Grundkurs kennenlernt. Dort wurde behandelt: Der Datentyp »int« ist ein Untertyp des Datentyps »double«, da jeder int-Wert auch als double-Wert dargestellt werden kann. Deswegen kann ein int-Wert überall verwendet werden, wo auch ein double-Wert verwendet werden kann.
Allgemein muß beim Kopieren eines Wertes der Typ des Ausdrucks, der den Wert angibt, eine Untertyp des Typs des Ausdrucks, der das Ziel des Kopierens angibt, sein. Als Spezialfälle dieses Prinzips ergeben sich:
- Der Typ eines Initialisierungsausdrucks in einer Variablendeklaration muß ein Untertyp des Typs der Variablen sein.
- Der Typ der rechten Seite einer Zuweisung muß ein Untertyp des Typs der linken Seite der Zuweisung sein.
- Der Typ eines Argumentausdrucks muß ein Untertyp des Parametertyps sein.
- Der Typ eines Rückgabeausdrucks (in einer return-Anweisung) muß ein Untertyp des zugehörigen Rückgabetyps sein.
- Zitat
- “Substitution Principle: a variable of a given type may be assigned a value of any subtype of that type, and a method with a parameter of a given type may be invoked with an argument of any subtype of that type.”
- Java Generics and Collections – Maurice Naftalin & Philip Wadler
Substitutionsprinzip und Einträge
Ein Untertyp erweitert einen Typ oft um weitere Einträge. Da man diese zusätzlichen Einträge aber auch einfach ignorieren kann, ist, wenn ein Ausdruck eines bestimmten Typs erwartet wird, auch ein Ausdruck eines Untertyp akzeptabel.
Eine Typerwartung sagt eigentlich „Ich brauche alle Methoden dieses Typs.“, aber diese Methoden hat man auch bei allen Untertypen.
- Der Typ X übernimmt drei Methoden aus einem direkten Obertyp und fügt noch zwei eigene hinzu
Typ O, direkter Obertyp
| |
| f() g() h() |
| |
-----|-----------------|-------------
Typ X | f() g() h() | i() j()
-------------------------------------
Wenn ein Ausdruck eines Typs O erwartet wird, so liegt dies normalerweise daran, daß die Methoden dieses Typs (wie zum Beispiel f(), g() und h()) benötigt werden. Ein Untertyp, wie der Typ X, enthält aber auch diese Methoden und ist daher ebenfalls akzeptabel.
Beispielsweise ist ein Luxusauto, das um ein Radio und eine Klimaanlage erweitert wurde, weiterhin eine Auto (es kann als Auto verwendet werden).
- Ein Luxusauto übernimmt alle Aspekte eines Autos und fügt noch etwas hinzu
Auto
| |
| Gas Bremse Kupplung |
| |
-----|-------------------------|-----------------------
Luxusauto | Gas Bremse Kupplung | Radio Klimaanlage
-----------------------------------------------------
Methoden und Referenztypen
Die Methode »println« eines Ausdrucks vom Typ »java.lang.System.out« hat beispielsweise einen Parameter vom Typ »java.lang.Object«, aber nicht vom Typ »java.io.PrintStream«. Sie kann aber mit dem Ausdruck »java.lang.System.out« als Argument aufgerufen werden, weil der Typ dieses Ausdrucks, also »java.io.PrintStream«, ein Untertyp des Parametertyps »java.lang.Object« ist.
- Dokumentation (übersetzt und verändert)
java.io
Class PrintStream
void println( java.lang.Object x )
- Gibt das als Argumentwert übergebene Objekt und ein Zeilenende aus.
- Substitution eines Untertyps
java.lang.System.out.println( java.lang.Object )
^
| ^ ist Untertyp von
|
java.io.PrintStream
^
| ^ ist Ausdruck mit Typ
|
java.lang.System.outMain.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( java.lang.System.out ); }}transcript
java.io.PrintStream@15db9742
Ein Parameter vom Typ »java.lang.Object« drückt aus, daß an dieser Ausdrücke benötigt werden, die – als Kontexte verwendet – Aufrufe aller Methoden der Klasse »java.lang.Object« erlauben.
- Dokumentation (übersetzt und verändert)
javax.lang.model
Enum SourceVersion
static boolean isIdentifier( java.lang.CharSequence name )
- Ergibt, ob der als Argumentwert angegebene Name ein Bezeichner ist.
Die static-boolean-Methode »javax.lang.model.SourceVersion.isIdentifier(java.lang.CharSequence)« kann mit einem Argument vom Typ »java.lang.String« aufgerufen werden, obwohl ihr Parameter den Typ »java.lang.CharSequence« hat, weil »java.lang.String« ein Untertyp von »java.lang.CharSequence« ist.
- Substitution eines Untertyps
javax.lang.model.SourceVersion.isIdentifier( java.lang.CharSequence )
^
| ^ ist Untertyp von
|
java.lang.String
^
| ^ ist Ausdruck von
|
"args"Main.java
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( javax.lang.model.SourceVersion.isIdentifier( "args" )); }}transcript
true
»java.lang.CharSequence«
Als Beispiel einer Beziehung zwischen drei Typen zeigen wir hier die Typen »java.lang.Object«, »java.lang.CharSequence« und »java.lang.String«. Dabei stehen Obertypen eines Typs links von diesem Typ. Man erkennt deutlich, daß Untertypen mehr Methoden enthalten können als ihre Obertypen. Ein „String“ ist sozusagen eine „Luxus-CharSequence“.
- Der Typ »java.lang.String« übernimmt Methoden aus dem Obertyp »java.lang.CharSequence« und fügt noch eigene hinzu
.------------------------------.-------------------------------.------------------------------------------------------------.
| java.lang.Object | java.lang.CharSequence | java.lang.String |
|==============================|===============================|============================================================|
| equals(java.lang.Object) | equals(java.lang.Object) | equals(java.lang.Object) |
| getClass() | getClass() | getClass() |
| hashCode() | hashCode() | hashCode() |
| toString() | toString() | toString() |
|------------------------------|-------------------------------|------------------------------------------------------------|
| | length() | length() |
| | charAt(int) | charAt(int) |
| | subSequence(int,int) | subSequence(int,int) |
| | chars() | chars() |
| | codePoints() | codePoints() |
| |-------------------------------|------------------------------------------------------------|
| | | isEmpty() |
| | | codePointAt(int) |
| | | codePointBefore(int) |
| | | codePointCount(int,int) |
| | | offsetByCodePoints(int,int) |
| | | getChars(int,int,char[],int) |
| | | getBytes(int,int,byte[],int) |
| | | getBytes(java.nio.charset.Charset) |
| | | getBytes() |
| | | equals(java.lang.Object) |
| | | contentEquals(java.lang.StringBuffer) |
| | | contentEquals(java.lang.CharSequence) |
| | | equalsIgnoreCase(java.lang.String) |
| | | compareTo(java.lang.String) |
| | | compareToIgnoreCase(java.lang.String) |
| | | regionMatches(int,java.lang.String,int,int) |
| | | regionMatches(boolean,int,java.lang.String,int,int) |
| | | startsWith(java.lang.String,int) |
| | | startsWith(java.lang.String) |
| | | endsWith(java.lang.String) |
| | | hashCode() |
| | | indexOf(int) |
| | | indexOf(int,int) |
| | | lastIndexOf(int) |
| | | lastIndexOf(int,int) |
| | | indexOf(java.lang.String) |
| | | indexOf(java.lang.String,int) |
| | | lastIndexOf(java.lang.String) |
| | | lastIndexOf(java.lang.String,int) |
| | | substring(int) |
| | | substring(int,int) |
| | | concat(java.lang.String) |
| | | replace(char,char) |
| | | matches(java.lang.String) |
| | | contains(java.lang.CharSequence) |
| | | replaceFirst(java.lang.String,java.lang.String) |
| | | replaceAll(java.lang.String,java.lang.String) |
| | | replace(java.lang.CharSequence,java.lang.CharSequence) |
| | | split(java.lang.String,int) |
| | | split(java.lang.String) |
| | | toLowerCase(java.util.Locale) |
| | | toLowerCase() |
| | | toUpperCase(java.util.Locale) |
| | | toUpperCase() |
| | | trim() |
| | | toCharArray() |
| | | java.lang.String valueOf(java.lang.Object) |
| | | java.lang.String valueOf(char[]) |
| | | java.lang.String valueOf(char[],int,int) |
| | | java.lang.String copyValueOf(char[],int,int) |
| | | java.lang.String copyValueOf(char[]) |
| | | java.lang.String valueOf(boolean) |
| | | java.lang.String valueOf(char) |
| | | java.lang.String valueOf(int) |
| | | java.lang.String valueOf(long) |
| | | java.lang.String valueOf(float) |
| | | java.lang.String valueOf(double) |
| | | java.lang.String intern() |
| | | compareTo(java.lang.Object) |
'------------------------------'-------------------------------'------------------------------------------------------------'
Obertypentest durch Zuweisung (Initialisierung) ℳ
Lesehinweis In diesem Kurs sind Teile, die Kenntnisse über Methodendeklarationen voraussetzen, teilweise mit „ ℳ “ gekennzeichnet. Leser, die noch keine Kenntnisse über Methodendeklarationen haben, können diese Kursteile einfach überspringen. Es reicht aber vielleicht auch, wenn man sich merkt, daß eine Methodendeklaration mit »public static« beginnt und mit »}« endet (vereinfacht gesagt) und in den runden Klammern, die auf »public static« folgen, Variablen deklariert werden können, die man „Parameter“ nennt.
Die folgende Methode wird von Compiler akzeptiert, weil »java.lang.CharSequence« ein Obertyp von »java.lang.String« ist.
Main.java
public final class Main
{public static void test( final java.lang.String string )
{ final java.lang.CharSequence sequence = string; }public static void main( final java.lang.String[] args )
{ }}transcript
- (keine Ausgabe)
Die folgende Methode wird von Compiler akzeptiert, weil »java.lang.CharSequence« ein Obertyp von »java.lang.String« ist.
Das folgende Programmbeispiel zeigt, daß »java.lang.String« kein Untertyp von »java.lang.CharSequence« ist.
Main.java
public final class Main
{public static void test( final java.lang.CharSequence sequence )
{ final java.lang.String string = sequence; }public static void main( final java.lang.String[] args )
{ }}transcript
Main.java:5: error: incompatible types: CharSequence cannot be converted to String
{ final java.lang.String string = sequence; }
^
1 error
Mit »cannot be converted to« sagt der Compiler, daß der rechts stehende Typ kein Untertyp des linksstehenden Typs ist.
Obertypentest durch Aufruf ℳ ⃗
Das folgende Programm wird von Compiler akzeptiert, weil »java.lang.CharSequence« ein Obertyp von »java.lang.String« ist.
Main.java
public final class Main
{public static void sequence( final java.lang.CharSequence sequence ) {}
public static void test( final java.lang.String string ) { sequence( string ); }
public static void main( final java.lang.String[] args )
{ }}transcript
- (keine Ausgabe)
Das folgende Programmbeispiel zeigt, daß »java.lang.String« kein Untertyp von »java.lang.CharSequence« ist.
Main.java
public final class Main
{public static void string( final java.lang.String string ) {}
public static void test( final java.lang.CharSequence sequence ) { string( sequence ); }
public static void main( final java.lang.String[] args )
{ }}transcript
Main.java:5: error: incompatible types: CharSequence cannot be converted to String
{ final java.lang.String string = sequence; }
^
1 error
Mit »cannot be converted to« sagt der Compiler, daß der rechts stehende Typ kein Untertyp des linksstehenden Typs ist.
Obertypentests ℳ *
Wenn »S « und »T « beide Referenztypen sind, dann wird das folgende Programm vom Compiler ohne Fehlermeldung genau dann akzeptiert wird, wenn »T « ein Obertyp von »S « ist. Auf diese Weise ist es möglich, auch ohne Lesen der Dokumentation zu ermitteln, ob ein Typ ein Obertyp eines anderen Typs ist.
Main.java
public final class Main
{public static void test( final S s )
{ final T t = s; }public static void main( final java.lang.String[] args )
{ }}
Wenn »S « und »T « beide Referenztypen sind, dann das folgende Programm vom Compiler ohne Fehlermeldung genau dann akzeptiert wird, wenn »T « ein Obertyp von »S « ist. Auf diese Weise ist es möglich, auch ohne Lesen der Dokumentation zu ermitteln, ob ein Typ ein Obertyp eines anderen Typs ist.
Main.java
public final class Main
{public static void t( final T t ) {}
public static void test( final S s ) { t( s ); }
public static void main( final java.lang.String[] args )
{ }}
Übungsfragen
? Übungsfrage
Welcher Typ kann mehr Methoden enthalten?
- Anton: Ein Obertyp eines Typs kann mehr Methoden enthalten als der Typ.
- Berta: Ein Untertyp eines Typs kann mehr Methoden enthalten als der Typ.
? Übungsfrage
Welche der folgenden Aussagen treffen zu?
- Anton: Alle Methoden eines Obertyps eines Typs sind auch in dem Typ enthalten.
- Berta: Alle Methoden eines Untertyps eines Typs sind auch in dem Typ enthalten.
? Übungsfrage
Welche der folgenden Aussagen treffen zu?
- Anton: Auf der linken Seite einer Zuweisung muß ein Ausdruck eines Obertyps des Typs des Ausdrucks der rechten Seite stehen.
- Berta: Auf der linken Seite einer Zuweisung muß ein Ausdruck eines Untertyps des Typs des Ausdrucks der rechten Seite stehen.
? Übungsfrage
Welche der folgenden Aussagen treffen zu?
- Anton: Der Typ eines Arguments muß ein Obertyp des Typs des dazugehörigen Parameters sein.
- Berta: Der Typ eines Arguments muß ein Untertyp des Typs des dazugehörigen Parameters sein.
? Übungsfrage ℳ
Können Sie vorhersagen, ob das folgende Programm vom Compiler akzeptiert werden wird, ohne es auszuprobieren?
Bei Übungsaufgabe dieser Art ist zu beachten, daß die verwendeten Variablen durch die Deklarationen einen Typ erhalten. In dem folgenden Programm erhält die Variablen »sequence« beispielsweise den Typ »java.lang.CharSequence« und die Variable »string« den Typ »java.lang.String«. Da »sequence« ein Parameter ist, kann es in unserem Programm einen Typ erhalten ohne jemals einen Wert zu erhalten! Bei der Beantwortung der Aufgabe soll dann beurteilt werden, ob ein Aufruf oder eine Zuweisung/Initialisierung (hier: »string = sequence«) bei den gegebenen Typen erlaubt ist.
Main.java
public final class Main
{public static void test( final java.lang.CharSequence sequence )
{ final java.lang.String string = sequence; }public static void main( final java.lang.String[] args )
{ }}
? Übungsfrage (1) ℳ
Können Sie vorhersagen, ob das folgende Programm vom Compiler akzeptiert werden wird, ohne es auszuprobieren?
Main.java
public final class Main
{public static void test( final java.lang.String string )
{ final java.lang.String string1 = string; }public static void main( final java.lang.String[] args )
{ }}
? Übungsfrage (2) ℳ
Können Sie vorhersagen, ob das folgende Programm vom Compiler akzeptiert werden wird, ohne es auszuprobieren?
Main.java
public final class Main
{public static void test( final java.lang.String string )
{ final java.lang.CharSequence sequence = string; }public static void main( final java.lang.String[] args )
{ }}
? Übungsfrage (3) ℳ
Können Sie vorhersagen, ob das folgende Programm vom Compiler akzeptiert werden wird, ohne es auszuprobieren?
Main.java
public final class Main
{public static void test( final java.lang.String string ) { sequence( string ); }
public static void sequence( final java.lang.CharSequence sequence ) {}
public static void main( final java.lang.String[] args )
{ }}
? Übungsfrage (4) ℳ
Können Sie vorhersagen, ob das folgende Programm vom Compiler akzeptiert werden wird, ohne es auszuprobieren?
Main.java
public final class Main
{public static void string( final java.lang.String string ) {}
public static void test( final java.lang.CharSequence sequence )
{ string( sequence ); }public static void main( final java.lang.String[] args )
{ }}
Übungsfrage (5) ℳ
Können Sie vorhersagen, ob das folgende Programm vom Compiler akzeptiert werden wird, ohne es auszuprobieren?
Main.java
public final class Main
{public static void string( final java.lang.String string ) {}
public static void test( final java.io.PrintStream stream ) { string( stream ); }
public static void main( final java.lang.String[] args )
{ }}
Übungsaufgaben ⃗
/ Übungsaufgabe ℳ ⃗
Ermitteln Sie durch Ausprobieren, ob das folgende Programm vom Compiler akzeptiert wird, und damit, ob »java.lang.StringBuilder« ein Untertyp von »java.lang.CharSequence« ist.
Main.java
public final class Main
{public static void sequence( final java.lang.CharSequence sequence ) {}
public static void test( final java.lang.StringBuilder builder )
{ sequence( builder ); }public static void main( final java.lang.String[] args )
{ }}
/ Übungsaufgabe (1) ℳ ⃗
Ermitteln Sie durch Ausprobieren, ob das folgende Programm vom Compiler akzeptiert wird, und damit, ob »java.lang.Integer« ein Untertyp von »java.lang.Double« ist.
Main.java
public final class Main
{public static void d( final java.lang.Double d ) {}
public static void test( final java.lang.Integer i ) { d( i ); }
public static void main( final java.lang.String[] args )
{ }}