【Java基础】java 泛型知识整理

简介: Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

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

什么是泛型,有什么作用?

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
与非泛型代码相比,使用泛型有三大优点:
更健壮(在编译时进行更强的类型检查)、
更简洁(消除强转,编译后自动会增加强转)、
更通用(代码可适用于多种类型)* 适用于多种数据类型执行相同的代码(代码复用)

image.gif

什么是类型擦除机制?

将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

Java编译器中

先检查代码中泛型的类型(类型检查就是编译时完成的),

然后在进行类型擦除,

再进行编译。

类型擦除的具体步骤?

Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。

1:消除类型参数声明,即删除<>及其包围的部分 (见下图)

根据类型参数的上下界推断并替换所有的类型参数为原生态类型:

如果类型参数是无限制通配符或没有上下界限定则替换为Object,

如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)

2:为了保证类型安全,必要时插入强制类型转换代码

3:自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”

image.gif编辑

如何证明类型的擦除呢?

ArrayList list1 = new ArrayList();

list1.add("abc");

ArrayList list2 = new ArrayList();

list2.add(123);

System.out.println(list1.getClass() == list2.getClass()); // true

说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。

* 通过反射添加其它类型元素

ArrayList list = new ArrayList();

list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer

list.getClass().getMethod("add", Object.class).invoke(list, "asd")

这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型

如何理解泛型的编译期检查?

ArrayList list = new ArrayList();

list.add("123");

list.add(123);//编译错误

泛型变量的使用,是会在编译之前检查,真正涉及类型检查的是它的引用, 也就是 针对前半段 ArrayList list, 所以这里会报错。

因为如果是在编译之后检查,类型擦除后,原始类型为Object,是应该允许任意引用类型添加的。

类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测。

不是说泛型变量String会在编译的时候变为Object类型吗?为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测。

我们举例说明:

// list1 是string 类型

ArrayList list1 = new ArrayList();

list1.add("1"); //编译通过

list1.add(1); //编译错误

String str1 = list1.get(0); //返回类型就是String

// list2 是Object 类型

ArrayList list2 = new ArrayList();

list2.add("1"); //编译通过

list2.add(1); //编译通过

Object object = list2.get(0); //返回类型就是Object

// 下面两个都 是string 类型

new ArrayList().add("11"); //编译通过

new ArrayList().add(22); //编译错误

String str2 = new ArrayList().get(0); //返回类型就是String

ArrayList list1 = new ArrayList();

ArrayList list2 = list1; //编译错误

当我们使用list2引用用get()方法取值的时候,返回的都是String类型的对象,可是它里面实际上已经被我们存放了Object类型的对象,这样就会有ClassCastException,

所以这里编译是无法通过的。

如何理解基本类型不能作为泛型类型?

比如,我们没有ArrayList,只有ArrayList, 为何?

因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储int值,只能引用Integer的值。

如何理解泛型类型不能实例化?

不能实例化泛型类型, 这本质上是由于类型擦除决定的:

T test = new T(); // ERROR

因为在 Java 编译期没法确定泛型参数化类型,也就找不到对应的类字节码文件,所以自然就不行了,此外由于T 被擦除为 Object,如果可以 new T() 则就变成了 new Object(),失去了本意。     如果我们确实需要实例化一个泛型,应该如何做呢?可以通过反射实现:

static  T newTclass (Class < T > clazz) throws InstantiationException, IllegalAccessException {

   T obj = clazz.newInstance();

   return obj;

}

泛型数组:能不能采用具体的泛型类型进行初始化?

Java 的泛型数组初始化时数组类型不能是具体的泛型类型,只能是通配符的形式,因为具体类型会导致可存入任意类型对象,在取出时会发生类型转换异常,会与泛型的设计思想冲突,而通配符形式本来就需要自己强转,符合预期

泛型数组:如何正确的初始化泛型数组实例?

我们在使用到泛型数组的场景下应该尽量使用列表集合替换,此外也可以通过使用 java.lang.reflect.Array.newInstance(Class componentType, int length) 方法来创建一个具有指定类型和维度的数组

public ArrayWithTypeToken(Class type, int size) {

   array = (T[]) Array.newInstance(type, size);

}

如何理解泛型类中的静态方法和静态变量?

泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数

public class Test2 {

   public static T one; //编译错误

   public static T show(T one){ //编译错误

     return null;

   }

   //这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的 T,而不是泛型类中的T

   public static T show(T one){ //这是正确的

       return null;

   }

}

泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建。

如何理解异常中使用泛型?

不能抛出也不能捕获泛型类的对象,因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉

不能再catch子句中使用泛型变量,因为泛型信息在编译的时候已经变为原始类型,也就是说上面的T会变为原始类型Throwable

List 与 List有什么区别?

List 是原生类型,可以添加或访问元素,不具备编译期安全性,而 List 其实是 List的缩写,是协变型的(可引出协变型的特点与限制);从语义上,List 表明使用者清楚变量是类型安全的,而不是因为疏忽而使用了原生类型 List。


目录
打赏
0
0
0
0
46
分享
相关文章
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
93 0
重学Java基础篇—Java对象创建的7种核心方式详解
本文全面解析了Java中对象的创建方式,涵盖基础到高级技术。包括`new关键字`直接实例化、反射机制动态创建、克隆与反序列化复用对象,以及工厂方法和建造者模式等设计模式的应用。同时探讨了Spring IOC容器等框架级创建方式,并对比各类方法的适用场景与优缺点。此外,还深入分析了动态代理、Unsafe类等扩展知识及注意事项。最后总结最佳实践,建议根据业务需求选择合适方式,在灵活性与性能间取得平衡。
115 3
|
2月前
|
重学Java基础篇—Java泛型深度使用指南
本内容系统介绍了Java泛型的核心价值、用法及高级技巧。首先阐述了泛型在**类型安全**与**代码复用**中的平衡作用,解决强制类型转换错误等问题。接着详细讲解了泛型类定义、方法实现、类型参数约束(如边界限定和多重边界)、通配符应用(PECS原则)以及类型擦除的应对策略。此外,还展示了泛型在通用DAO接口、事件总线等实际场景的应用,并总结了命名规范、边界控制等最佳实践。最后探讨了扩展知识,如通过反射获取泛型参数类型。合理运用泛型可大幅提升代码健壮性和可维护性,建议结合IDE工具和单元测试优化使用。
46 1
|
2月前
|
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
83 1
|
8月前
|
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
266 2
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
85 1
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
627 37
|
7月前
|
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
199 0
[Java]泛型
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
96 9
Java——包装类和泛型

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等