资讯专栏INFORMATION COLUMN

Java泛型:类型擦除

Hanks10100 / 1238人阅读

博客地址:Java泛型:类型擦除

前情回顾

Java泛型:泛型类、泛型接口和泛型方法

类型擦除 代码片段一
Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass(); 
System.out.println(c1 == c2);

/* Output
true
*/

显然在平时使用中,ArrayList()new ArrayList()是完全不同的类型,但是在这里,程序却的的确确会输出true

这就是Java泛型的类型擦除造成的,因为不管是ArrayList()还是new ArrayList(),都在编译器被编译器擦除成了ArrayList。那编译器为什么要做这件事?原因也和大多数的Java让人不爽的点一样——兼容性。由于泛型并不是从Java诞生就存在的一个特性,而是等到SE5才被加入的,所以为了兼容之前并未使用泛型的类库和代码,不得不让编译器擦除掉代码中有关于泛型类型信息的部分,这样最后生成出来的代码其实是『泛型无关』的,我们使用别人的代码或者类库时也就不需要关心对方代码是否已经『泛化』,反之亦然。

在编译器层面做的这件事(擦除具体的类型信息),使得Java的泛型先天都存在一个让人非常难受的缺点:

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

代码片段二
List list = new ArrayList();
Map map = new HashMap();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));

/* Output
[E]
[K, V]
*/

关于getTypeParameters()的解释:

Returns an array of TypeVariable objects that represent the type variables declared by the generic declaration represented by this GenericDeclaration object, in declaration order. Returns an array of length 0 if the underlying generic declaration declares no type variables.

我们期待的是得到泛型参数的类型,但是实际上我们只得到了一堆占位符。

代码片段三
public class Main {

    public T[] makeArray() {
        // error: Type parameter "T" cannot be instantiated directly
        return new T[5];
    }
}

我们无法在泛型内部创建一个T类型的数组,原因也和之前一样,T仅仅是个占位符,并没有真实的类型信息,实际上,除了new表达式之外,instanceof操作和转型(会收到警告)在泛型内部都是无法使用的,而造成这个的原因就是之前讲过的编译器对类型信息进行了擦除。

同时,面对泛型内部形如T var;的代码时,记得多念几遍:它只是个Object,它只是个Object……

代码片段四
public class Main {

    private T t;

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

    public T get() {
        return t;
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.set("findingsea");
        String s = m.get();
        System.out.println(s);
    }
}

/* Output
findingsea
*/

虽然有类型擦除的存在,使得编译器在泛型内部其实完全无法知道有关T的任何信息,但是编译器可以保证重要的一点:内部一致性,也是我们放进去的是什么类型的对象,取出来还是相同类型的对象,这一点让Java的泛型起码还是有用武之地的。

代码片段四展现就是编译器确保了我们放在t上的类型的确是T(即便它并不知道有关T的任何类型信息)。这种确保其实做了两步工作:

set()处的类型检验

get()处的类型转换

这两步工作也成为边界动作

代码片段五
public class Main {

    public List fillList(T t, int size) {
        List list = new ArrayList();
        for (int i = 0; i < size; i++) {
            list.add(t);
        }
        return list;
    }

    public static void main(String[] args) {
        Main m = new Main();
        List list = m.fillList("findingsea", 5);
        System.out.println(list.toString());
    }
}

/* Output
[findingsea, findingsea, findingsea, findingsea, findingsea]
*/

代码片段五同样展示的是泛型的内部一致性。

擦除的补偿

如上看到的,但凡是涉及到确切类型信息的操作,在泛型内部都是无法共工作的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。

代码片段六
public class Main {

    public T create(Class type) {
        try {
            return type.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        Main m = new Main();
        String s = m.create(String.class);
    }
}

代码片段六展示了一种用类型标签生成新对象的方法,但是这个办法很脆弱,因为这种办法要求对应的类型必须有默认构造函数,遇到Integer类型的时候就失败了,而且这个错误还不能在编译器捕获。

进阶的方法可以用限制类型的显示工厂和模板方法设计模式来改进这个问题,具体可以参见《Java编程思想 (第4版)》P382。

代码片段七
public class Main {

    public T[] create(Class type) {
        return (T[]) Array.newInstance(type, 10);
    }

    public static void main(String[] args) {
        Main m = new Main();
        String[] strings = m.create(String.class);
    }
}

代码片段七展示了对泛型数组的擦除补偿,本质方法还是通过显示地传递类型标签,通过Array.newInstance(type, size)来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。

以上,泛型的第二部分的结束。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64658.html

相关文章

  • Java 泛型总结(一):基本用法与类型擦除

    摘要:然而中的泛型使用了类型擦除,所以只是伪泛型。总结本文介绍了泛型的使用,以及类型擦除相关的问题。一般情况下泛型的使用比较简单,但是某些情况下,尤其是自己编写使用泛型的类或者方法时要注意类型擦除的问题。 简介 Java 在 1.5 引入了泛型机制,泛型本质是参数化类型,也就是说变量的类型是一个参数,在使用时再指定为具体类型。泛型可以用于类、接口、方法,通过使用泛型可以使代码更简单、安全。然...

    Java_oldboy 评论0 收藏0
  • Java系列之泛型

    摘要:总结泛型的类型必须是引用类型,不能是基本类型,泛型的个数可以有多个,可以使用对创建对象时的泛型类型以及方法参数类型进行限制,如使用关键字和对泛型的具体类型进行向下限制或向上限制,最后一点,可以声明泛型数组,但是不能创建泛型数组的实例。 自从 JDK 1.5 提供了泛型概念,泛型使得开发者可以定义较为安全的类型,不至于强制类型转化时出现类型转化异常,在没有反省之前,可以通过 Object...

    MadPecker 评论0 收藏0
  • 初探Java类型擦除

    摘要:可以看到,如果我们给泛型类制定了上限,泛型擦除之后就会被替换成类型的上限。相应的,泛型类中定义的方法的类型也是如此。参考语言类型擦除下界通配符和的区别 本篇博客主要介绍了Java类型擦除的定义,详细的介绍了类型擦除在Java中所出现的场景。 1. 什么是类型擦除 为了让你们快速的对类型擦除有一个印象,首先举一个很简单也很经典的例子。 // 指定泛型为String List list1 ...

    DevTalking 评论0 收藏0
  • 聊聊Java泛型及实现

    摘要:静态变量是被泛型类的所有实例所共享的。所以引用能完成泛型类型的检查。对于这个类型系统,有如下的一些规则相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。事实上,泛型类扩展都不合法。 前言 和C++以模板来实现静多态不同,Java基于运行时支持选择了泛型,两者的实现原理大相庭径。C++可以支持基本类型作为模板参数,Java却只能接受类作为泛型参数;Java可以在泛型类的方法中取得...

    lewif 评论0 收藏0
  • Java泛型总结

    摘要:静态变量是被泛型类的所有实例所共享的。对于这个类型系统,有如下的一些规则相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。在代码中避免泛型类和原始类型的混用。参考泛型类型擦除 Java泛型总结 Java泛型是JDK5引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用的时候使用具体的类型来替换。泛型最主要的应用是在JDK5...

    CoreDump 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<