深入学习 Java 泛型实现方式:擦除法!

举报
喵手 发表于 2025/03/19 18:17:26 2025/03/19
【摘要】 开篇语哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,...

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

  在 Java 中,泛型是一个非常重要的概念,它使得代码能够更加灵活和类型安全。你可能已经在开发中使用过泛型,它允许你在编译时指定类型,而在运行时保证类型安全。但你是否了解过 Java 泛型的实现方式?很多人可能并不知道,Java 泛型的实现是通过一种叫做“擦除法”(Type Erasure)的方法实现的。

  今天,我们将深入探索 Java 泛型是如何实现的,特别是“擦除法”背后的原理和实际应用。通过这篇文章,你将更加清楚地理解泛型在 Java 中的工作原理,以及为什么 Java 需要使用擦除法来实现泛型。

1. 泛型简介

  泛型允许你在类、接口和方法中使用类型参数。它使得代码能够在编译时就确定类型,从而避免了运行时的类型转换错误。泛型的核心思想就是类型参数化,也就是说你可以在不具体指定类型的情况下,定义类、接口和方法。

泛型的基本语法

class Box<T> {
    private T value;
    
    public T getValue() {
        return value;
    }

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

在上面的例子中,Box 类定义了一个泛型类型 T,它可以接受任何类型的值。当你使用这个类时,你可以指定具体的类型:

Box<Integer> intBox = new Box<>();
intBox.setValue(100);

Box<String> strBox = new Box<>();
strBox.setValue("Hello, World!");

通过泛型,Box 类可以处理不同类型的数据,而不需要写多个不同类型的类。

2. 泛型的实现方式:擦除法

  在 Java 中,泛型是通过 擦除法 来实现的。擦除法的核心思想是,在编译时,Java 编译器会移除所有泛型类型的具体信息,并将泛型类型替换为原始类型。也就是说,泛型在编译后的字节码中并不存在。泛型类型的“擦除”发生在编译时,而在运行时,JVM 并不知道泛型的类型参数是什么。

擦除法的工作原理

  1. 类型擦除:Java 编译器会将所有的泛型类型替换为它们的原始类型。例如,Box<Integer>Box<String> 会被擦除为 Box,这意味着在编译后的字节码中,Box<Integer>Box<String> 没有任何区别。
  2. 类型参数替换:对于泛型类型的实例化,编译器会将泛型类型的类型参数替换为原始类型或上界类型。例如,Box<T> 会被转换为 Box<Object>,如果指定了类型上限,泛型类型会被替换为该上限类型。
  3. 类型安全保证:擦除法保证了 Java 中的类型安全。在运行时,类型信息已经丢失,但 Java 会通过类型检查和转换来确保类型的安全性。

擦除的例子

假设我们有一个简单的泛型类:

class Box<T> {
    private T value;
    
    public T getValue() {
        return value;
    }

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

在编译后,Box<Integer>Box<String> 的字节码将会变成类似以下形式:

class Box {
    private Object value;
    
    public Object getValue() {
        return value;
    }

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

可以看到,所有的泛型信息(例如 <T>)已经被移除了,T 被替换为 Object。这就是 Java 中泛型的“擦除”过程。

泛型的类型擦除例子

为了更好地理解擦除法,来看一个具体的例子:

class Pair<T, U> {
    private T first;
    private U second;

    public Pair(T first, U second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() {
        return first;
    }

    public U getSecond() {
        return second;
    }
}

public class Test {
    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<>("Hello", 10);

        // 输出:Hello, 10
        System.out.println(pair.getFirst() + ", " + pair.getSecond());
    }
}

编译后,Pair<String, Integer>Pair<Double, String> 的字节码会被擦除为如下形式:

class Pair {
    private Object first;
    private Object second;

    public Pair(Object first, Object second) {
        this.first = first;
        this.second = second;
    }

    public Object getFirst() {
        return first;
    }

    public Object getSecond() {
        return second;
    }
}

擦除与泛型边界

在泛型中,我们通常可以设置类型的边界,例如:

class Box<T extends Number> {
    private T value;
    
    public T getValue() {
        return value;
    }

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

在上述代码中,T 的类型被限定为 Number 的子类,编译后,T 会被擦除为 Number 类型:

class Box {
    private Number value;
    
    public Number getValue() {
        return value;
    }

    public void setValue(Number value) {
        this.value = value;
    }
}

这说明泛型类型 T 被擦除为 Number,即使我们传入的是 IntegerDouble 等具体类型,最终 Box 仍然能够接受 Number 类型的值。

3. 擦除法的影响

3.1 反射中的限制

由于泛型类型在运行时被擦除,使用反射时无法获取泛型的实际类型。例如,下面的代码会失败,因为 List<String>List<Integer> 在运行时都被擦除为 List

import java.lang.reflect.*;

public class Test {
    public static void main(String[] args) throws NoSuchFieldException {
        Field field = Pair.class.getDeclaredField("first");
        System.out.println(field.getGenericType());  // 输出:T
    }
}

上述代码中的 Pair<String, Integer>Pair<Double, String> 在运行时无法知道类型参数 TU 的实际类型。

3.2 类型转换和类型安全

由于擦除法的存在,在使用泛型时,编译器会进行类型检查,确保类型转换的安全性。例如:

List<String> list = new ArrayList<>();
list.add("Hello");
String value = list.get(0);  // 类型安全,编译时就检查通过

虽然在编译时可以保证类型的安全性,但在运行时,由于泛型信息已被擦除,JVM 只会看到原始类型的 List

3.3 泛型的变通方法

为了弥补泛型擦除带来的限制,Java 引入了通配符和类型参数的边界约束。例如,使用 ? extends T? super T 来表示可以接收 T 的子类型或父类型,从而增强泛型的灵活性。

public static void printList(List<? extends Number> list) {
    for (Number number : list) {
        System.out.println(number);
    }
}

这种方式允许我们在运行时处理不同的类型,而无需依赖具体的类型参数。

4. 总结

  Java 泛型的实现方式主要依赖于 擦除法,即在编译时移除泛型类型的具体信息,在运行时只保留原始类型或上界类型。擦除法能够有效地保持 Java 的兼容性,并且提供类型安全,但它也带来了反射时无法获取泛型类型、类型转换不方便等问题。

通过理解擦除法的工作原理,我们可以更好地利用 Java 泛型提供的类型安全性,并在开发过程中根据实际需求选择合适的泛型实现方式。希望本文能够帮助你深入理解 Java 泛型的实现机制,为你在开发中解决实际问题提供指导。

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。