泛型

简介: 泛型

一.概念

1.什么是泛型

  • 泛型的本质是类型参数化。
  • 允许在定义类、接口、方法时使用类型形参,当使用时指定具体类型。
  • 所有使用该泛型参数的地方都被统一化,保证类型一致。
  • 如果未指定具体类型,默认是 Object 类型。
  • 集合体系中的所有类都增加了泛型,泛型也主要用在集合。

泛型是将类型参数化,允许定义在类、接口、方法时使用类型参数,当使用的时候指定具体类型。

泛型主要应用在集合

2.泛型的优点

  • 代码需要更精简
  • 程序更加健壮
  • 编码期,可读性很高

3.类型擦除和桥接方法

泛型是给 javac 编译器使用,在编译器编译之后的 class 文件中是没有泛型信息的,所以泛型的使用不会让程序运行效率收到影响,这个过程称之为擦除。由于类型擦除了,为了维持多态性,需要一些桥接方法类保持多态性,桥接方法是编译器自动生成的

// 最初的代码

publicclassNode<T> {

  publicTdata;

  publicvoidsetData(Tdata) {

    this.data=data;

  }

}

publicclassMyNodeextendsNode<Integer> {

  publicvoidsetData(Integerdata) {

    //....

  }

}

// jvm不认识泛型的,把泛型擦除掉, 兼容⽼版本jdk

public class Node {

 public Object data;

 public void setData(Object data) {

   this.data = data;

 }

}

public class MyNode extends Node {

 // 桥接⽅法,编译器⾃动⽣成

 public void setData(Object data) {

   setData((Integer)data)

 }

 public void setData(Integer data) {

   //....

 }

}

二.泛型上下限

1.泛型的上下限

上限格式:类型名称 <? extend 类> 对象名称说明:只能接收该类型及其子类,指的是参数,不是修改

下限格式:类型名称 <? super 类> 对象名称说明:只能接收该类型及其父类型,指的是参数是父类,添加是该类型自己

2.通配符的上限

/**

* 测试类

* 通配符的上限 List<? extends Type>

* 格式:类型名称<? extends 类> 对象名称

* 意义:只能接收该类型及其子类

*

* @author : kwan

* @version : 1.0.0

* @date : 2022/8/11 16:57

*/

public class Collection_02_extends_super {

   public static void main(String[] args) {

       List<Dog> dogs = new ArrayList<>();

       dogs.add(new Dog());


       // 编译器只知道animals里保存的是Animal的子类

       //但是并不知道是哪个子类

       //Animal 是接口 ,无法添加

       List<? extends Animal> animals = dogs;


//        animals.add(new Dog()); // 编译不通过

//        animals.add(new Animal()); // 编译不通过

//

       Animal animal = animals.get(0);

//        Dog dog = animals.get(0); // 编译不通过

   }

}

3.通配符的下限

/**

* 测试类

* 通配符的下限 List<? supper Type>

* 格式:类型名称<? supper 类> 对象名称

* 意义:只能接收该类型及其父类

*

* @author : kwan

* @version : 1.0.0

* @date : 2022/8/11 16:57

*/

public class Collection_03_extends_super {

 public static void main(String[] args) {

   List<Dog> as = new ArrayList<>();

   as.add(new Dog());

   // 编译器只知道bs里保存的是BigDog的父类

   List<? super BigDog> bs = as;

   //      bs.add(new Dog()); // 编译不通过

   bs.add(new BigDog());

   //      Dog a = bs.get(0); // 编译不通过

   //      BigDog b = bs.get(0); // 编译不通过

 }

}

4.上限解读

通配符的上限 List<? extends Type>可以接收该类型及其子类,即可以接收 Type 的所有子类的 List 对象。

使用通配符的上限可以使得我们在定义方法或变量时,可以限制传入的参数类型或赋值的对象类型。例如,我们定义了一个方法,需要传入一个 List 对象,但我们只需要使用 List 中的一部分元素,此时我们可以使用通配符的上限来限制传入的 List 类型,代码如下:

public class Collection_03_extends_super {

   public static void main(String[] args) {

       List<Dog> dogs = new ArrayList<>();

       dogs.add(new Dog());

       printList(dogs);

   }


   public static void printList(List<? extends Animal> list) {

       for (Animal n : list) {

           System.out.println(n);

       }

   }

}

在上述代码中,我们使用了通配符的上限 List<? extends Animal>来限制传入的 List 对象类型,可以接收 Animal 及其子类的 List 对象。在方法中,我们只需要遍历 List 中的元素,不需要对 List 进行修改,因此使用通配符的上限可以很好地满足我们的需求。

需要注意的是,通配符的上限只能用于读取操作,不能用于写入操作。即我们可以从 List 中读取元素,但不能向 List 中添加元素。这是因为,通配符的上限限制了传入的 List 对象类型,但不能确定 List 对象的具体类型,因此不能向 List 中添加元素。

5.下限解读

通配符的下限 List<? super Type>可以接收该类型及其父类,即可以接收 Type 的所有父类的 List 对象。

使用通配符的下限可以使得我们在定义方法或变量时,可以限制传入的参数类型或赋值的对象类型。通配符的下限通常用于写入操作,例如,我们定义了一个方法,需要向 List 中添加元素,但我们只能添加类型为 Type 及其父类的元素,此时我们可以使用通配符的下限来限制传入的 List 类型,代码如下:

public class Collection_05_extends_super {

   /**

    * BigDog继承Dog

    *

    * @param args

    */

   public static void main(String[] args) {

       List<Dog> as = new ArrayList<>();

       as.add(new Dog());

       addToList(as);

   }


   public static void addToList(List<? super BigDog> list) {

       list.add(new BigDog());

       list.add(new BigDog());

       list.add(new BigDog());

       for (Object o : list) {

           System.out.println(o);

       }

   }

}

在上述代码中,我们使用了通配符的下限 List<? super BigDog>来限制传入的 List 对象类型,可以接收 BigDog 及其父类的 List 对象。在方法中,我们向 List 中添加了三个元素,这些元素的类型都是 BigDog 及其子类,因此不会违反通配符的下限的限制。

需要注意的是,通配符的下限只能用于写入操作,不能用于读取操作。即我们可以向 List 中添加元素,但不能从 List 中读取元素。这是因为,通配符的下限限制了传入的 List 对象类型,但不能确定 List 对象的具体类型,因此不能从 List 中读取元素。

三.泛型符号

1.常用符号

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:

  • ?表示不确定的 java 类型
  • T (type) 表示具体的一个 java 类型
  • K V (key value) 分别代表 java 键值中的 Key Value
  • E (element) 代表 Element

2.?和 T 的区别

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种

// 可以

T t = operate();


// 不可以

?car = operate();

T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

相关文章
|
7月前
|
安全 算法 Java
深入理解泛型
深入理解泛型
|
7月前
|
安全 编译器 Scala
何时需要指定泛型:Scala编程指南
本文是Scala编程指南,介绍了何时需要指定泛型类型参数。泛型提供代码重用和类型安全性,但在编译器无法推断类型、需要提高代码清晰度、调用泛型方法或创建泛型集合时,应明确指定类型参数。通过示例展示了泛型在避免类型错误和增强编译时检查方面的作用,强调了理解泛型使用时机对编写高效Scala代码的重要性。
46 1
何时需要指定泛型:Scala编程指南
|
7月前
|
Java 编译器 语音技术
泛型的理解
泛型的理解
36 0
|
7月前
|
存储 算法 容器
什么是泛型?
什么是泛型?
27 0
|
7月前
|
Java
什么是泛型, 泛型的具体使用
什么是泛型, 泛型的具体使用
35 0
|
7月前
|
存储 Java
什么是泛型, 泛型的具体使用?
什么是泛型, 泛型的具体使用?
|
存储 算法 编译器
泛型的讲解
泛型的讲解
60 0
|
Java
泛型讲解
本章讲解了什么是泛型以及泛型擦除相关的知识点
85 1
|
存储 Java 编译器
对泛型的认识
对泛型的认识
|
Java 编译器 API
泛型-详解
泛型-详解
128 0
泛型-详解