美文网首页
Java泛型之类型擦除与局限性

Java泛型之类型擦除与局限性

作者: markeNick | 来源:发表于2020-03-19 22:13 被阅读0次

《Java核心技术卷一 第10版》读书笔记之Java泛型之类型擦除与局限性

以下内容参考文献包括:《java核心技术卷一 第10版》技术博客

类型擦除


要正确理解java的泛型,就需要了解类型擦除。

《java核心技术卷一》:无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased) 类型变量, 并替换为限定类型(无限定的变量用Object)。

白话:java的泛型是伪泛型,在编译期间,所有的泛型信息会被擦掉,java在生成字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

我们可以通过代码来验证这一类型擦除过程

public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(10);

        // 输出结果:true
        System.out.println(list1.getClass() == list2.getClass());
}

由上面的代码可以看出,两个ArrayList所存储的数据类型是不一样的,但是通过getClass()获取类的类型却是一样的,说明String和Integer被擦除了,变成原始类型。

这里有个概念:原始类型

原始类型:擦除了泛型信息后,在字节码中的类型变量的真正类型。

《java核心技术卷一》中对原始类型的例子

我们定义泛型类和泛型方法

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

Pair会变成这样子,所有的泛型信息都被自动替换成另一种类型了,这个类型就是原始类型

class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

《java核心技术卷一》:如果类型变量有限定,那么原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 Object 替换。例如, 类 Pair<T>中的类型变量没有显式的限定, 因此, 原始类型用 Object 替换 T。

看下面的例子:

public class Pair<T extends Comparable & Serializable> implements Serializable {
    private T value;
    
    public Pair(T value) {
        ....
    }
}

类型擦除后,变成这样子

public class Pair implements Serializable {
    private Comparable value;
    
    public Pair(Comparable value) {
        ....
    }
}

如果限定变成这样子 class Pair<T extends Serializable & Comparable>

那么类型擦除的时候是用Serializable替换掉T的

所以,为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界列表的末尾

泛型的局限性


由上面可以知道Java的泛型是伪泛型,编译时使用了类型擦除,所以我们在使用Java泛型时需要考虑一些限制。

不能用基本类型实例化类型参数

不能用类型参数代替基本类型。因此, 没有 Pair<int>, 只 有 Pair<Integer>。 当然,其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 int值。

这的确令人烦恼。但是,这样做与 Java 语言中基本类型的独立状态相一致。这并不是一个致命的缺陷——只有 8 种基本类型, 当包装器类型(wrapper type) 不能接受替换时, 可以使用独立的类和方法处理它们。

运行时类型查询只适用于原始类型

查询一个对象是否属于某个泛型类型时,倘若使用 instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。

if (a instanceof Pair<String>)  // 编译错误
    
if (a instanceof Pair<T>)       // 编译错误
    
Pair<String> p = (Pair<String>) a;  // 警告

getClass方法总是返回原始类型,例如:

Pair<String> stringPair = . .
Pair<Employee> employeePair = . .
if (stringPair.getClass() == employeePair.getClass()) // 结果:true

两次调用getClass都返回Pair.class

不能创建参数化类型的数组

例如:

Pair<String>[] table = new Pair<String>[10]; // 编译错误

如果需要收集参数化类型对象, 只有一种安全而有效的方法:使用 ArrayList: ArrayList<Pair<String>>

不能实例化类型变量

不能使用像 new T(...)、 newT[...] 或 T.class 这样的表达式中的类型变量,例如:

public class Pair<T> {
    private T first;
    private T sencond;
    
    // Pair的构造器
    public Pair() {
        first = new T();    // 编译错误
        second = new T();   // 编译错误
    } 
}

在 Java SE 8 之后,最好的解决办法是让调用者提供一个构造器表达式。例如:

Pair<String> p = Pair.makePair(String::new);

makePair 方法接收一个 Supplier<T>,这是一个函数式接口,表示一个无参数而且返回类型为 T 的函数:

public static <T> Pair<T> makePair(Supplier<T> constr) {
    return new Pair<>(constr.get0. constr.get0);
}

或者,这么写也可以:

public static <T> Pair<T> makePair(Class<T> cl) {
    try {
        return new Pair<T>(cl.newInstance(), cl.newInstance());
    } catch(Exception e) {
        return null;
    }
}

Pair<String> p = Pair.makePair(String.class);

泛型类的静态上下文中类型变量无效

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

public class Singleton<T> {
    private static T singlelnstance;        // Error
    public static T getSinglelnstance() {   // Error
        if (singleinstance == null) construct new instance of T
        return singlelnstance; 
    } 
}

类型擦除之后, 只剩下 Singleton 类,它只包含一个 singlelnstance 域。 因此, 禁止使用带有类型变量的静态域和方法。

不能抛出或捕获泛型类的实例

实际上, 甚至泛型类扩展 Throwable 都是不合法的。例如, 以下定义就不能正常编译:

// Error can't extend Throwable
public class Problem<T> extends Exception { 
    /* . . . */ 
} 

catch 子句中不能使用类型变量。例如, 以下方法将不能编译:

public static <T extends Throwable> void doWork(Class<T> t) {
    try{
        do work
    } catch (T e) {     // Error can 't catch type variable
        Logger,global.info(...) 
    } 
}

注意擦除后的冲突

当泛型类型被擦除时, 无法创建引发冲突的条件。下面是一个示例。假定像下面这样将equals 方法添加到 Pair 类中:

public class Pair<T> {
    public boolean equals(T value) { 
        return first,equals(value) && second,equals(value); 
    }
}

考虑一个 Pair<String>。从概念上讲, 它有两个 equals 方法:

boolean equals(String) // defined in Pair<T>
boolean equals(Object) // inherited from Object

但实际上,方法擦除了 boolean equals(T) 就是 boolean equals(Object),这会与Object.equals方法发送冲突。

相关文章

  • 【进阶之路】Java的类型擦除式泛型

    【进阶之路】Java的类型擦除式泛型 Java选择的泛型类型叫做类型擦除式泛型。什么是类型擦除式泛型呢?就是Jav...

  • Java泛型之类型擦除与局限性

    《Java核心技术卷一 第10版》读书笔记之Java泛型之类型擦除与局限性 以下内容参考文献包括:《java核心技...

  • Android 开发也要掌握的Java知识 - Java泛型

    如果需要看泛型擦除Java泛型擦除 1.Java泛型有什么用?为啥要使用泛型? Java中数组的类型是定义的时候就...

  • Java 泛型与通配符

    参考地址:《Java 泛型,你了解类型擦除吗?》 《Java中的逆变与协变》 《java 泛型中 T、E .....

  • Java如何在运行时获取泛型的类型

    Java泛型是伪泛型,会在编译完成时进行类型的擦除,我们无法在运行时获取泛型参数的具体类型(类型擦除会被替换成泛型...

  • Java 泛型之类型擦除和通配符PECS原则

    类型擦除 泛型是Java 5才引入的特性,在这之前,并没有泛型,所以Java的泛型和C++的不一样,是通过类型擦除...

  • JAVA泛型和类型擦除

    什么是类型擦除 Java是使用擦除来实现泛型的。使用泛型后在运行时任何具体的类型信息都被擦除了,关于泛型的处理都是...

  • java 泛型

    [Java 泛型总结(一):基本用法与类型擦除] https://segmentfault.com/a/11900...

  • Java编程思想---泛型(2)

    Java编程思想---泛型(2) 类型擦除 先上例子: ArrayList< String >与ArrayList...

  • java泛型

    java的泛型是"伪泛型",为什么这么说。因为泛型只是作用在编译之前,编译之后,泛型都被擦除了(类型擦除)。所以说...

网友评论

      本文标题:Java泛型之类型擦除与局限性

      本文链接:https://www.haomeiwen.com/subject/apmryhtx.html