【Java面试】泛型

Java泛型引入了参数化类型,解决集合元素类型安全和强制转换问题。泛型擦除是在编译期间移除类型参数,使用Object作为默认类型。List<?superT>用于设定类型通配符下限,确保元素是T或其超类型;List<?extendsT>设定上限,元素为T或其子类型。这种设计避免了运行时异常,但不支持型变,如数组那样。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说一说你对泛型的理解

Java集合有个缺点—把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object类型(其运行时类型没变)。
在这里插入图片描述
在这里插入图片描述

Java集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性。但这样做带来如下两个问题:

  • 集合对元素类型没有任何限制,这样可能引发一些问题。例如,想创建一个只能保存Dog对象的集合,但程序也可以轻易地将Cat对象“丢”进去,所以可能引发异常。

  • 由于把对象“丢进”集合时,集合丢失了对象的状态信息,只知道它盛装的是Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂度,也可能引发ClassCastException异常。

从Java 5开始,Java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型,Java的参数化类型被称为泛型(Generic)。例如 List< String >,表明该List只能保存字符串类型的对象。

有了泛型以后,程序再也不能“不小心”地把其他对象“丢进”集合中。而且程序更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。

面试场景:
面试官:说说你对泛型的理解。

我:好的。我们知道,在我们使用集合的时候,集合默认是允许我们放入一个Object类型的对象的。而Java中所有的类都默认继承了Object类。因此其实所有的类都可以放入到集合中。

我:这就造成了一个问题,如果我们想要一个只能存放String类型的集合,那么可能我们放入String之后,又放入了Integer等类型。

我:并且我们在取出元素的时候,它的返回类型也是Object,那么此时我们就得显式的进行类型转换,提高了ClassCastException的可能性。

我:所以在JDK1.5之后,Java提供了一种“参数化类型的概念”,也就是我们说的泛型,他允许我们在使用集合的时候设定类型,而之后再向这个集合中添加或获取元素的时候,元素类型必须是这个类型的或者其子类。这样子我们就能自己控制集合中元素的类型了,也就无需在对元素进行强制类型转换。

我:以上是我对泛型的理解。

介绍一下泛型擦除

在严格的泛型代码里,带泛型声明的类总应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型。如果没有为这个泛型类指定实际的类型,此时被称作raw type(原始类型),默认是声明该泛型形参时指定的第一个上限类型。

当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉。比如一个
List < String > 类型被转换为List,则该List对集合元素的类型检查变成了泛型参数的上限(即Object)。

上述规则即为泛型擦除,可以通过下面代码进一步理解泛型擦除:

List<String> list1 = ...; List list2 = list1; // list2将元素当做Object处理

从逻辑上来看,List< String > 是List的子类,如果直接把一个List对象赋给一个List< String >对象应该引起编译错误,但实际上不会。对泛型而言,可以直接把一个List对象赋给一个 List< String > 对象,编译器仅仅提示“未经检查的转换”。

上述规则叫做泛型转换,可以通过下面代码进一步理解泛型转换:

List list1 = ...; List<String> list2 = list1; // 编译时警告“未经检查的转换”

List<? super T>和List<? extends T>有什么区别?

? 是类型通配符,List<?> 可以表示各种泛型List的父类,意思是元素类型未知的List;

List<? super T> 用于设定类型通配符的下限,此处 ? 代表一个未知的类型,但它必须是T的父类型;

List<? extends T> 用于设定类型通配符的上限,此处 ? 代表一个未知的类型,但它必须是T的子类型。

在Java的早期设计中,允许把Integer[]数组赋值给Number[]变量,此时如果试图把一个Double对象保存到该Number[]数组中,编译可以通过,但在运行时抛出ArrayStoreException异常。这显然是一种不安全的设计,因此Java在泛型设计时进行了改进,它不再允许把 List< Integer > 对象赋值给 List< Number > 变量。

数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型,但G< Foo > 不是 G< Bar > 的子类型。Foo[]自动向上转型为Bar[]的方式被称为型变,也就是说,Java的数组支持型变,但Java集合并不支持型变Java泛型的设计原则是,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ZhangBlossom

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

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

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

打赏作者

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

抵扣说明:

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

余额充值