Maison > Java > javaDidacticiel > Résumé des génériques Java (3) - Explication détaillée de l'utilisation des caractères génériques

Résumé des génériques Java (3) - Explication détaillée de l'utilisation des caractères génériques

黄舟
Libérer: 2017-03-22 10:25:10
original
1561 Les gens l'ont consulté

Dans l'utilisation des génériques, il existe une autre chose importante appelée wildcard. Cet article présente l'utilisation des caractères génériques. A une très bonne valeur de référence. Jetons un coup d'œil avec l'éditeur ci-dessous

Introduction

Les deux premiers articles ont présenté l'utilisation de base des génériques, de l'effacement de type et des tableaux génériques. Dans l'utilisation des génériques, il existe une autre chose importante appelée les caractères génériques. Cet article présente l'utilisation des caractères génériques.

Covariance des tableaux

Avant de comprendre les caractères génériques, commençons par comprendre les tableaux. Les tableaux en Java sont covariants, qu'est-ce que cela signifie ? Regardez l'exemple suivant :

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
 public static void main(String[] args) { 
 Fruit[] fruit = new Apple[10];
 fruit[0] = new Apple(); // OK
 fruit[1] = new Jonathan(); // OK
 // Runtime type is Apple[], not Fruit[] or Orange[]:
 try {
  // Compiler allows you to add Fruit:
  fruit[0] = new Fruit(); // ArrayStoreException
 } catch(Exception e) { System.out.println(e); }
 try {
  // Compiler allows you to add Oranges:
  fruit[0] = new Orange(); // ArrayStoreException
 } catch(Exception e) { System.out.println(e); }
 }
} /* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
*///:~
Copier après la connexion

La première ligne de la méthode main crée un tableau Apple et l'assigne à une référence au tableau Fruit. Cela a du sens, Apple est une sous-classe de Fruit, un objet Apple est également un objet Fruit, donc un tableau Apple est également un tableau de Fruit. C'est ce qu'on appelle la covariance des tableaux . Java a conçu les tableaux pour être covariants, ce qui est controversé. Certaines personnes pensent que c'est un défaut.

Bien qu'Apple[] puisse être "upcasté" vers Fruit[], le type réel des éléments du tableau est toujours Apple, et nous ne pouvons mettre qu'Apple ou une sous-classe d'Apple dans le tableau. Dans le code ci-dessus, les objets Fruit et Orange sont placés dans le tableau. Pour le compilateur, cela peut être compilé, mais au moment de l'exécution, la JVM peut savoir que le type réel du tableau est Apple[], donc lorsque d'autres objets sont ajoutés au tableau, une exception sera levée .

L'un des objectifs de la conception générique est de permettre la découverte de telles erreurs d'exécution au moment de la compilation. Jetez un œil à ce qui se passera si une classe conteneur générique est utilisée à la place d'un tableau :

<🎜. >

Le code ci-dessus ne sera pas compilé du tout. En ce qui concerne les génériques, bien qu'Apple soit un sous-type de Fruit, ArrayList n'est pas un sous-type de ArrayList et les génériques ne prennent pas en charge la covariance.
// Compile Error: incompatible types:
ArrayList<Fruit> flist = new ArrayList<Apple>();
Copier après la connexion

Utilisez des caractères génériques

D'après ce qui précède, nous savons que des instructions comme

ne peuvent pas être compilées, même si Integer est un sous-type de Number. Et si nous avions réellement besoin de cette relation de « transformation ascendante » ? C’est là que les jokers entrent en jeu.

List<Number> list = ArrayList<Integer>

Caractère générique de limite supérieureUtiliser des caractères génériques sous la forme pour obtenir une transformation générique vers le haut :

Dans l'exemple ci-dessus, le type de flist est
public class GenericsAndCovariance {
 public static void main(String[] args) {
 // Wildcards allow covariance:
 List<? extends Fruit> flist = new ArrayList<Apple>();
 // Compile Error: can&#39;t add any type of object:
 // flist.add(new Apple());
 // flist.add(new Fruit());
 // flist.add(new Object());
 flist.add(null); // Legal but uninteresting
 // We know that it returns at least Fruit:
 Fruit f = flist.get(0);
 }
}
Copier après la connexion
On peut le lire comme : un type de List Ce type peut être un type qui

hérite de List<? extends Fruit> Fruit. Notez que Cela ne signifie pas que cette liste peut contenir n'importe quel type de fruit. Le caractère générique représente un type spécifique, ce qui signifie « un type spécifique, mais flist ne le précise pas ». Ce n'est pas facile à comprendre. L'explication spécifique de cet exemple est que la référence flist peut pointer vers un certain type de List. Tant que le type hérite de Fruit, il peut s'agir de Fruit ou Apple, comme dans le fichier. exemple Mais pour se transformer vers le haut en flist, flist ne se soucie pas du type spécifique. new ArrayList<Apple>Comme mentionné ci-dessus, le caractère générique

représente une liste d'un type spécifique (Fruit ou sa sous-classe), mais peu importe le type réel. Quoi qu'il en soit, c'est un sous-type de Fruit, et le Fruit est sa limite supérieure. Alors que pouvons-nous faire avec une telle liste ? En fait, si nous ne savons pas quel type contient cette liste, comment pouvons-nous ajouter un objet en toute sécurité ? Dans le code ci-dessus, ajouter n'importe quel objet à flist, qu'il s'agisse d'un objet Apple ou Orange ou même Fruit, le compilateur ne le permettra pas, la seule chose qui peut être ajoutée est null. Donc, si nous effectuons une conversion générique vers le haut (

), alors nous perdons la possibilité d'ajouter n'importe quel objet à cette liste, même Object. List<? extends Fruit>List<? extends Fruit> flist = new ArrayList<Apple>()Par contre, si vous appelez une méthode qui renvoie un Fruit, c'est sûr. Parce que nous savons que dans cette liste, quel que soit son type réel, il peut certainement être converti en Fruit, le compilateur permet donc le retour de Fruit.

Après avoir compris les fonctions et les limitations des caractères génériques, il semble que nous ne puissions appeler aucune méthode acceptant des paramètres. En fait, pas vraiment, regardez l'exemple suivant :

Dans l'exemple ci-dessus, le type de flist est
public class CompilerIntelligence {
 public static void main(String[] args) {
 List<? extends Fruit> flist =
 Arrays.asList(new Apple());
 Apple a = (Apple)flist.get(0); // No warning
 flist.contains(new Apple()); // Argument is ‘Object&#39;
 flist.indexOf(new Apple()); // Argument is ‘Object&#39;
 //flist.add(new Apple()); 无法编译
 }
}
Copier après la connexion
, et le paramètre générique utilise des caractères génériques restreints, donc on perd la possibilité d'ajouter Exemple de tout type d'objet, la dernière ligne de code ne sera pas compilée.

但是 flist 却可以调用 contains 和 indexOf 方法,它们都接受了一个 Apple 对象做参数。如果查看 ArrayList 的源代码,可以发现 add() 接受一个泛型类型作为参数,但是 contains 和 indexOf 接受一个 Object 类型的参数,下面是它们的方法签名:

public boolean add(E e)
public boolean contains(Object o)
public int indexOf(Object o)
Copier après la connexion

所以如果我们指定泛型参数为 <? extends Fruit> 时,add() 方法的参数变为 ? extends Fruit,编译器无法判断这个参数接受的到底是 Fruit 的哪种类型,所以它不会接受任何类型。

然而,contains 和 indexOf 的类型是 Object,并没有涉及到通配符,所以编译器允许调用这两个方法。这意味着一切取决于泛型类的编写者来决定那些调用是 “安全” 的,并且用 Object 作为这些安全方法的参数。如果某些方法不允许类型参数是通配符时的调用,这些方法的参数应该用类型参数,比如 add(E e)。

当我们自己编写泛型类时,上面介绍的就有用了。下面编写一个 Holder 类:

public class Holder<T> {
 private T value;
 public Holder() {}
 public Holder(T val) { value = val; }
 public void set(T val) { value = val; }
 public T get() { return value; }
 public boolean equals(Object obj) {
 return value.equals(obj);
 }
 public static void main(String[] args) {
 Holder<Apple> Apple = new Holder<Apple>(new Apple());
 Apple d = Apple.get();
 Apple.set(d);
 // Holder<Fruit> Fruit = Apple; // Cannot upcast
 Holder<? extends Fruit> fruit = Apple; // OK
 Fruit p = fruit.get();
 d = (Apple)fruit.get(); // Returns ‘Object&#39;
 try {
  Orange c = (Orange)fruit.get(); // No warning
 } catch(Exception e) { System.out.println(e); }
 // fruit.set(new Apple()); // Cannot call set()
 // fruit.set(new Fruit()); // Cannot call set()
 System.out.println(fruit.equals(d)); // OK
 }
} /* Output: (Sample)
java.lang.ClassCastException: Apple cannot be cast to Orange
true
*///:~
Copier après la connexion

在 Holer 类中,set() 方法接受类型参数 T 的对象作为参数,get() 返回一个 T 类型,而 equals() 接受一个 Object 作为参数。fruit 的类型是 Holder<? extends Fruit>,所以set()方法不会接受任何对象的添加,但是 equals() 可以正常工作。

下边界限定通配符

通配符的另一个方向是 “超类型的通配符“: ? super TT是类型参数的下界。使用这种形式的通配符,我们就可以 ”传递对象” 了。还是用例子解释:

public class SuperTypeWildcards {
 static void writeTo(List<? super Apple> apples) {
 apples.add(new Apple());
 apples.add(new Jonathan());
 // apples.add(new Fruit()); // Error
 }
}
Copier après la connexion

writeTo 方法的参数 apples 的类型是 List<? super Apple> 它表示某种类型的 List,这个类型是 Apple 的基类型。也就是说,我们不知道实际类型是什么,但是这个类型肯定是 Apple 的父类型。因此,我们可以知道向这个 List 添加一个 Apple 或者其子类型的对象是安全的,这些对象都可以向上转型为 Apple。但是我们不知道加入 Fruit 对象是否安全,因为那样会使得这个 List 添加跟 Apple 无关的类型。

在了解了子类型边界和超类型边界之后,我们就可以知道如何向泛型类型中 “写入” ( 传递对象给方法参数) 以及如何从泛型类型中 “读取” ( 从方法中返回对象 )。下面是一个例子:

public class Collections { 
 public static <T> void copy(List<? super T> dest, List<? extends T> src) 
 {
 for (int i=0; i<src.size(); i++) 
 dest.set(i,src.get(i)); 
 } 
}
Copier après la connexion

src 是原始数据的 List,因为要从这里面读取数据,所以用了上边界限定通配符:,取出的元素转型为 T。dest 是要写入的目标 List,所以用了下边界限定通配符:,可以写入的元素类型是 T 及其子类型。

无边界通配符

还有一种通配符是无边界通配符,它的使用形式是一个单独的问号:List,也就是没有任何限定。不做任何限制,跟不用类型参数的 List 有什么区别呢?

List<?> list表示 list 是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以,因为并不知道实际是哪种类型,所以不能添加任何类型,这是不安全的。而单独的 List list ,也就是没有传入泛型参数,表示这个 list 持有的元素的类型是 Object,因此可以添加任何类型的对象,只不过编译器会有警告信息。

总结

通配符的使用可以对泛型参数做出某些限制,使代码更安全,对于上边界和下边界限定的通配符总结如下:

  • 使用 List<? extends C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素类型是 C 的子类型 ( 包含 C 本身)的一种。

  • 使用 List<? super C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素就类型是 C 的超类型 ( 包含 C 本身 ) 的一种。

大多数情况下泛型的使用比较简单,但是如果自己编写支持泛型的代码需要对泛型有深入的了解。这几篇文章介绍了泛型的基本用法、类型擦除、泛型数组以及通配符的使用,涵盖了最常用的要点,泛型的总结就写到这里。

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!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal