java中的类型擦除type erasure


java中的类型擦除type erasure

简介

泛型是java从JDK 5开始引入的新特性,泛型的引入可以让我们在代码编译的时候就强制检查传入的类型,从而提升了程序的健壮度。

泛型可以用在类和接口上,在集合类中非常常见。本文将会讲解泛型导致的类型擦除。

举个例子

我们先举一个最简单的例子:

@Slf4j
public class TypeErase {

    public static void main(String[] args) {
        ArrayList<String> stringArrayList = new ArrayList<String>();
        stringArrayList.add("a");
        stringArrayList.add("b");
        action(stringArrayList);
    }

    public static void action(ArrayList<Object> al){
        for(Object o: al)
            log.info("{}",o);
    }
}

上面的例子中,我们定义了一个ArrayList,其中指定的类型是String。

然后调用了action方法,action方法需要传入一个ArrayList,但是这个list的类型是Object。

乍看之下好像没有问题,因为String是Object的子类,是可以进行转换的。

但是实际上代码编译出错:

Error:(18, 16) java: 不兼容的类型: java.util.ArrayList<java.lang.String>无法转换为java.util.ArrayList<java.lang.Object>

原因

上面例子的原因就是类型擦除(type erasure)。java中的泛型是在编译时做检测的。而编译后生成的二进制文件中并不保存类型相关的信息。

上面的例子中,编译之后不管是ArrayList<String> 还是ArrayList<Object> 都会变成ArrayList。其中的类型Object/String对JVM是不可见的。

但是在编译的过程中,编译器发现了两者的类型不同,然后抛出了错误。

解决办法

要解决上面的问题,我们可以使用下面的办法:

    public static void actionTwo(ArrayList<?> al){
        for(Object o: al)
            log.info("{}",o);
    }

通过使用通配符?,可以匹配任何类型,从而通过编译。

但是要注意这里actionTwo方法中,因为我们不知道传入的类型到底是什么,所以我们不能在actionTwo中添加任何元素。

总结

从上面的例子我们可以看出,ArrayList<String>并不是ArrayList<Object>的子类。如果一定要找出父子关系,那么ArrayList<String>是Collection<String>的子类。

但是Object[] objArray是String[] strArr的父类。因为对Array来说,其具体的类型是已知的。

本文的例子https://github.com/ddean2009/learn-java-collections

更多精彩内容且看:

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/java-type-erasure/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

<think>嗯,用户之前问过Java和C++语法区别,现在接着问Java类型擦除。看来他对Java的泛型机制不太清楚,特别是和C++模板的区别。类型擦除Java泛型实现的一个核心点,可能他在实际编程中遇到了泛型相关的问题,或者想深入理解Java的设计选择。 我需要先确认类型擦除的定义,然后解释为什么Java要用类型擦除,比如为了兼容旧版本,保持字节码兼容。然后要举例子说明类型擦除的具体表现,比如编译后的类型信息丢失,无法在运行时获取泛型参数类型。可能还需要对比C++的模板,因为用户之前的问题涉及到两者的比较,这样他能更好地理解两者的差异。 另外,用户可能想知道类型擦除带来的限制,比如不能创建泛型数组,instanceof无法使用,以及重载的问题。这些实际开发中的痛点需要具体例子说明。同时,要提到绕过类型擦除的方法,比如通过传递Class对象,这样用户知道如何处理类型信息丢失的情况。 还要注意用户的技术水平,他可能已经有一定编程经验,但可能对底层机制不熟悉,所以需要用简洁明了的语言,避免过于技术化的术语,但又要准确。可能需要用代码示例来辅助解释,比如ArrayList<Integer>和ArrayList<String>在运行时的类型是一样的,这样更直观。 最后总结类型擦除的意义,优缺点,让用户理解Java设计的选择背后的权衡。可能用户想了解为什么Java不采用C++那样的具体化泛型,所以需要提到兼容性和迁移成本的问题。</think>以下是关于 Java 类型擦除Type Erasure)的详细说明,从设计原理到实际影响逐步解析: --- ### 一、**什么是类型擦除?** 类型擦除Java 泛型(Generics)的核心实现机制。**编译时保留泛型类型信息,但在编译后会将泛型类型替换为原始类型(Raw Type)或边界类型(如 `Object`),并插入强制类型转换**。这一设计是为了兼容 Java 5 之前的旧版本代码。 **示例**: ```java // 编译前代码 List<String> list = new ArrayList<>(); list.add("Hello"); String s = list.get(0); // 编译后等效代码(类型擦除后) List list = new ArrayList(); // 原始类型 list.add("Hello"); String s = (String) list.get(0); // 强制转换 ``` --- ### 二、**类型擦除的具体表现** #### 1. **泛型类型信息丢失** - 运行时无法获取泛型参数的具体类型: ```java List<Integer> intList = new ArrayList<>(); List<String> strList = new ArrayList<>(); // 输出均为 true(运行时类型相同) System.out.println(intList.getClass() == strList.getClass()); ``` #### 2. **泛型类型参数替换规则** - 无界类型参数(如 `<T>`)替换为 `Object`: ```java // 编译前 public class Box<T> { private T value; } // 编译后等效 public class Box { private Object value; } ``` - 有界类型参数(如 `<T extends Number>`)替换为边界类型: ```java // 编译前 public class Box<T extends Number> { private T value; } // 编译后等效 public class Box { private Number value; } ``` --- ### 三、**类型擦除带来的限制** #### 1. **无法创建泛型数组** ```java // 编译错误:Generic array creation T[] array = new T[10]; ``` #### 2. **无法使用 `instanceof` 检查泛型类型** ```java if (list instanceof List<String>) { ... } // 编译错误 ``` #### 3. **重载冲突** ```java // 编译错误:方法签名冲突(擦除后均为 List<Object>) public void print(List<String> list) {} public void print(List<Integer> list) {} ``` #### 4. **无法直接实例化泛型类型** ```java public class Box<T> { private T obj = new T(); // 编译错误:类型擦除后 T 变为 Object } ``` --- ### 四、**如何绕过类型擦除的限制** #### 1. **显式传递 `Class` 对象** ```java public class Box<T> { private Class<T> type; public Box(Class<T> type) { this.type = type; } public T createInstance() throws Exception { return type.getDeclaredConstructor().newInstance(); } } ``` #### 2. **通过反射获取泛型信息(有限场景)** ```java // 获取父类泛型参数类型(需继承带泛型的父类) public class StringList extends ArrayList<String> {} Type genericType = StringList.class.getGenericSuperclass(); if (genericType instanceof ParameterizedType) { Type[] types = ((ParameterizedType) genericType).getActualTypeArguments(); System.out.println(types[0]); // 输出 class java.lang.String } ``` --- ### 五、**类型擦除的意义与争议** #### **优点**: - **兼容性**:确保泛型代码与旧版本 JVM 兼容。 - **性能**:运行时无需维护泛型类型信息,减少开销。 #### **缺点**: - **灵活性降低**:部分泛型高级特性无法实现(如具体化类型)。 - **代码冗余**:需手动处理类型转换(如集合类)。 --- ### 六、**与 C++ 模板的对比** | 特性 | Java 泛型(类型擦除) | C++ 模板 | |------------------|------------------------------------------|-----------------------------------| | **实现机制** | 编译时擦除类型,运行时无类型信息 | 编译时生成具体类型代码(代码膨胀)| | **类型检查** | 编译时检查,运行时无泛型类型 | 编译时生成类型安全检查 | | **性能** | 无额外优化(依赖强制转换) | 可生成高度优化的机器码 | | **灵活性** | 受限于类型擦除 | 支持模板元编程等复杂特性 | --- ### 总结 类型擦除Java 泛型实现的核心机制,**在保证兼容性的同时牺牲了部分运行时灵活性**。理解这一机制有助于避免泛型编程中的常见陷阱(如类型转换错误),并合理利用反射等工具突破限制。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flydean程序那些事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值