Table des matières
1 Méthode générique
1.1 Inférence de paramètre de type
.
existe pour le paramètre de type T Lorsque vous
ce modèle, le compilateur C++ ? va vérifier, donc pour le moment
2.3 擦除
2.4 擦除的问题
2.5 边界处的动作
3 擦除的补偿(Compensating for erasure)
4 边界(bound)
5 通配符(wildcards)
5.1 编译器有多聪明
5.2 逆变(Contravariance)
5.3 无界通配符(Unbounded wildcards)
5.4 捕获转换
6 问题
7 总结
Maison Java javaDidacticiel Cours d'apprentissage sur les pensées de programmation Java (3) Chapitre 15 - Génériques

Cours d'apprentissage sur les pensées de programmation Java (3) Chapitre 15 - Génériques

Aug 09, 2018 pm 02:39 PM
java

Le concept de génériques (génériques) est l'un des changements majeurs de Java SE5. Les génériques implémentent le concept de types paramétrés (types paramétrés), permettant d'appliquer du code à plusieurs types. Le terme « générique » signifie : « applicable à de très nombreux types ».

1 Méthode générique

  Cela n'a rien à voir avec le fait que la classe dans laquelle se trouve la méthode générique soit générique, c'est-à-dire que la classe dans laquelle se trouve la méthode générique peut être un type de classe générique ou non.

  • Les méthodes génériques permettent à la méthode de changer indépendamment de la classe.

  • Un principe directeur de base : chaque fois que vous le pouvez, vous devriez essayer d'utiliser des méthodes génériques. Autrement dit, si l’utilisation d’une méthode générique peut remplacer la généralisation de la classe entière, vous ne devez utiliser que la méthode générique car elle rend les choses plus claires.

  • Pour une méthode static, les paramètres de type de la classe générique ne sont pas accessibles, donc si la méthode static doit utiliser des capacités génériques, elle doit être rendue méthode générique.

  • Pour définir une méthode générique, il suffit de mettre la liste des paramètres génériques avant la valeur de retour.

1.1 Inférence de paramètre de type

  Lors de l'utilisation de méthodes génériques, il n'est généralement pas nécessaire de spécifier le type de paramètre, car le compilateur découvrira le type spécifique pour nous. C’est ce qu’on appelle l’inférence d’argument de type.

L'inférence de type n'est valable que pour les opérations d'affectation
    .
  • Si le résultat d'un appel de méthode générique est transmis en tant que

    paramètre
  • à une autre méthode, le compilateur
  • n'effectuera pas d'inférence de type

    . 1.1.1 Spécification de type explicite

  •   Insérez des crochets angulaires entre l'opérateur point et le nom de la méthode, puis placez le type entre les crochets angulaires, c'est-à-dire
Spécification de type explicite

.

2 Le mystère de l'effacement

Selon la documentation du JDK,

"renverra un

Class.getTypeParameters() Tableau de objets représentant des paramètres de type avec une déclaration générique..." Cela semble impliquer que vous pouvez trouver des informations sur les types de paramètres, mais, comme vous pouvez le voir sur la sortie, tout ce que vous pouvez trouver est utilisé comme Paramètre L'identifiant TypeVariablede l'espace réservé n'est pas une information utile. La dure réalité est donc la suivante :

À l'intérieur du code générique, il n'y a aucun moyen d'obtenir des informations sur le type du paramètre générique

. Ainsi, vous pouvez connaître des choses comme les identifiants de paramètres génériques et les

limites de type génériques

- mais vous ne pouvez pas savoir comment créer un type réel spécifique. paramètres de l'instance . …, c'est le problème le plus fondamental auquel vous devez faire face lorsque vous travaillez avec des génériques Java. Les génériques Java sont implémentés en utilisant erasure, ce qui signifie que lorsque vous utilisez des génériques, toute information de type spécifique est effacée,

la seule chose que vous savez est que vous utilisez un objet

. Ainsi, et sont en fait List<String> du même type au moment de l'exécution. Les deux formulaires sont effacés vers leurs "List<Integer>types natifs, c'est-à-dire .2.1 Méthode C++ List 2.1.1 L'exemple de modèle C++ suivant :

Comment sait-il que la méthode

existe pour le paramètre de type T Lorsque vous

instanciez
ce modèle, le compilateur C++ ? va vérifier, donc pour le moment
est instancié par

, il voit que f() a une méthode Si ce n'est pas le cas, vous obtiendrez un Manipulator<HasF>. erreur de compilation, afin que la sécurité du type soit garantie 2.1.2 Traduit en Java, il ne sera pas compilé HasFf()En raison de l'effacement, le. Le compilateur Java ne peut pas mapper l'exigence selon laquelle manipul() doit pouvoir appeler f() sur obj à HasF. Le fait que f() > Classe générique, étant donné les limites de la classe générique <.>, cela indique au compilateur qu'il ne peut accepter que les types qui suivent cette limite En raison des limites, le code suivant sera compilé

.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

package net.mrliuli.generics.erase;/**

 * Created by li.liu on 2017/12/7.

 *//**

 * 由于有了擦除,Java编译器无法将manipulate()必须能够在obj上调用f()这一需求映射到HasF拥有f()这一事实上。

 * @param <T>

 */class Manipulator<T>{    private T obj;    public Manipulator(T x){ obj = x; }    // Error: Cannot resolve method &#39;f()&#39;

    //public void manipulate(){ obj.f(); }}/**

 * 为了调用f(),我们必须协助泛型类,给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型。由于有了边界,下面的代码就可以编译了。

 * @param <T>

 */class Manipulator2<T extends HasF>{    private T obj;    public Manipulator2(T x){ obj = x; }    public void manipulate(){ obj.f(); }

}public class Manipulation {

    public static void main(String[] args){

        HasF hf = new HasF();

        Manipulator<HasF> manipulator = new Manipulator<>(hf);        //manipulator.manipulate();

        Manipulator2<HasF> manipulator2 = new Manipulator2<>(hf);

        manipulator2.manipulate();

    }

}

Copier après la connexion

2.3 擦除

我们说泛型类型参数将擦除到它的第一个边界(它可能会有多个边界),我们还提到了类型参数的擦除。编译器实际上会把类型参数替换为它的擦除,就像上面的示例一样。T 擦除到HasF,就好像在类的声明中用 HasF 替换T 一样。

2.4 擦除的问题

擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为迁移兼容性

因此,擦除主要的正当理由是从非泛化的代码到泛化的代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入Java语言。

擦除的代码是显著的。

  如果编写了下面这样的代码:

1

class Foo<T>{ T var; }

Copier après la connexion

  那么看起来当你在创建Foo的实例时:

1

Foo<Cat> f = new Foo<Cat>();

Copier après la connexion
  • class Foo中的代码应该知道现在工作于Cat之上,而泛型语法也强烈暗示:在整个类中的各个地方,类型T都在被替换。但是事实上并非如此,无论何时,当你在编写这个类的代码时,必须提醒自己:“不,它只是一个Object。”

  • 擦除和迁移兼容性意味着,使用泛型并不是强制的。

1

class GenericBase<T>{}class Derived1<T> extends GenericBase<T>{}class Derived2 extends GenericBase{} // No warning

Copier après la connexion

2.5 边界处的动作

  • 即使擦除在方法或类内部移除了有关实际类型的信息,编译器仍旧可以确保在方法或类中使用的类型的内部一致性

  • 因为擦除在方法体中移除了类型信息,所以在运行时的问题就是边界即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查插入转型代码的地点。

  • 在泛型中的所有动作都发生在边界处——对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。这有助于澄清对擦除的混淆,记住,“边界就是发生动作的地方。”

3 擦除的补偿(Compensating for erasure)

有时必须通过引入类型标签(type tag)来对擦除进行补偿(compensating)。这意味着你需要显示地传递你的类型的Class对象,以便你可以在类型表达式中使用它。

  • 创建类型实例

  • 泛型数组

4 边界(bound)

  • 边界使得你可以在用于泛型的参数类型上设置限制条件。尽管这使得你可以强制规定泛型可以应用的类型,但是其潜在的一个更重要的效果是你可以按照自己的边界类型来调用方法

  • 因为擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只是那些可以用Object调用的方法。

  • 但是,如果能够将这个参数限制为某个类型子集,那么你就可以用这些类型子集来调用方法。

  • 通配符被限制为单一边界

5 通配符(wildcards)

  • 数组的一种特殊行为
      可以将子类型的数组赋给基类型的数组引用。然后编译期数组元素可以放置基类型及其子类型的元素,即编译时不报错,但运行时的数组机制知道实际的数组类型是子类,因此会在运行时检查放置的类型是否是实际类型及其再导出的子类型,不是则抛出java.lang.ArrayStoreException异常。

  • 容器的类型与容器持有的类型

1

// Compile Error: incompatible types:List<Fruit> list = new ArrayList<Apple>();

Copier après la connexion

  与数组不同,泛型没有内建的协变类型。即*协变性对泛型不起作用

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

package net.mrliuli.generics.wildcards;import java.util.*;/**

 * Created by leon on 2017/12/8.

 */public class GenericsAndCovariance {

    public static void main(String[] args){        // Compile Error: incompatible types:

        //List<Fruit> list = new ArrayList<Apple>();

 

        // Wildcards allow covariance:

        List<? extends Fruit> flists = new ArrayList<Apple>();        // But, 编译器并不知道flists持有什么类型对象。实际上上面语句使得向上转型,丢失掉了向List中传递任何对象的能力,甚至是传递Object也不行。

        //flists.add(new Apple());

        //flists.add(new Fruit());

        //flists.add(new Object());

 

        flists.add(null);   // legal but uninteresting

        // We know that it returns at least Fruit:

        Fruit f = flists.get(0);

    }

}

Copier après la connexion

5.1 编译器有多聪明

  • 对于 List<? extends Fruit>set() 方法不能工作于 AppleFruit,因为 set() 的参数也是 ? extends Furit,这意味着它可以是任何事物,而编译器无法验证“任何事物”的类型安全性。

  • 但是,equals() 方法工作良好,因为它将接受Object类型而并非T类型的参数。因此,编译器只关注传递进来和要返回的对象类型,它并不会分析代码,以查看是否执行了任何实际的写入和读取操作。

5.2 逆变(Contravariance)

  • 使用超类型通配符。声明通配符是由某个特定类的任何基类界定的,方法是指定<? super MyClass>,甚至或者使用类型参数:<? super T>。这使得你可以安全地传递一个类型对象到泛型类型中。

  • 参数apples是Apple的某种基类型的List,这样你就知道向其中添加Apple或Apple的子类型是安全的。

1

2

3

4

5

6

7

8

9

10

package net.mrliuli.generics.wildcards;import java.util.*;public class SuperTypeWildcards {

    /**

     * 超类型通配符使得可以向泛型容器写入。超类型边界放松了在可以向方法传递的参数上所作的限制。

     * @param apples    参数apples是Apple的某种基类型的List,这样你就知道向其中添加Apple或Apple的子类型是安全的。

     */

    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
  • GenericWriting.java 中 writeExact(fruitList, new Apple()); 在JDK1.7中没有报错,说明进入泛型方法 writeExact()T 被识别为 Fruit,书中说报错,可能JDK1.5将 T 识别为 Apple

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

package net.mrliuli.generics.wildcards;import java.util.*;/**

 * Created by li.liu on 2017/12/8.

 */public class GenericWriting {

    static <T> void writeExact(List<T> list, T item){

        list.add(item);

    }    static List<Apple> appleList = new ArrayList<Apple>();    static List<Fruit> fruitList = new ArrayList<Fruit>();    static void f1(){

        writeExact(appleList, new Apple());

        writeExact(fruitList, new Apple());

    }    static <T> void writeWithWildcard(List<? super T> list, T item){

        list.add(item);

    }    static void f2(){

        writeWithWildcard(appleList, new Apple());

        writeWithWildcard(fruitList, new Apple());

    }    public static void main(String[] args){

        f1();

        f2();

    }

}

Copier après la connexion

5.3 无界通配符(Unbounded wildcards)

  原生泛型HolderHolder<?>

原生Holder将持有任何类型的组合,而Holder<?>将持有具有某种具体类型同构集合,因此不能只是向其中传递Object。

5.4 捕获转换

以下示例,被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。参数类型在调用f2()的过程中被捕获,因此它可以在对f1()的调用中被使用。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

package net.mrliuli.generics.wildcards;/**

 * Created by leon on 2017/12/9.

 */public class CaptureConversion {

    static <T> void f1(Holder<T> holder){

        T t = holder.get();

        System.out.println(t.getClass().getSimpleName());

    }    static void f2(Holder<?> holder){

        f1(holder);     // Call with captured type

    }    public static void main(String[] args){

        Holder raw = new Holder<Integer>(1);

        f1(raw);

        f2(raw);

        Holder rawBasic = new Holder();

        rawBasic.set(new Object());

        f2(rawBasic);

        Holder<?> wildcarded = new Holder<Double>(1.0);

        f2(wildcarded);

    }

}

Copier après la connexion

6 问题

  • 基本类型不能作为类型参数

  • 由于探险,一个类不能实现同一个泛型接口的两种变体

  • 由于擦除,通过泛型来重载方法将产生相同的签名,编译出错,不能实现重载

  • 基类劫持了接口

7 总结

我相信被称为泛型的通用语言特性(并非必须是其在Java中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的容器。类型安全的容器是能够创建更通用代码这一能力所带来的副作用。

泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型有更少的限制,因此单个的代码段能够应用到更多的类型上

相关文章:

Java编程思想学习课时(一):第1~13、16章

Java编程思想学习课时(二)第14章-类型信息

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!

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.

Article chaud

R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
1 Il y a quelques mois By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Meilleurs paramètres graphiques
1 Il y a quelques mois By 尊渡假赌尊渡假赌尊渡假赌
Will R.E.P.O. Vous avez un jeu croisé?
1 Il y a quelques mois By 尊渡假赌尊渡假赌尊渡假赌

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