java面试之泛型

本文深入探讨Java泛型的概念,包括其由来、伪泛型的特点、类型擦除机制及其带来的问题与解决方案。同时,还讨论了泛型在多态中的应用及其实现细节。

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

1、泛型基本概念

1.1、由来

泛型是JDK 1.5的一项新特性,在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,那Object转型为任何对象成都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会被转嫁到程序运行期之中。

1.2、伪泛型

泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。

  Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int>ArrayList<String>就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。

1.3、泛型的使用

  • 泛型类

  • 泛型接口


  • 泛型方法

1.4、一些要求及规则

  • 不能使基本类型
  • 不管该限定是类还是接口,统一都使用关键字extends
  • 可以使用&符号给出多个限定
  •  如果限定既有接口也有类,那么类必须只有一个,并且放在首位置

详见:http://blog.youkuaiyun.com/lonelyroamer/article/details/7864531

2、原理

2.1、类型擦除

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

如在代码中定义的List<object>和List<String>等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。

2.2、重要:用反射来看泛型的机制(甚至可以破坏)

在程序中定义了一个ArrayList泛型类型实例化为Integer的对象,如果直接调用add方法,那么只能存储整形的数据。不过当我们利用反射调用add方法的时候,却可以存储字符串。这说明了Integer泛型实例在编译之后被擦除了,只保留了原始类型。

2.3、原始类型

原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除(crased),并使用其限定类型(使用extends的,如果extends多个则原始类型就用第一个边界的类型变量来替换)替换,无限定的变量用Object替换。

3、类型擦除引起的问题及解决办法

3.1、先检查、再编译

因为类型擦除是在编译期完成的,在运行的时候就会忽略泛型,为了保证在运行的时候不出现类型错误,就需要在编译之前就检查是否满足泛型要求(类型检查)。

3.2、类型检查的依据

以上两种情况都没有错误:第一种情况,在使用arrayList1的时候与完全使用泛型参数一样的效果,因为new ArrayList()只是在内存中新开辟一个存储空间,它并不能判断类型,而真正涉及类型检查的是它的引用,所以在调用arrayList1的时候会进行类型检查。同理,第二种情况,就不会进行类型检查。

3.3、泛型参数化类型没有继承关系

  • 第一种情况,可以扩展为一下形式:

假设它编译没错,那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是String类型的对象(类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object类型的对象,这样就会出现ClassCastException。

  • 第二种情况,同样扩展为:

虽然不会出现ClassCastException,但是假设它编译没错,那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是Object类型的对象,需要进行类型转换才行,这样,泛型就完全没有作用了。

4、类型擦除与多态的冲突和解决方法

现在看来我们在子类中重写了父类的两个方法,而实际上,经过类型擦除之后:

可以看到,父类和子类的方法中参数类型不同,所以如果是在普通的继承关系中,这完全不是重写,而是重载;但是如果在泛型中呢?

如果是重载,那么子类中两个setValue方法,一个是参数Object类型,一个是Date类型,可是我们发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,在泛型中确实是重写了,而不是重载。

4.1、桥方法

         通过编译源代码会发现DataInter最后会有四种方法,其中两个是编译器自己生成的桥方法,它的参数类型是Object,也就是说,子类中真正覆盖/重写父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法

5、泛型在静态类和静态方法中的问题

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

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

因为这是一个 泛型方法,在泛型方法中使用的T是自己在方法中定义的T,而不是 泛型类中的 T。调用这个方法时会声明T类型的。
<think>好的,我现在要帮用户解答关于Java面试中的常见问题和知识点。首先,我需要回顾用户提供的引用内容,特别是引用[1]、[2]、[3]、[4],这些引用中提到了的基本概念、类擦除、通配符、方法、类、以及一些常见面试题。用户的需求是了解面试中常见的问题和相关知识点,所以需要把这些内容整理得清晰易懂,结构分明。 首先,我应该从最基础的问题开始,比如什么是,使用的好处是什么。根据引用[2]和[3],提供了类安全,避免了类转换,同时提高了代码的可读性和重用性。这里需要强调类擦除,因为这是Java的实现机制,可能在面试中被深入提问。 接下来,用户可能想知道如何编写方法和类。引用[4]中的示例代码展示了类Box<T>和方法printArray,这可以作为具体例子来说明。需要解释方法的定义方式,比如使用<E>在方法返回类之前。 然后是关于通配符的问题,包括限定通配符(如<? extends T>和<? super T>)和非限定通配符<?>。根据引用[2],PECS原则(Producer Extends, Consumer Super)需要解释清楚,并举例子说明何时使用extends和super。 另外,数组中使用的问题,引用[2]提到数组不能使用,因为类擦除会导致运行时类信息丢失,而数组需要知道具体的类信息。这一点需要明确说明,可能涉及到为什么List可以而数组不行。 类擦除是另一个重点,引用[3]提到在编译时被擦除,运行时无法获取类参数的信息。需要解释类擦除带来的限制,比如不能进行instanceof检查,以及如何影响重载方法。 还有关于List<String>和原始类List的区别,引用[2]和[3]都提到了类安全的问题,原始类不会进行类检查,而在编译时检查。需要强调类安全的重要性,以及如何避免ClassCastException。 可能还需要提到的一些限制,比如不能使用基本类作为类参数,必须使用包装类。引用[2]中的问题14提到了这一点,所以需要解释原因,即在编译后变为Object,而基本类无法继承Object。 此外,静态上下文中使用的限制,比如静态变量不能引用参数,因为类参数属于实例级别,而静态成员属于类级别。这点在引用[2]的问题16中提到,需要举例说明。 还有关于在继承中的表现,比如List<String>并不是List<Object>的子类,这可能导致一些常见的错误理解,需要澄清。同时,通配符的使用可以增加灵活性,比如List<? extends Number>可以接受List<Integer>或List<Double>。 最后,需要整理相关问题,确保覆盖用户可能关心的其他方面,比如类擦除的细节、通配符的应用场景、与数组的对比、以及实际编程中的注意事项。 在组织内容时,要保持结构清晰,分点回答,每个问题先解释概念,再给出示例或应用场景,必要时引用提供的资料中的例子。同时,语言要简洁明了,符合面试准备的实用性,帮助用户快速掌握重点。</think>### Java面试核心知识点解析 #### 一、基础概念 1. **什么是?** 是JDK5引入的**类参数化机制**,允许在类、接口、方法中使用类参数(如`List<T>`)。通过,编译器可在编译时检查类安全性,避免运行时的`ClassCastException`[^3]。 2. **的好处** - **类安全**:编译时检测类错误,如`List<String>`只能添加字符串[^2] - **消除强制转换**:如从集合中获取元素时无需显式转换 - **代码复用**:同一套逻辑可处理多种数据类(如方法`printArray`)[^4] #### 二、实现机制 1. **类擦除** 信息在编译后被擦除,运行时仅保留原始类。例如`List<String>`和`List<Integer>`在JVM中均为`List`[^3]。 ```java // 编译前 List<String> list = new ArrayList<>(); // 编译后 List list = new ArrayList(); ``` 2. **桥方法(Bridge Method)** 用于解决擦除后多态性冲突的问题。例如子类重写父类方法时,编译器会生成桥方法保证类正确性。 #### 三、核心语法与应用 1. **类与方法** - **类**:通过类参数定义类(如`Box<T>`)[^4] - **方法**:在方法返回值前声明类参数(如`<E> void printArray(E[] array)`)[^4] 2. **通配符与边界** - **`<?>`(非限定通配符)**:表示未知类,如`List<?>`可接受任何的List[^2] - **`<? extends T>`(上界通配符)**:接受T或其子类,遵循**PECS原则(Producer Extends)** - **`<? super T>`(下界通配符)**:接受T或其父类,遵循**PECS原则(Consumer Super)** ```java // 示例:使用上界通配符读取数据 void process(List<? extends Number> list) { Number num = list.get(0); // 安全读取 } ``` #### 四、常见面试问题解析 1. **为什么数组不能使用?** 数组需要明确的类信息(如`new Integer[10]`),而在运行时被擦除,导致无法保证类安全[^2]。 2. **`List<String>`与原始类`List`的区别?** - `List<String>`:编译时检查元素类,插入非String对象会报错 - `List`:绕过类检查,可能引发运行时异常[^3] 3. **如何编写方法?** ```java public static <T> T getFirst(List<T> list) { return list.get(0); } ``` 4. **擦除导致的问题** - **无法使用`instanceof`检查**:如`if (list instanceof List<String>)`会编译报错 - **无法重载参数类的方法**:如`void method(List<String> list)`和`void method(List<Integer> list)`会被擦除为相同签名[^3] #### 五、实际应用场景 1. **集合框架**:如`ArrayList<E>`、`HashMap<K,V>` 2. **通用工具类**:如`Collections.sort()`通过支持多种数据类 3. **设计模式**:工厂模式、策略模式中通过解耦逻辑与类[^1] #### 六、注意事项与限制 1. **基本类不可用**:参数必须是包装类(如`List<Integer>`而非`List<int>`)[^2] 2. **静态上下文限制**:静态变量不能引用参数(如`class Box<T> { static T t; }`非法) 3. **类擦除的影响**:无法在运行时获取信息(如通过反射获取`T`的实际类) ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值