13.泛型

为什么我们需要泛型?

所以泛型的好处就是:

  • 适用于多种数据类型执行相同的代码
  • 泛型中的类型在使用时指定,不需要强制类型转换

泛型类 和泛型接口的定义

泛型接口与泛型类的定义基本相同

  • 泛型类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class NormalGeneric<T>{
    private T data;

    public NormalGeneric()

    public NormalGeneric(T data){
    this();
    this.data = data;
    }
    }


    public class NormalGeneric2<T,K>{
    private T data;
    private K result;
    public NormalGeneric2 (){

    }
    }
  • 泛型接口

    1
    2
    3
    public interface Generator<T> {
    public T next();
    }

    而实现泛型接口的类,有两种实现方法:

    • 未传入泛型实参时:

      1
      public class ImplGenerator<T> implements Generator<T>{}

      在 new 出类的实例时,需要指定具体类型:
      ImplGenerator<String> implGenerator = new

    • 传入泛型实参:

      1
      public class ImplGenerator2 implements Generator<String>{}

      在 new 出类的实例时,和普通的类没区别。

泛型方法辨析

  • 普通方法

    1
    2
    3
    public T getKey(){
    return key;
    }
  • 泛型方法

    1
    2
    3
    4
    //public修饰符 和 T返回值之间有 <T>
    public <T> T genericMethod(T...a){
    return a[a.length/2];
    }

限定类型变量

有时候,我们需要对类型变量加以约束,比如计算两个变量的最小,最大值。 请问,如果确保传入的两个变量一定有 compareTo 方法?那么解决这个问题的方案就是将 T 限制为实现了接口 Comparable 的类

  • 类和接口混用,类必须写在前面,是第一个(Java语法规定)
  • 类有且只能有一个,不能有多个
  • Java里面是单继承,多实现

泛型中的约束和局限性

  1. 不能用基本类型实例化类型参数(只能用其包装类)

    1
    2
    // Restrict<double> 这种不允许
    Restrict<Double>restrict = new Restrict<>();
  2. 运行时类型查询只适用于原始类型

    • 在程序运行过程中确定对象的实际类型
    • 这种类型查询通常使用 instanceof 关键字以及类型转换来实现,允许程序在运行时检测一个对象是否是某个特定类的实例。
    1
    2
    3
    4
    5
    //if(restrict instanceof Restrict<Double>)(这种不允许
    //if(restrict instanceof Restrict<T>)(J这种不允许
    Restrict<String> restrictString = new Restrict<>();
    System.out.println(restrict.getClass()==restrictString.getClass()); //true
    System.outprintln(restrict.getClass().getName();
  3. 泛型类的静态上下文中类型变量失效

    • 不能在静态域或方法中引用类型变量。

      • 因为泛型是要在对象创建的时候才知道是什么类型的,
      • 而对象创建的代码执行先后顺序是 static 的部分,然后才是构造函数等等。
      • 所以在对象初始化之前 static 的部分已经执行了,如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还没有初始化。
      1
      2
      3
      4
      //静态域或者方法里不能引用类型变量
      //private static T instance;
      //静态方法本身是泛型方法就行
      //private static <T> T getInstance(){}
  4. 不能创建参数化类型的数组

    • 可以定义泛型数组但是不能创建

      1
      2
      Restrict<Double>[] restrictArray;//可以
      //Restrict<Double>[] restrictArray = new Restrict<Double>[10] //不允许
  5. 不能实例化类型变量

    1
    2
    3
    4
    //不可以
    public Restrict() {
    this.data = new T();
    }
  6. 不能捕获泛型类的实例(try catch)

    1
    2
    3
    4
    5
    6
    7
    8
    //这是可以的
    public <T extends Throwable> void doWorkSuccess(T x)throws T{
    try{

    }catch(Throwable e){ //但是这里不能catch(T x)
    throw X;
    }
    }
  7. 泛型类不能extends Exception/Throwable

泛型类型的继承规则

1
2
3
4
5
6
//有一个类 和 一个其子类
public class Employee {}
public class Worker extends Employee {}
//有一个泛型类
public class Pair<T>

请问 Pair和 Pair是继承关系吗?
答案:不是,他们之间没有什么关系

1
2
3
Employee employee = new Worker();
//↓这是错误的
Pair<Employee> employeePair2 = new Pair<Worker>();

但是泛型类可以 继承或者扩展其他泛型类,比如 List 和 ArrayList

1
2
3
4
5
private static class ExtendPair<T> extends Pair<T>{

}
//这是OK的
Pair<Employee> pair = new ExtendPair<>();

通配符类型

类型的上界——GenericType**<? extends Fruit>** p

  • 可以安全访问get数据

类型的下界——GenericType**<? super Apple>** p

  • 只能set Apple其本身及其子类,不能set其父类

  • get 只能返回Object

  • Apple及其子类 可以安全转型为Apple,但是Apple的父类fruit不能安全转型

虚拟机是如何实现泛型的 ?

类型擦除

面试

谈谈你对Java泛型中类型擦除的理解,并说说其局限性?(享学)

1. 类型擦除的理解

(1) 定义

类型擦除是指在编译时,将泛型类型参数替换为object或指定的限定类型(通常是 Object),并在必要时插入类型转换

(2) 实现方式

  • 泛型类和方法

    • 编译时,泛型类型参数会被替换为 Object 或指定的限定类型。
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class Box<T> {
      private T value;

      public void set(T value) {
      this.value = value;
      }

      public T get() {
      return value;
      }
      }
      • 编译后:
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        public class Box {
        private Object value;

        public void set(Object value) {
        this.value = value;
        }

        public Object get() {
        return value;
        }
        }
  • 类型转换

    • 在使用泛型时,编译器会自动插入类型转换代码。
    • 示例:
      1
      2
      3
      Box<String> box = new Box<>();
      box.set("Hello");
      String value = box.get(); // 编译后插入类型转换
      • 编译后:
        1
        2
        3
        Box box = new Box();
        box.set("Hello");
        String value = (String) box.get(); // 自动插入类型转换

(3) 桥接方法

  • 问题:类型擦除可能导致子类重写父类方法时出现方法签名冲突。(确保子类能够正确重写父类的方法)

    • 方法签名:由方法名、参数类型列表和返回类型组成,用于唯一标识一个方法
  • 解决方案:编译器会生成桥接方法(Bridge Method)来确保多态性。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Node<T> {
    public void setData(T data) {
    System.out.println("Node.setData");
    }
    }

    class MyNode extends Node<Integer> {
    public void setData(Integer data) {
    System.out.println("MyNode.setData");
    }
    }
    • 编译后:

    • 确保通过父类引用调用 setData 方法时,实际执行的是子类的实现

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Node {
      public void setData(Object data) {
      System.out.println("Node.setData");
      }
      }

      class MyNode extends Node {
      public void setData(Integer data) {
      System.out.println("MyNode.setData");
      }

      // 编译器生成的桥接方法
      public void setData(Object data) {
      setData((Integer) data);
      }
      }

2. 类型擦除的局限性

(1) 无法获取运行时类型信息

  • 由于类型擦除,泛型类型参数的具体类型在运行时不可用。
  • 示例:
    1
    2
    List<String> list = new ArrayList<>();
    System.out.println(list.getClass()); // 输出:class java.util.ArrayList

(2) 无法创建泛型类型的数组

  • 由于类型擦除,无法直接创建泛型类型的数组。
  • 示例:
    1
    2
    // 编译错误
    // List<String>[] array = new ArrayList<String>[10];

(3) 无法使用基本类型作为泛型类型参数

  • 泛型类型参数必须是引用类型,不能是基本类型。
  • 示例:
    1
    2
    // 编译错误
    // List<int> list = new ArrayList<>();

(4) 无法重载泛型方法

  • 由于类型擦除,泛型方法的重载可能引发冲突。
  • 示例:
    1
    2
    public void print(List<String> list) {}
    public void print(List<Integer> list) {} // 编译错误:方法签名冲突

(5) 无法实例化泛型类型

  • 由于类型擦除,无法直接实例化泛型类型。
  • 示例:
    1
    2
    3
    4
    public <T> void createInstance() {
    // 编译错误
    // T instance = new T();
    }

3. 总结

  • 类型擦除:Java 泛型通过类型擦除实现,将泛型类型参数替换为 Object 或限定类型,并在必要时插入类型转换。
  • 桥接方法:编译器生成桥接方法以确保多态性。
  • 局限性
    • 无法获取运行时类型信息。
    • 无法创建泛型类型的数组。
    • 无法使用基本类型作为泛型类型参数。
    • 无法重载泛型方法。
    • 无法实例化泛型类型。

理解类型擦除的机制及其局限性,有助于更好地使用泛型并避免常见的陷阱。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2023-2025 Annie
  • Visitors: | Views:

嘿嘿 请我吃小蛋糕吧~

支付宝
微信