Typparameter ohne Varianz in Java
Diese Einführung in Typparameter in Java greift nur einige Aspekte beispielhaft heraus. Sie ist nicht als systematische und vollständige Einführung in dieses Thema gemeint.
Typparameter von Methoden
Typparameter können nie elementare Typen darstellen, sondern immer nur Referenztypen. Letztendlich stellen sie eine glorreiche Verzierung für etwas dar, das dann vom Compiler letztendlich als »java.lang.Object« übersetzt wird, denn die genauen Typen von Typparametern werden nur vom Compiler zur Prüfung eines Programms verwendet, aber dann meistens mit »java.lang.Object« übersetzt!
Als einfaches Beispiel betrachten wir eine Methode, die ihr Argumentobjekt wieder zurückgibt:
Main.java
public final class Main
{public static java.lang.Object id( final java.lang.Object arg )
{ return arg; }public static void main( final java.lang.String args[] )
{ java.lang.System.out.println( id( "abc" ).length() ); }}transcript
Main.java:8: error: cannot find symbol
{ java.lang.System.out.println( id( "abc" ).length() ); }}
^
symbol: method length()
location: class Object
1 error
Die Information, daß das Argument den Typ »java.lang.String« hat, ist verloren gegangen.
Durch Festlegung des Typs java.lang.String, können wir das Problem lösen, aber die Methode ist nun nicht mehr so universell, da sie nur noch mit java.lang.String-Objekten aufgerufen werden kann.
Main.java
public final class Main
{public static java.lang.String id( final java.lang.String arg ){ return arg; }
public static java.lang.Integer id( final java.lang.Integer arg ){ return arg; }
public static java.lang.Double id( final java.lang.Double arg ){ return arg; }public static void main( final java.lang.String args[] )
{ java.lang.System.out.println( Main.id( "abc" ).length() ); }}transcript
3
Durch einen Typparameter können wir die Methode am Orte ihrer Deklaration weiterhin universell belassen und dann erst beim Aufruf auf java.lang.String oder – bei einem anderen Aufruf – auf java.lang.Integer festlegen.
Main.java
public final class Main
{public static< T >T id( final T arg )
{ return arg; }public static void main( final java.lang.String args[] )
{ java.lang.System.out.println( Main.< java.lang.String >id( "abc" ).length() );
/* a.lang.System.out.println( < java.lang.String >id( "abc" ).length() ); // geht nicht */
java.lang.System.out.println( Main.< java.lang.Integer >id( new java.lang.Integer( 2 )).doubleValue() ); }}transcript
3
Wir können das Typargument auch weglassen, es wird dann vom Compiler hergeleitet (als java.lang.String oder java.lang.Integer). Dadurch wird dann auch der Rückgabetyp festgelegt.
Main.java
public final class Main
{public static< T >T id( final T arg )
{ return arg; }public static void main( final java.lang.String args[] )
{ java.lang.System.out.println( Main.< java.lang.String >id( "abc" ).length() );
java.lang.System.out.println( Main.< java.lang.Integer >id( new java.lang.Integer( 2 )).doubleValue() );
java.lang.System.out.println( id( "abc" ).length() );
java.lang.System.out.println( id( new java.lang.Integer( 2 )).doubleValue() ); }}transcript
3
2.0
3
2.0
Falls wir in der Methode eine spezielle Methode aufrufen, kann und sollte der Typparameter entsprechend eingeschränkt werden.
Main.java
public final class Main
{public static< T extends java.lang.CharSequence >void printLength( final T arg )
{ java.lang.System.out.println( arg.length() ); }public static void main( final java.lang.String args[] )
{ printLength( "abcde" ); }}transcript
5
Mit einer Und-Verknüpfung können wir ganz genau sagen, welche Schnittstellen wir alle benötigen.
Main.java
public final class Main
{public static< T extends java.lang.CharSequence & java.lang.Comparable< T >>
void printLengthIfSame( final T arg, final T arg1 )
{ if( arg.compareTo( arg1 )== 0 )
java.lang.System.out.println( arg.length() ); }public static void main( final java.lang.String args[] )
{ printLengthIfSame( "abcde", "abcde" ); }}transcript
5
(Eine Oder-Verknüpfung zweier Typen kann durch Überladung gelöst werden.)
Typparameter von Klassen
Auch Klassen können Typparameter haben.
Main.java
class Holder< T >
implements java.util.function.Supplier< T >
{ T object;
public Holder( final T object ){ this.object = object; }
public T get(){ return this.object; }}public final class Main
{public static void main( final java.lang.String args[] )
{ { final Holder< java.lang.String >holder = new Holder< java.lang.String >( "defg" );
java.lang.System.out.println( holder.get() ); }
{ final Holder< java.lang.String >holder = new Holder<>( "hijk" );
java.lang.System.out.println( holder.get() ); }}}transcript
defg
hijk
Für die Typparameter von Klassen gilt das oben zu den Typparameter von Methoden Gesagte (extends, &) entsprechend.
In der Methode-Holder sind keine Operationen, wie beispielsweise »new T()« möglich, weil diese die Kenntnis des speziellen Argumenttyps zur Laufzeit voraussetzen. Diese Information steht zur Laufzeit aber in der Regel nicht zur Verfügung.
Typlöschung
Die Information über das konkrete Typargument dient nur dem Compiler zur Typprüfung. Sie ist zur Laufzeit nicht verfügbar!
Main.java
class Wrapper< T >
{ T t;
Wrapper(){ this.t = new T(); }
T get(){ return t; }}public final class Main
{public static void main( final java.lang.String args[] )
{ final Wrapper< java.lang.Object >wrapper
= new Wrapper< java.lang.Object >(); }}transcript
Main.java:3: error: unexpected type
Wrapper(){ this.t = new T(); }
^
required: class
found: type parameter T
where T is a type-variable:
T extends Object declared in class Wrapper
1 error
Tricks gegen die Typlöschung
Bei anonymen inneren Klassen, stehen aber gewisse Informationen über die Typargumente ihrer Oberklasse zur Verfügung, so daß man hier den Typ zur Laufzeit durch komplizierte Abfragen erhalten kann. (Dieses Beispiel dient an dieser Stelle des Kurses nur der Unterhaltung und soll nicht als Empfehlung verstanden werden, davon bei der Programmierung Gebrauch zu machen.)
Gafter's Device, http://gafter.blogspot.com/2006/12/super-type-tokens.html
Main.java
class B< T >{}
class C extends B< java.lang.Double >{}
public final class Main
{ public static void main( final java.lang.String[] args )
{ java.lang.System.out.println
( ( ( java.lang.reflect.ParameterizedType )C.class.getGenericSuperclass() ).
getActualTypeArguments()[ 0 ]); }}transcript
class java.lang.Double
Main.java
class MyWrapper< T >
{ T mio;
@java.lang.SuppressWarnings( "unchecked" )
MyWrapper() throws java.lang.Throwable
{ mio =( T )
( ( ( java.lang.Class )
( ( ( java.lang.reflect.ParameterizedType )
this.getClass().getGenericSuperclass() ).
getActualTypeArguments()[ 0 ])).newInstance() ); }}
public final class Main
{ public static void main( final java.lang.String args[] ) throws java.lang.Throwable
{
{ MyWrapper< java.lang.String >a = new MyWrapper< java.lang.String >(){};
java.lang.System.out.println( "ax = " + a.mio );
java.lang.System.out.println( a.mio.getClass() ); }
{ MyWrapper< java.lang.Object >a = new MyWrapper< java.lang.Object >(){};
java.lang.System.out.println( "ax = " + a.mio );
java.lang.System.out.println( a.mio.getClass() ); }}}transcript
ax =
class java.lang.String
ax = java.lang.Object@1db9742
class java.lang.Object- Aussprachehinweise
- super ˈsupɚ (sd)