【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。


相关文章
|
3月前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
77 2
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
2月前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
44 0
[Java]泛型
|
3月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
485 37
|
2月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
20 1
|
3月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
46 9
Java——包装类和泛型
|
2月前
|
前端开发 小程序 Java
java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
26 1
|
2月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
28 5
|
2月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
19 1
|
2月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
22 2