Maison > Java > javaDidacticiel > Explication détaillée des connaissances liées aux génériques Java (avec code)

Explication détaillée des connaissances liées aux génériques Java (avec code)

不言
Libérer: 2019-02-22 13:35:25
avant
2335 Les gens l'ont consulté

Cet article vous apporte des connaissances détaillées sur les génériques Java (avec code). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

Je pense que tout le monde est très familier avec l'utilisation des génériques, mais ils peuvent ne pas être très clairs sur des détails tels que l'effacement de type et l'expansion des limites, donc cet article se concentrera sur leur explication en plus de la compréhension des génériques ; peut en fait On voit que la logique de génération d'une fonctionnalité de langage est également très utile pour notre développement quotidien

1 Pourquoi les génériques apparaissent

Premier de. tous, les génériques Ce n'est pas une fonctionnalité du langage Java, c'est une fonctionnalité qui n'était pas prise en charge jusqu'au JDK1.5 (les différences spécifiques seront discutées plus tard), alors qu'a-t-on fait avant l'apparition des génériques ?

List list = new ArrayList();
list.add("123");
String s = (String) list.get(0);
Copier après la connexion

Comme le montre le code ci-dessus, nous devons nous souvenir de ce que nous avons mis dans la collection, puis le forcer lorsque nous le retirons, cela reporte également cette erreur de conversion de type au moment de l'exécution. , c'est gênant mais pas sûr, donc des génériques sont apparus

Scénarios d'utilisation : classes génériques, interfaces génériques, méthodes génériques

public class Test<T>
public interface Test<T>
public <T> void test(T t)
Copier après la connexion

2. Quel genre de problèmes les génériques apporteront-ils ?

Comme mentionné ci-dessus, les génériques ne sont pas une fonctionnalité que Java possède depuis le début, donc lorsque vous souhaitez ajouter des génériques plus tard, ils doivent être compatibles avec les versions précédentes. La solution de compromis proposée par Sun est Effacement de type ; ce qui signifie que les informations génériques n'existent que lors de la compilation et que toutes les informations génériques sont effacées pendant l'exécution. Sauf qu'il n'y a rien d'autre ; 🎜>// Print :

class java.util.ArrayList
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass());
System.out.println(list2.getClass() == list1.getClass());
Copier après la connexion
true


Vous pouvez voir
et

En fait, ils sont tous pareils au moment de l'exécution, ils sont tous

 ; donc lorsque vous utilisez des génériques, vous devez vous rappeler que List<String> n'a aucune information générique au moment de l'exécution et qu'il est impossible d'obtenir des informations sur les types de paramètres List<String> donc toute opération nécessitant l'obtention du type d'exécution n'est pas prise en charge ; par les génériques ! class java.util.ArrayList1. Le paramètre de type

ne peut pas être instancié avec un type de base car l'effacement du type l'effacera jusqu'à sa limite supérieure, qui est

 ; La classe parent directe des 8 types de base de Java est
new ArrayList<int>();      // error
new ArrayList<Integer>();  // correct
Copier après la connexion
, donc les types de base ne peuvent pas utiliser les types de base pour instancier les paramètres de type, mais doivent utiliser des classes wrapper de type de base

ObjectNumber
2. Ne peut pas être ; utilisé pour la vérification du type d'exécution

mais peut être utilisé pour la compensation

t instanceof T             // error
t instanceof List<T>       // error
t instanceof List<String>  // error
t instanceof List          // correct
Copier après la connexion

clazz.isInstance(); 3. Impossible de créer des instances de type

Vous pouvez utilisez également

pour compenser

T t = new T();  // error
Copier après la connexion

clazz.newInstance();4. Il ne peut pas être statique

car les variables statiques sont partagées dans les classes et les génériques Le type n'est pas défini, donc les génériques ne peuvent pas être statiques ; mais lorsqu'ils sont non statiques, le compilateur peut déduire ce que

est basé sur le contexte, par exemple :

private static T t;                  // error
private T t;                         // correct
private static List<T> list;         // error
private static List<?> list;         // correct
private static List<String> list;    // correct

// e.g.
class Test<T> {
  private T t;
  public void set(T arg) { t = arg; }
  public T get() { return t; }
}
Copier après la connexion

D'après le code ci-dessus, cela peut être très Voir clairement la dérivation des types non statiques par le compilateur ;T

De plus, la raison pour laquelle List<?> et List<String> 🎜>
Test l = new Test();
System.out.println(l.get());
l.set("123");
System.out.println(l.get());

// javap -v 反编译
12: invokevirtual #15         // Method JDK/Test14_genericity$Test.get:()Ljava/lang/Object;
15: invokevirtual #16         // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: aload_1
19: ldc       #17             // String 123
21: invokevirtual #18         // Method JDK/Test14_genericity$Test.set:(Ljava/lang/Object;)V
24: getstatic   #6            // Field java/lang/System.out:Ljava/io/PrintStream;

// ---------------------------
Test l = new Test();
System.out.println(l.get());
l.set("123");
System.out.println(l.get());

// javap -v 反编译
12: invokevirtual #15         // Method JDK/Test14_genericity$Test.get:()Ljava/lang/Object;
15: invokevirtual #16         // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: aload_1
19: bipush    123
21: invokestatic  #17         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
Copier après la connexion

5. Les instances de classes génériques ne peuvent pas être lancées ou capturées


car elles sont nécessaires pour détecter les exceptions. la relation d'héritage des exceptions, de sorte que les instances de classes génériques ne peuvent pas être lancées ou capturées

6 La surcharge en tant que paramètre n'est pas autorisée
catch (T t)                        // error
class Test<T> extends Throwable    // error
Copier après la connexion


Parce que les informations génériques sont effacées ; pendant l'exécution, les signatures des deux méthodes surchargées sont exactement les mêmes ;

7. Impossible de créer un tableau générique
void test(List<Integer> list)
void test(List<String> list)
Copier après la connexion

Pour un point, je pense que c'est le plus important. pour les tableaux, veuillez vous référer à la section relative aux tableaux ;

La principale raison pour laquelle les tableaux génériques ne peuvent pas être créés :

List<String>[] lists = new ArrayList<String>[10];             // error
List<String>[] lists1 = (List<String>[]) new ArrayList[10];   // correct
Copier après la connexion
Les tableaux sont des variables coopératives, tandis que les tableaux génériques restent inchangés. ;

  • Les informations

    du tableau sont créées dynamiquement au moment de l'exécution, mais les informations de classe générique ne peuvent pas être obtenues au moment de l'exécution ;

  • D'après l'explication ci-dessus, nous pouvons voir que ce qu'on appelle la compensation d'effacement ou la correction après effacement, l'idée générale est d'utiliser des méthodes supplémentaires pour informer les informations de type d'exécution, qui peuvent être enregistrées localement. Les variables peuvent également être le type exact de spécifié paramètres (Class);

3. Expansion des limites Array.newInstance(Class<?> componentType, int length)

Pour des raisons de sécurité, les génériques Java ne sont pas variables (pour éviter les erreurs de conversion de type lors de la récupération data);

Ainsi, lors de l'utilisation de la classe collection, il est un peu gênant de forcer chaque collection à spécifier le type exact. Par exemple, je souhaite spécifier une collection Store A et des sous-classes de A ; dans ce cas, extends,super,? sont introduits pour étendre et gérer les limites des génériques

 ;

1. 无界通配符 <?>

通配符主要用于泛型的使用场景(泛型一般有“声明”和“使用”两种场景);
通常情况下 <?> 和原生类型大致相同,就像 List 和 List<?> 的表现大部分都是一样的;但是要注意他们其实是有本质去别的,<?> 代表了某一特定的类型,但是编译器不知道这种类型是什么;而原生的表示可以是任何 Object,其中并没有类型限制;

List<?> list = new ArrayList<String>();    // correct
list.add("34");                            // error
String s = list.get(0);                    // error
Object o = list.get(0);                    // correct

boolean add(E e);
Copier après la connexion

上面的代码很明确的反应了这一点(<?> 代表了某一特定的类型,但是编译器不知道这种类型是什么),

  • 因为编译器不知道这种类型是什么,所以在添加元素的时候,当然也就不能确认添加的这个类型是否正确;当使用<?>的时候,代码中的 add(E e) 方法,此时的 E 会被替换为 <?>实际上编译器为了安全起见,会直接拒绝参数列表中涉及通配符的方法调用;就算这个方法没有向集合中添加元素,也会被直接拒绝;

  • List<?> 取出元素的时候,同样因为不知道这个特定的类型是什么,所以只能将取出的元素放在Object中;或者在取出后强转;

2. 上界 <extends>

extends,主要用于确定泛型的上界;

<T extends Test>                             // 泛型声明
<T extends Test & interface1 & interface2>   // 声明泛型是可以确定多个上界
<? extends T>                                // 泛型使用时
Copier après la connexion

界定的范围如图所示:

Explication détaillée des connaissances liées aux génériques Java (avec code)

应当注意的是当extends用于参数类型限定时:

List<? extends List> list = new ArrayList<ArrayList>();  // correct
list.add(new ArrayList());                               // error
List l = list.get(0);                                    // correct
ArrayList l = list.get(0);                               // error
Copier après la connexion

上面的分析同无界通配符类似,只是 List l = list.get(0); 是正确的,是因为 <? extends List> 界定了放入的元素一定是 List 或者 list 的子类,所以取出的元素能放入 List 中,但是不能放入 ArrayList 中;

3. 下界 <super>

super,主要用于确定泛型的下界;如图所示:

Explication détaillée des connaissances liées aux génériques Java (avec code)

List<? super HashMap> list = new ArrayList<>();   // correct
LinkedHashMap m = new LinkedHashMap();            // correct
HashMap m1 = m;                                   // correct
Map m2 = m;                                       // correct
list.add(m);                                      // correct
list.add(m1);                                     // correct
list.add(m2);                                     // error

Map mm = list.get(0);                             // error
LinkedHashMap mm1 = list.get(0);                  // error
Copier après la connexion

根据图中的范围对照代码,就能很快发现Map在List的范围之外;而编辑器为了安全泛型下界集合取出的元素只能放在 Object里面;

4. PECS 原则

PECS原则是对上界和下界使用的归纳,即producer-extends, consumer-super;结合上面的两幅图,表示:

  • extends只能读,相当于生产者,向外产出;

  • super只能写,相当于消费者,只能接收消费;

  • 同时边界不能同时规定上界和下界,正如图所示,他们的范围其实是一样的,只是开口不一样;

5. 自限定类型

对于上面讲的泛型边界拓展,有一个很特别的用法,

class Test<T extends Test<T>> {}
public <T extends Comparable<T>> T max(List<T> list) {}
Copier après la connexion

自限定类型可以通俗的解释,就是用自己限定自己,即自和自身相同的类进行某操作;如上面的 max 方法,就表示可以和自身进行比较的类型;

那么如果想要表达只要是同一祖先就能相互比较呢?

public <T extends Comparable<? super>> T max(List<? extends T> list) {}
Copier après la connexion

>:表明只要是同一祖先就能相互比较, extends T>表明集合中装的都是同一祖先的元素;(出至《Effective Java》第 28 条)

总结

  • 对于泛型的时候首先要很清楚的知道,在运行时没有任何泛型的信息,全部都被擦除掉了;

  • 需要知道 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!

Étiquettes associées:
source:cnblogs.com
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