Cet article présente principalement l'utilisation des génériques Java et les problèmes liés à l'effacement de type. A une très bonne valeur de référence. Jetons un coup d'œil avec l'éditeur ci-dessous
Introduction
Java a introduit le mécanisme générique dans la version 1.5. L'essence des génériques est constituée de types paramétrés, c'est-à-dire<.>Le type de variable est un paramètre qui est spécifié comme un type spécifique lorsqu'il est utilisé. Les génériques peuvent être utilisés pour les classes, les interfaces et les méthodes. En utilisant des génériques, le code peut être rendu plus simple et plus sûr. Cependant, les génériques en Java utilisent l'effacement de type, ils ne sont donc que des pseudo-génériques. Cet article résume l'utilisation des génériques et les problèmes existants, se référant principalement aux « Réflexions sur la programmation Java ».
Les deux autres articles de cette série :Utilisation de base
Classes génériques
S'il existe une classe Holder utilisée pour envelopper une variable, le type de cette variable peut être arbitraire. Comment écrire le Holder ? Avant les génériques, vous pouviez faire ceci :public class Holder1 { private Object a; public Holder1(Object a) { this.a = a; } public void set(Object a) { this.a = a; } public Object get(){ return a; } public static void main(String[] args) { Holder1 holder1 = new Holder1("not Generic"); String s = (String) holder1.get(); holder1.set(1); Integer x = (Integer) holder1.get(); } }
Objet , il doit donc être forcé au type correspondant. Dans la méthode principale,holder1 enregistre d'abord une string, qui est un objet String, puis enregistre un objet Integer (le paramètre 1 sera automatiquement encadré). Le casting est déjà problématique lors de la suppression de variables du Holder. Vous devez également mémoriser différents types ici. Si vous faites une erreur, une exception d'exécution se produira.
Regardons la version générique de Holder :public class Holder2<T> { private T a; public Holder2(T a) { this.a = a; } public T get() { return a; } public void set(T a) { this.a = a; } public static void main(String[] args) { Holder2<String> holder2 = new Holder2<>("Generic"); String s = holder2.get(); holder2.set("test"); holder2.set(1);//无法编译 参数 1 不是 String 类型 } }
Méthodes génériques
Les génériques peuvent non seulement cibler des classes, mais peuvent également rendre une méthode générique individuellement. Par exemple :public class GenericMethod { public <K,V> void f(K k,V v) { System.out.println(k.getClass().getSimpleName()); System.out.println(v.getClass().getSimpleName()); } public static void main(String[] args) { GenericMethod gm = new GenericMethod(); gm.f(new Integer(0),new String("generic")); } } 代码输出: Integer String
Effacement de type
Qu'est-ce que l'effacement de type
Utilisation des génériques Java Le mécanisme d'effacement de type a a suscité beaucoup de controverses, à tel point que les fonctions génériques de Java ont été limitées et ne peuvent être qualifiées que de "pseudo-génériques". Qu’est-ce que l’effacement de texte ? Pour faire simple, les paramètres de type n'existent qu'au moment de la compilation, la machine virtuelle Java (JVM) ne connaît pas l'existence de génériques. Regardons d'abord un exemple :public class ErasedTypeEquivalence { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } }
C# qui prennent en charge les vrais génériques, ce sont des classes différentes.
Le paramètre générique sera effacé jusqu'à sa première limite. Par exemple, dans la classe Holder2 ci-dessus, si le type de paramètre est un seul T, alors il sera effacé en Objet, ce qui équivaut à tous les endroits où T apparaît. Remplacer par Objet. Ainsi, du point de vue de la JVM, la variable enregistrée a est toujours de type Object. La raison pour laquelle il est automatiquement supprimé est le type de paramètre que nous avons transmis. En effet, le compilateur insère le code de conversion de type dans le fichier de bytecode compilé et nous n'avons pas besoin de le convertir manuellement. Si le type de paramètre a des limites, effacez-le jusqu'à sa première limite, qui sera abordée dans la section suivante.擦除带来的问题
擦除会出现一些问题,下面是一个例子:
class HasF { public void f() { System.out.println("HasF.f()"); } } public class Manipulator<T> { private T obj; public Manipulator(T obj) { this.obj = obj; } public void manipulate() { obj.f(); //无法编译 找不到符号 f() } public static void main(String[] args) { HasF hasF = new HasF(); Manipulator<HasF> manipulator = new Manipulator<>(hasF); manipulator.manipulate(); } }
上面的 Manipulator 是一个泛型类,内部用一个泛型化的变量 obj,在 manipulate 方法中,调用了 obj 的方法 f(),但是这行代码无法编译。因为类型擦除,编译器不确定 obj 是否有 f() 方法。解决这个问题的方法是给 T 一个边界:
class Manipulator2<T extends HasF> { private T obj; public Manipulator2(T x) { obj = x; } public void manipulate() { obj.f(); } }
现在 T 的类型是
地方都用 HasF 替换。这样编译器就知道 obj 是有方法 f() 的。
但是这样就抵消了泛型带来的好处,上面的类完全可以改成这样:
class Manipulator3 { private HasF obj; public Manipulator3(HasF x) { obj = x; } public void manipulate() { obj.f(); } }
所以泛型只有在比较复杂的类中才体现出作用。但是像
class ReturnGenericType<T extends HasF> { private T obj; public ReturnGenericType(T x) { obj = x; } public T get() { return obj; } }
这里的 get() 方法返回的是泛型参数的准确类型,而不是 HasF。
类型擦除的补偿
类型擦除导致泛型丧失了一些功能,任何在运行期需要知道确切类型的代码都无法工作。比如下面的例子:
public class Erased<T> { private final int SIZE = 100; public static void f(Object arg) { if(arg instanceof T) {} // Error T var = new T(); // Error T[] array = new T[SIZE]; // Error T[] array = (T)new Object[SIZE]; // Unchecked warning } }
通过 new T() 创建对象是不行的,一是由于类型擦除,二是由于编译器不知道 T 是否有默认的构造器。一种解决的办法是传递一个工厂对象并且通过它创建新的实例。
interface FactoryI<T> { T create(); } class Foo2<T> { private T x; public <F extends FactoryI<T>> Foo2(F factory) { x = factory.create(); } // ... } class IntegerFactory implements FactoryI<Integer> { public Integer create() { return new Integer(0); } } class Widget { public static class Factory implements FactoryI<Widget> { public Widget create() { return new Widget(); } } } public class FactoryConstraint { public static void main(String[] args) { new Foo2<Integer>(new IntegerFactory()); new Foo2<Widget>(new Widget.Factory()); } }
另一种解决的方法是利用模板设计模式:
abstract class GenericWithCreate<T> { final T element; GenericWithCreate() { element = create(); } abstract T create(); } class X {} class Creator extends GenericWithCreate<X> { X create() { return new X(); } void f() { System.out.println(element.getClass().getSimpleName()); } } public class CreatorGeneric { public static void main(String[] args) { Creator c = new Creator(); c.f(); } }
具体类型的创建放到了子类继承父类时,在 create 方法中创建实际的类型并返回。
总结
本文介绍了 Java 泛型的使用,以及类型擦除相关的问题。一般情况下泛型的使用比较简单,但是某些情况下,尤其是自己编写使用泛型的类或者方法时要注意类型擦除的问题。接下来会介绍数组与泛型的关系以及通配符的使用。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!