<T,R,P>
.public class MyGenericClass<T> { T attr1; MyGenericClass() { } MyGenericClass(T attr1) { this.attr1 = attr1; } public T getAttr1() { return attr1; } public void setAttr1(T attr1) { this.attr1 = attr1; } }Comme mentionné précédemment le type générique est présent uniquement dans le code source et il est complètement perdu dans le bytecode. En fait, le code précédent est considéré, lors de la génération du bytecode, comme s'il a été écrit comme suit :
class MyGenericClass { Object attr1; MyGenericClass() { } MyGenericClass(Object attr1) { this.attr1 = attr1; } public Object getAttr1() { return attr1; } public void setAttr1(Object attr1) { this.attr1 = attr1; } }Le compilateur remplace le type générique par le type de la classe
Object
.public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<Number> myGenericClass; myGenericClass = new MyGenericClass<Number>(Integer.valueOf(0)); myGenericClass.setAttr1(Integer.valueOf(10)); Number v1 = myGenericClass.getAttr1(); // ~ Number v1 = (Number) myGenericClass.getAttr1(); System.out.println(v1); } }Le type que l'on souhaite utilisé doit être spécifié lors de la déclaration des variables qui référencent la classe générique.
public class MyGenericClassTest { private static MyGenericClass<Number> foo(MyGenericClass<Number> myGenericClass) { if(myGenericClass == null) return null; Number v1 = myGenericClass.getAttr1(); if(v1 == null) return new MyGenericClass<Number>(Integer.valueOf(0)); if(v1 instanceof Byte) myGenericClass.setAttr1(v1.byteValue() * (byte) 2); else if(v1 instanceof Short) myGenericClass.setAttr1(v1.shortValue() * (short) 2); else if(v1 instanceof Integer) myGenericClass.setAttr1(v1.intValue() * 2); else if(v1 instanceof Long) myGenericClass.setAttr1(v1.longValue() * 2L); else if(v1 instanceof Float) myGenericClass.setAttr1(v1.floatValue() * 2.0f); else if(v1 instanceof Double) myGenericClass.setAttr1(v1.doubleValue() * 2.0d); return myGenericClass; } public static void main(String[] args) { MyGenericClass<Number> myGenericClass = new MyGenericClass<Number>(Integer.valueOf(10)); myGenericClass = foo(myGenericClass); } }Le code précédent est considéré, lors de la génération du bytecode, comme s'il a été écrit comme suit :
public class MyGenericClassTest { private static MyGenericClass foo(MyGenericClass myGenericClass) { if(myGenericClass == null) return null; Number v1 = (Number) myGenericClass.getAttr1(); if(v1 == null) return new MyGenericClass(Integer.valueOf(0)); if(v1 instanceof Byte) myGenericClass.setAttr1(v1.byteValue() * (byte) 2); else if(v1 instanceof Short) myGenericClass.setAttr1(v1.shortValue() * (short) 2); else if(v1 instanceof Integer) myGenericClass.setAttr1(v1.intValue() * 2); else if(v1 instanceof Long) myGenericClass.setAttr1(v1.longValue() * 2L); else if(v1 instanceof Float) myGenericClass.setAttr1(v1.floatValue() * 2.0f); else if(v1 instanceof Double) myGenericClass.setAttr1(v1.doubleValue() * 2.0d); return myGenericClass; } public static void main(String[] args) { MyGenericClass myGenericClass = new MyGenericClass(Integer.valueOf(10)); myGenericClass = foo(myGenericClass); } }Le compilateur utilise le type générique pour ajouter un cast lors de l'affectation d'une référence de type générique à une variable du type spécifié lors de la déclaration de la classe.
Number v1 = myGenericClass.getAttr1();
sera remplacé par Number v1 = (Number) myGenericClass.getAttr1();
.MyGenericClass<Number> myGenericClass; // compiler error: Type mismatch: cannot convert from MyGenericClass<Integer> to MyGenericClass<Number> myGenericClass = new MyGenericClass<Integer>(Integer.valueOf(0));Cette restriction s'applique aussi aux valeurs de retour et arguments des méthodes :
public class MyGenericClassTest { private static MyGenericClass<Number> foo(MyGenericClass<Number> myGenericClass) { return null; } public static void main(String[] args) { MyGenericClass<Integer> myGenericClass = null; // compiler error: The method foo(MyGenericClass<Number>) in the type MyGenericClassTest is not applicable for the arguments (MyGenericClass<Integer>) foo(myGenericClass); // compiler error: The method foo(MyGenericClass<Number>) in the type MyGenericClassTest is not applicable for the arguments (MyGenericClass<Float>) foo(new MyGenericClass<Float>()); // compiler error: Type mismatch: cannot convert from MyGenericClass<Number> to MyGenericClass<Integer> myGenericClass = foo(new MyGenericClass<Number>()); } }La règle générale est la suivante : le compilateur va toujours interdire l'affectation d'une référence à une autre si les types génériques des deux références ne correspondent pas exactement.
MyGenericClass<Integer> myGenericClass_Integer = null; MyGenericClass<Float> myGenericClass_Float = null; // compiler error: Type mismatch: cannot convert from MyGenericClass<Float> to MyGenericClass<Integer> myGenericClass_Integer = myGenericClass_Float;
// Warning: Type safety: The expression of type MyGenericClass needs unchecked conversion to conform to MyGenericClass<Number> MyGenericClass<Number> myGenericClass_Number = new MyGenericClass(new String("10")); // Warning: MyGenericClass is a raw type. References to generic type MyGenericClass<T> should be parameterized MyGenericClass myGenericClass = null; // Warning: Type safety: The constructor MyGenericClass(Object) belongs to the raw type MyGenericClass. // References to generic type MyGenericClass<T> should be parameterized myGenericClass = new MyGenericClass(new String("10")); // Warning: Type safety: The expression of type MyGenericClass needs unchecked conversion to conform to MyGenericClass<Number> myGenericClass_Number = myGenericClass; // OK myGenericClass = myGenericClass_Number;
myGenericClass.setAttr1(new Float(10.0f)); // OK : Float est une sous-classe de Number myGenericClass.setAttr1(Integer.valueOf(10)); // OK : Integer est une sous-classe de NumberLe compilateur affichera une erreur si le type spécifié lors de l'invocation des méthodes génériques de la classe est différent du type spécifié lors de l'instanciation de cette classe :
// compiler error: The method setAttr1(Number) in the type MyGenericClass<Number> is not applicable for the arguments (String) myGenericClass.setAttr1(new String("10"));
public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<? extends Number> myGenericClass_1; // OK MyGenericClass<? super Integer> myGenericClass_2; // OK MyGenericClass<?> myGenericClass_3; // OK // compiler error: Cannot instantiate the type MyGenericClass<? extends Number> myGenericClass_1 = new MyGenericClass<? extends Number>(); // compiler error: Cannot instantiate the type MyGenericClass<? super Integer> myGenericClass_2 = new MyGenericClass<? super Integer>(); // compiler error: Cannot instantiate the type MyGenericClass<?> myGenericClass_3 = new MyGenericClass<?>(); myGenericClass_1 = new MyGenericClass<Float>(); // OK : Float extends Number myGenericClass_2 = new MyGenericClass<Object>(); // OK : Integer extends Object myGenericClass_3 = new MyGenericClass<String>(); // OK : ? means any type } public static MyGenericClass<? extends Number> foo(MyGenericClass<? extends Number> myGenericClass_1) { return null; } }
myGenericClass
n'importe quelle référence dont le type générique est le même type ou un sous-type de la classe Number
.public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<? extends Number> myGenericClass; myGenericClass = new MyGenericClass<Integer>(); // OK myGenericClass = new MyGenericClass<Float>(); // OK } }Il y a cependant quelques restrictions avec l'utilisation de cette syntaxe :
MyGenericClass<? extends Number> myGenericClass; myGenericClass = new MyGenericClass<Integer>(Integer.valueOf(10)); // OK // compiler error: The method setAttr1(capture#2-of ? extends Number) in the type MyGenericClass<capture#2-of ? extends Number> is not applicable for the arguments (Integer) myGenericClass.setAttr1(Integer.valueOf(10));
// compiler error: Type mismatch: cannot convert from capture#3-of ? extends Number to Integer Integer intVar1 = myGenericClass.getAttr1(); Integer intVar2 = (Integer) myGenericClass.getAttr1();Attention, le compilateur vérifie uniquement que le type utilisé pour le cast est un sous-type du type générique défini à la déclaration de la variable. Ce qui veut dire que vous pouvez avoir une erreur à l'exécution si le cast n'est pas possible :
package com.mtitek.generics; public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<? extends Number> myGenericClass; myGenericClass = new MyGenericClass<Float>(new Float(10.0)); // OK Integer intVar1 = (Integer) myGenericClass.getAttr1(); // Runtime error: ClassCastException } }Résultat d'exécution du code :
Exception in thread "main" java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.Integer at com.mtitek.generics.MyGenericClassTest.main(MyGenericClassTest.java:9)
Number
.public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<? super Integer> myGenericClass; myGenericClass = new MyGenericClass<Integer>(); // OK // compiler error: Type mismatch: cannot convert from MyGenericClass<Float> to MyGenericClass<? super Integer> myGenericClass = new MyGenericClass<Float>(); } }Vous pouvez utiliser la référence créée avec cette syntaxe pour lire et modifier les attributs dont le type est générique (typiquement en accédant directement à ces attributs ou en invoquant des méthodes qui renvoient ou modifient ces attributs), mais il faut ajouter un cast explicite pour pouvoir le faire :
public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<? super Integer> myGenericClass; myGenericClass = new MyGenericClass<Integer>(); // OK Object objVar1 = Integer.valueOf(10); // compiler error: The method setAttr1(capture#2-of ? super Integer) in the type MyGenericClass<capture#2-of ? super Integer> is not applicable for the arguments (Object) myGenericClass.setAttr1(objVar1); myGenericClass.setAttr1((Integer) objVar1); // OK : il faut ajouter un cast explicite // compiler error: Type mismatch: cannot convert from capture#4-of ? super Integer to Integer Integer intVar1 = myGenericClass.getAttr1(); Integer intVar2 = (Integer) myGenericClass.getAttr1(); // OK : il faut ajouter un cast explicite } }Attention, le compilateur vérifie uniquement que le type utilisé pour le cast est un sous-type du type générique défini à la déclaration de la variable. Ce qui veut dire que vous pouvez avoir une erreur à l'exécution si le cast n'est pas possible :
package com.mtitek.generics; public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<? super Integer> myGenericClass; myGenericClass = new MyGenericClass<Integer>(); // OK Object objVar1 = new Float(10.0); myGenericClass.setAttr1((Integer) objVar1); // Runtime error: ClassCastException } }Résultat d'exécution du code :
Exception in thread "main" java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.Integer at com.mtitek.generics.MyGenericClassTest.main(MyGenericClassTest.java:11)
myGenericClass
n'importe quellle référence dont le type générique peut être n'importe quel type.public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<?> myGenericClass; myGenericClass = new MyGenericClass<Integer>(); // OK myGenericClass = new MyGenericClass<Object>(); // OK } }En fait, la syntaxe
<?>
est équivalente à <? extends Object>
.MyGenericClass<?> myGenericClass; myGenericClass = new MyGenericClass<Integer>(Integer.valueOf(10)); // OK // compiler error: The method setAttr1(capture#2-of ?) in the type MyGenericClass<capture#2-of ?> is not applicable for the arguments (Integer) myGenericClass.setAttr1(Integer.valueOf(10));
// compiler error: Type mismatch: cannot convert from capture#2-of ? to Integer Integer intVar1 = myGenericClass.getAttr1(); Integer intVar2 = (Integer) myGenericClass.getAttr1();Attention, le compilateur vérifie uniquement que le type utilisé pour le cast est correct pour l'instruction d'affectation.
package com.mtitek.generics; public class MyGenericClassTest { public static void main(String[] args) { MyGenericClass<?> myGenericClass; myGenericClass = new MyGenericClass<Float>(new Float(10.0)); // OK Integer intVar1 = (Integer) myGenericClass.getAttr1(); // Runtime error: ClassCastException } }Résultat d'exécution du code :
Exception in thread "main" java.lang.ClassCastException: java.lang.Float cannot be cast to java.lang.Integer at com.mtitek.generics.MyGenericClassTest.main(MyGenericClassTest.java:9)
public class MyGenericMethodTest { private static <T> T getValue(T value, T defaultValue) { return value != null ? value : defaultValue; } private static <R extends Number> Number multiply(R arg1, R arg2) { R var1 = arg1; R var2 = arg2; Number var3 = null; if (var1 == null || var2 == null) return null; if (var1 instanceof Byte) var3 = var1.byteValue() * var2.byteValue(); else if (var1 instanceof Short) var3 = var1.shortValue() * var2.shortValue(); else if (var1 instanceof Integer) var3 = var1.intValue() * var2.intValue(); else if (var1 instanceof Long) var3 = var1.longValue() * var2.longValue(); else if (var1 instanceof Float) var3 = var1.floatValue() * var2.floatValue(); else if (var1 instanceof Double) var3 = var1.doubleValue() * var2.doubleValue(); return var3; } public static void main(String[] args) { // explicit syntax: MyGenericMethodTest.<Integer> Integer result1 = MyGenericMethodTest.<Integer>getValue(1, 2); System.out.println(result1); // implicit syntax: : you can omit the <Integer> type String result2 = getValue(null, "default"); System.out.println(result2); // another test: you still need an explicit cast otherwise you get this error "Type mismatch: cannot convert from Number to Integer" Integer result3 = (Integer) MyGenericMethodTest.<Integer>multiply(10, 5); System.out.println(result3); } }
public class MyGenericConstructorTest { Number field1; <R extends Number> MyGenericConstructorTest(R arg1) { field1 = arg1; } public static void main(String[] args) { MyGenericConstructorTest myGenericConstructorTest_Integer = new MyGenericConstructorTest(Integer.valueOf(10)); System.out.println(myGenericConstructorTest_Integer.field1); MyGenericConstructorTest myGenericConstructorTest_Float = new MyGenericConstructorTest(new Float(10.0f)); System.out.println(myGenericConstructorTest_Float.field1); } }
throws
pour lancer l'exception spécifiée par le type générique.Throwable
, Error
, Exception
, ou n'importe quelle classe qui hérite de ces classes.package com.mtitek.generics; public class MyGenericExceptionTest { private static <T extends RuntimeException, R> void foo(R arg1) throws T { if(arg1 == null) throw new IllegalArgumentException(); else if(! (arg1 instanceof Number)) throw new NumberFormatException(); } public static void main(String[] args) { try { foo(null); } catch (RuntimeException e) { e.printStackTrace(); } try { foo("NOT A NUMBER"); } catch (RuntimeException e) { e.printStackTrace(); } } }Résultat d'exécution du code :
java.lang.IllegalArgumentException at com.mtitek.generics.MyGenericExceptionTest.foo(MyGenericExceptionTest.java:20) at com.mtitek.generics.MyGenericExceptionTest.main(MyGenericExceptionTest.java:6) java.lang.NumberFormatException at com.mtitek.generics.MyGenericExceptionTest.foo(MyGenericExceptionTest.java:22) at com.mtitek.generics.MyGenericExceptionTest.main(MyGenericExceptionTest.java:12)