Table des matières
Concepts de base
1. Développement
2. Terminologie
3. Explorer
Qualification de type
1. Ne définissez pas de limites pour les paramètres de type <. 🎜>
2. Définissez des limites sur les paramètres de type
Effacement de type
1. Type brut
2. Type du paramètre de type
3. Vérification de type
4.类型擦除与多态的冲突
注意事项
Maison Java javaDidacticiel 11.Bases de Java – Génériques

11.Bases de Java – Génériques

Feb 27, 2017 am 10:43 AM

Concepts de base

L'essence des génériques est l'application du Type paramétré, c'est-à-dire , le type de données sur lequel l'opération est effectuée est spécifié en tant que paramètre et le type spécifique est spécifié lors de son utilisation.

Ce type de paramètre peut être utilisé dans la création de classes, d'interfaces et de méthodes, appelées respectivement classes génériques, interfaces génériques et méthodes génériques .


1. Développement

Avant JDK 1.5, seul Object est la classe parent de tous les types et du type casting La combinaison de ceux-ci les caractéristiques peuvent réaliser une généralisation de type.

Par conséquent, lors de la compilation, le compilateur ne peut pas vérifier si le cast de cet Object est réussi, ce qui peut provoquer une ClassCastException (exception de cast).

Regardons un exemple pour comprendre le rôle des génériques :

  • Quand les génériques ne sont pas utilisés (avant la version 1.5)

ArrayList arrayList = new ArrayList();
arrayList.add(100);
arrayList.add("abc");//因为不知道取出来的值的类型,类型转换的时候容易出错  String str = (String) arrayList.get(0);
Copier après la connexion
  • Lors de l'utilisation de génériques (après 1.5)

ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add("abc");//因为限定了类型,所以不能添加整形,编译器会提示出错arrayList.add(100);
Copier après la connexion

2. Terminologie

// 以 ArrayList<E>,ArrayList<Integer> 为例:ArrayList<E>:泛型类型

E:类型变量(或者类型参数)

ArrayList<Integer> :参数化的类型

Integer:类型参数的实例(或实际类型参数)

ArrayList :原始类型
Copier après la connexion

3. Explorer

Classes génériques

class Demo<T> {    private T value;

    Demo(T value) {        this.value = value;
    }    public T getValue() {        return value;
    }    public void setValue(T value) {        this.value = value;
    }
}public class Test {
    public static void main(String[] args) {
        Demo<String> demo = new Demo("abc");
        demo.setValue("cba");
        System.out.println(demo.getValue()); // cba
    }
}
Copier après la connexion

Interfaces génériques

interface Demo<K, V> {    void print(K k, V v);
}

class DemoImpl implements Demo<String, Integer> {    @Override
    public void print(String k, Integer v) {
        System.out.println(k + "-" + v);
    }
}public class Test {
    public static void main(String[] args) {
        Demo<String, Integer> demo = new DemoImpl();
        demo.print("abc", 100);
    }
}
Copier après la connexion

Méthodes génériques

public class Test {
    public static void main(String[] args) {        int num = get("abc", 100);
        System.out.println(num);
    }    // 关键 --> 多了 <K, V> ,可以理解为声明此方法为泛型方法
    public static <K, V> V get(K k, V v) {        if (k != null) {            return v;
        }        return null;
    }
}
Copier après la connexion

Qualification de type

La qualification de type peut être utilisée dans les classes génériques, les interfaces génériques et les méthodes génériques, mais faites attention aux points suivants :

  • Que la qualification soit une classe ou une interface, le mot-clé étend

  • peut être donné à l'aide du symbole & Qualifications multiples

  • Si la qualification comporte à la fois des interfaces et des classes, alors il ne doit y avoir qu'une seule classe et elle doit être placée en premier. Par exemple :

public static <T extends Comparable&Serializable> T get(T t1,T t2)
Copier après la connexion

Analysons le rôle de la qualification de type...


1. Ne définissez pas de limites pour les paramètres de type <. 🎜>

Observez le code suivant, une erreur de compilation se produira lorsque les paramètres de type ne sont pas qualifiés de type. La raison est la suivante :

  • Parce qu'avant la compilation, le compilateur ne peut pas confirmer de quel type est le type générique (T)

  • il est donc par défaut à T Est un type primitif (Objet).

  • Vous ne pouvez donc appeler que la méthode Object, mais pas la méthode compareTo.

public static <T> T get(T t1,T t2) {    //编译错误
    if(t1.compareTo(t2)>=0);    return t1;
}
Copier après la connexion

2. Définissez des limites sur les paramètres de type

Après avoir défini des limites sur le paramètre de type T, compilez l'erreur. se produit. Parce qu'à l'heure actuelle, le compilateur utilise par défaut le type d'origine de T comme comparable.

public static <T extends Comparable> T get(T t1,T t2) {    if(t1.compareTo(t2)>=0);    return t1;
}
Copier après la connexion

Effacement de type

  • Les génériques en Java sont essentiellement implémentés au niveau du compilateur .

  • Les informations de type dans les génériques ne sont pas incluses dans le bytecode Java généré.

  • Les paramètres de type ajoutés lors de l'utilisation de génériques seront supprimés par le compilateur lors de la compilation. Ce processus est appelé effacement de type.


Regardez l'exemple suivant :

public class Test {
    public static void main(String[] args) {
        ArrayList<String> arrayList1 =new ArrayList<String>();
        ArrayList<Integer> arrayList2 = new ArrayList<Integer>();        // true
        System.out.println(arrayList1.getClass() == arrayList2.getClass());
    }
}
Copier après la connexion
Observez le code, deux tableaux ArrayList sont définis ici :

  • L'un est le type générique ArrayList, qui ne peut stocker que des chaînes, et l'autre est le type générique ArrayList, qui ne peut stocker que des entiers.

  • En comparant leurs objets de classe, le résultat s'avère vrai.

  • Expliquez que les types génériques String et Integer sont effacés lors du processus de compilation, ne laissant que le type d'origine (c'est-à-dire Objet).


Regardons un autre exemple :

public class Test {
    public static void main(String[] args) throws Exception{
        ArrayList<String> arrayList =new ArrayList<String>();
        arrayList.add("abc");
        arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, 100);         for (int i=0;i<arrayList.size();i++) {  
                System.out.println(arrayList.get(i));  
            }  
    }
}
Copier après la connexion
Observez le code, ici un objet de type générique ArrayList est instancié en tant qu'Integer

  • Si vous appelez directement la méthode add, seules les données entières peuvent être stockées.

  • Utilisez la réflexion pour appeler la méthode add, mais vous pouvez stocker des chaînes.

  • Explication Les instances génériques Integer sont effacées après compilation, ne laissant que le type d'origine.


1. Type brut

  • Le type brut (raw type) efface les informations génériques, et enfin le type réel de la variable type dans le

    bytecode .

  • Tout paramètre de type générique a une variable primitive correspondante.

  • Une fois qu'une variable de type est écrasée, elle est remplacée par son type qualifié (les variables non qualifiées sont des objets).

// 此时 T 是一个无限定类型,所以原始类型就是 Objectclass Pair<T> { } 

// 类型变量有限定,原始类型就用第一个边界的类型变量来替换,即Comparableclass Pair<T extends Comparable& Serializable> { }  

// 此时原始类型为 Serializable,编译器在必要的时要向 Comparable 插入强制类型转换
// 为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界限定列表的末尾class Pair<T extends Serializable&Comparable>
Copier après la connexion

2. Type du paramètre de type

Dans l'exemple suivant, le paramètre de type fait référence à T, le type de T C'est le type de ce qu'on appelle le [paramètre de type].

En observant le code, nous pouvons tirer les conclusions suivantes :

  • Ne spécifiez pas le type de [paramètre de type T]. le type original prend la même classe parent Le niveau minimum de

  • Lors de la spécification du type de [paramètre de type T], le type original ne peut être que le type spécifié ou une sous-classe du type

public class Test {    // 定义泛型方法
    public static <T> T add(T x, T y) {        return y;
    }    public static void main(String[] args) {        // 1.不指定泛型

        // 两个参数都是 Integer,所以 T 为 Integer 类型
        int i = Test.add(1, 2); 

        // 两个参数分别是 Integer,Float,取同一父类的最小级,T 为 Number 类型
        Number f = Test.add(1, 1.2);        // T 为 Object
        Object o = Test.add(1, "asd");        // 2.指定泛型

        // 指定了Integer,所以只能为 Integer 类型或者其子类
        int a = Test.<Integer> add(1, 2);        //编译错误,指定了 Integer,不能为Float
        int b=Test.<Integer>add(1, 2.2); 

         // 指定为Number,所以可以为 Integer,Float
        Number c = Test.<Number> add(1, 2.2);
    }

}
Copier après la connexion

3. Vérification de type

La vérification de type des génériques est pour la référence, pas pour l'objet référencé lui-même.

Dans l'exemple suivant, list est un objet de référence, donc la vérification de type s'effectue par rapport à lui.

// 没有进行类型检查,等价于 ArrayList list = new ArrayLis()ArrayList list = new ArrayList<String>();
list.add(100);
list.add("hello");// 进行编译检查,等价于 ArrayList<String> list = new ArrayList<String>();ArrayList<String> list = new ArrayList();
list.add("hello");
list.add(100);  // 编译错误
Copier après la connexion

4.类型擦除与多态的冲突

来看下面的例子,这里定义了一个泛型类 Parent,一个实现它的子类 Son,并在子类中重写了父类的方法。

class Parent<T> {    private T value;    public T getValue() {        return value;
    }    public void setValue(T value) {        this.value = value;
    }
}

class Son extends Parent<String>{    @Override
    public void setValue(String value) {         super.setValue(value);
    }    @Override
    public String getValue(){        return super.getValue();}
    }
Copier après la connexion

在上面提到过泛型的类型参数在编译时会被类型擦除,因此编译后的 Parent 类如下:

class Parent {    private Object value;    public Object getValue() {        return value;
    }    public void setValue(Object value) {        this.value = value;
    }
}
Copier après la connexion

此时对比 Parent 与 Son 的 getValue/setValue 方法,发现方法的参数类型已经改变,从 Object -> String,这也意味着不是重写(overrride) 而是重载(overload)。

然而调用 Son 的 setValue 方法, 发现添加 Object 对象时编译错误。说明也不是重载。

public class Test {
    public static void main(String[] args) {
        Son son = new Son();
        son.setValue("hello");        // 关键 -->编译错误
        son.setValue(new Object());
    }
}
Copier après la connexion

那么问题来了,通过上面的分析?Son 中定义的方法到底是重写还是重载?答案是:重写。这里 JVM 采用了桥方法(Brige)来解决类型擦除和多态引起的冲突。

我们对 Son 进行反编译(”Javap -c 类名.class”),得到如下内容:

Compiled from "Test.java"class Son extends Parent<java.lang.String> {
  Son();
    Code:       0: aload_0       
       1: invokespecial #8                  // Method Parent."<init>":()V
       4: return        

  public void setValue(java.lang.String);
    Code:       0: aload_0       
       1: aload_1       
       2: invokespecial #16                 // Method Parent.setValue:(Ljava/lang/Object;)V
       5: return        

  public java.lang.String getValue();
    Code:       0: aload_0       
       1: invokespecial #23                 // Method Parent.getValue:()Ljava/lang/Object;
       4: checkcast     #26                 // class java/lang/String
       7: areturn       

  public java.lang.Object getValue();
    Code:       0: aload_0       
       1: invokevirtual #28                 // Method getValue:()Ljava/lang/String;
       4: areturn       

  public void setValue(java.lang.Object);
    Code:       0: aload_0       
       1: aload_1       
       2: checkcast     #26                 // class java/lang/String
       5: invokevirtual #30                 // Method setValue:(Ljava/lang/String;)V
       8: return        }
Copier après la connexion

发现这里共有 4 个 setValue/getValue 方法,除了 Son 表面上重写的 String 类型,编译器又自己生成了 Object 类型的方法,也称为桥方法。结果就是,编译器通过桥方法真正实现了重写,只是在访问时又去调用表面的定义的方法。


注意事项

  • 不能用基本类型实例化类型参数,可以用对应的包装类来实例化类型参数

// 编译错误ArrayList<int> list = new ArrayList<int>();// 正确写法ArrayList<Integer> list = new ArrayList<Integer>();
Copier après la connexion
  • 参数化类型的数组不合法

Demo<T >{
}public class Test {
    public static void main(String[] args) {        // 编译错误 --> 类型擦除导致数组变成 Object [],因此没有意义
        Demo<String>[ ]  demo =new Demo[10];
    }
}
Copier après la connexion
  • 不能实例化类型变量

// 编译错误,需要类型参数需要确定类型Demo<T> demo = new Demo<T>
Copier après la connexion
  • 泛型类的静态上下文中不能使用类型变量

public class Demo<T> {
    public static T name;    public static T getName() {
        ...
    }
}
Copier après la connexion
  • 不能抛出也不能捕获泛型类的对象

//异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。会导致这里捕获的类型一致try{  
}catch(Problem<Integer> e1){  
    //do Something... }catch(Problem<Number> e2){  
    // do Something ...}
Copier après la connexion

 以上就是11.Java 基础 - 泛型的内容,更多相关内容请关注PHP中文网(www.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

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Nombre parfait en Java Nombre parfait en Java Aug 30, 2024 pm 04:28 PM

Guide du nombre parfait en Java. Nous discutons ici de la définition, comment vérifier le nombre parfait en Java ?, des exemples d'implémentation de code.

Weka en Java Weka en Java Aug 30, 2024 pm 04:28 PM

Guide de Weka en Java. Nous discutons ici de l'introduction, de la façon d'utiliser Weka Java, du type de plate-forme et des avantages avec des exemples.

Numéro de Smith en Java Numéro de Smith en Java Aug 30, 2024 pm 04:28 PM

Guide du nombre de Smith en Java. Nous discutons ici de la définition, comment vérifier le numéro Smith en Java ? exemple avec implémentation de code.

Questions d'entretien chez Java Spring Questions d'entretien chez Java Spring Aug 30, 2024 pm 04:29 PM

Dans cet article, nous avons conservé les questions d'entretien Java Spring les plus posées avec leurs réponses détaillées. Pour que vous puissiez réussir l'interview.

Break or Return of Java 8 Stream Forach? Break or Return of Java 8 Stream Forach? Feb 07, 2025 pm 12:09 PM

Java 8 présente l'API Stream, fournissant un moyen puissant et expressif de traiter les collections de données. Cependant, une question courante lors de l'utilisation du flux est: comment se casser ou revenir d'une opération FOREAK? Les boucles traditionnelles permettent une interruption ou un retour précoce, mais la méthode Foreach de Stream ne prend pas directement en charge cette méthode. Cet article expliquera les raisons et explorera des méthodes alternatives pour la mise en œuvre de terminaison prématurée dans les systèmes de traitement de flux. Lire plus approfondie: Améliorations de l'API Java Stream Comprendre le flux Forach La méthode foreach est une opération terminale qui effectue une opération sur chaque élément du flux. Son intention de conception est

Horodatage à ce jour en Java Horodatage à ce jour en Java Aug 30, 2024 pm 04:28 PM

Guide de TimeStamp to Date en Java. Ici, nous discutons également de l'introduction et de la façon de convertir l'horodatage en date en Java avec des exemples.

Programme Java pour trouver le volume de la capsule Programme Java pour trouver le volume de la capsule Feb 07, 2025 am 11:37 AM

Les capsules sont des figures géométriques tridimensionnelles, composées d'un cylindre et d'un hémisphère aux deux extrémités. Le volume de la capsule peut être calculé en ajoutant le volume du cylindre et le volume de l'hémisphère aux deux extrémités. Ce tutoriel discutera de la façon de calculer le volume d'une capsule donnée en Java en utilisant différentes méthodes. Formule de volume de capsule La formule du volume de la capsule est la suivante: Volume de capsule = volume cylindrique volume de deux hémisphères volume dans, R: Le rayon de l'hémisphère. H: La hauteur du cylindre (à l'exclusion de l'hémisphère). Exemple 1 entrer Rayon = 5 unités Hauteur = 10 unités Sortir Volume = 1570,8 unités cubes expliquer Calculer le volume à l'aide de la formule: Volume = π × r2 × h (4

Comment exécuter votre première application Spring Boot dans Spring Tool Suite? Comment exécuter votre première application Spring Boot dans Spring Tool Suite? Feb 07, 2025 pm 12:11 PM

Spring Boot simplifie la création d'applications Java robustes, évolutives et prêtes à la production, révolutionnant le développement de Java. Son approche "Convention sur la configuration", inhérente à l'écosystème de ressort, minimise la configuration manuelle, allo

See all articles