1. 引言
泛型是Java的核心概念,于Java 5首次引入。几乎所有Java代码库都会使用泛型,开发者几乎必然会在工作中遇到它。因此正确理解泛型至关重要,这也是面试中高频考点的原因。
2. 问题
2.1 Q1. 什么是泛型类型参数?
类型指的是类或接口的名称。泛型类型参数就是将类型作为参数使用在类、方法或接口声明中。
先看一个非泛型示例:
public interface Consumer {
public void consume(String parameter)
}
这里consume()
方法的参数类型固定为String
,不可配置。
现在用泛型类型T
(按惯例命名)替换String
:
public interface Consumer<T> {
public void consume(T parameter)
}
实现时可以指定具体类型:
public class IntegerConsumer implements Consumer<Integer> {
public void consume(Integer parameter)
}
这样就能灵活处理不同类型的数据。
2.2 Q2. 使用泛型有哪些优势?
主要优势包括:
- ✅ 避免强制类型转换,提供编译时类型安全
- ✅ 消除代码重复,实现通用算法
以集合操作为例:
// 非泛型写法
List list = new ArrayList();
list.add("foo");
Object o = list.get(0);
String foo = (String) o; // 需要强制转换
如果误添加了Integer
:
list.add(1);
String foo = (String) list.get(0); // 运行时抛出ClassCastException
使用泛型后:
List<String> list = new ArrayList<>();
list.add("foo");
String o = list.get(0); // 无需转换
Integer foo = list.get(0); // 编译错误,直接暴露问题
泛型在编译时就能发现类型错误,避免运行时异常。
2.3 Q3. 什么是类型擦除?
⚠️ 类型擦除指泛型类型信息在运行时对JVM不可用,仅存在于编译阶段。
实现原理:
- 泛型类型被替换为
Object
- 有界类型被替换为第一个边界类
- 插入隐式类型转换
这样设计是为了保持与Java 5之前版本的向后兼容。开发者常犯的错误是试图在运行时获取泛型类型:
public void foo(Consumer<T> consumer) {
Type type = consumer.getGenericTypeParameter() // 编译错误
}
泛型类型在运行时完全不可用,反射也无法获取。
2.4 Q4. 省略泛型类型参数还能编译吗?
可以编译,但会收到编译器警告:
List list = new ArrayList(); // 警告:未检查类型
虽然向后兼容允许这种写法,但强烈建议不要省略泛型参数,否则会失去类型安全检查。
2.5 Q5. 泛型方法与泛型类型有何区别?
泛型方法在方法内部引入类型参数,作用域仅限于该方法:
public static <T> T returnType(T argument) {
return argument;
}
与泛型类不同,泛型方法可以独立于类存在,且能利用类型推断简化调用。
2.6 Q6. 什么是类型推断?
类型推断指编译器根据方法参数自动推断泛型类型:
Integer inferredInteger = returnType(1); // 推断T为Integer
String inferredString = returnType("String"); // 推断T为String
无需显式指定类型参数,代码更简洁。
2.7 Q7. 什么是有界类型参数?
有界类型参数限制泛型参数必须满足特定条件:
public abstract class Cage<T extends Animal> {
abstract void addAnimal(T animal)
}
这里T
必须是Animal
的子类:
Cage<Cat> catCage; // 合法
Cage<Object> objectCage; // 编译错误,Object不是Animal子类
优势:
- 可访问边界类的方法
- 实现通用算法:
public void firstAnimalJump() { T animal = animals.get(0); animal.jump(); // 所有Animal子类都有jump方法 }
2.8 Q8. 可以声明多个有界类型参数吗?
可以,使用&
连接多个边界:
public abstract class Cage<T extends Animal & Comparable>
要求:
- 类型必须同时满足所有边界
- 类边界必须在前(
Animal
在前,Comparable
在后)
2.9 Q9. 什么是通配符类型?
通配符?
表示未知类型:
public static void consumeListOfWildcardType(List<?> list)
可接受任何类型的列表,但无法添加元素(除null
外)。
2.10 Q10. 什么是上界通配符?
上界通配符? extends T
限制类型为T或其子类,解决集合继承问题:
public class Farm {
public void addAnimals(Collection<? extends Animal> newAnimals) {
animals.addAll(newAnimals);
}
}
现在可以接受Animal
的任何子类集合:
farm.addAnimals(cats); // 合法
farm.addAnimals(dogs); // 合法
而原始写法会编译错误:
public void addAnimals(Collection<Animal> newAnimals) {
// 只能接受Animal集合,不接受子类集合
}
2.11 Q11. 什么是无界通配符?
无界通配符?
表示任何类型,但不同于Object
:
List<?> wildcardList = new ArrayList<String>(); // 合法
List<Object> objectList = new ArrayList<String>(); // 编译错误
关键区别:
List<?>
可接受任何具体类型的列表List<Object>
只能接受Object
类型的列表
2.12 Q12. 什么是下界通配符?
下界通配符? super T
限制类型为T或其超类:
public static void addDogs(List<? super Animal> list) {
list.add(new Dog("tom")); // 可添加Dog(Animal子类)
}
可接受Animal
及其超类:
ArrayList<Object> objects = new ArrayList<>();
addDogs(objects); // 合法,Object是Animal超类
但不可接受子类:
ArrayList<Cat> cats = new ArrayList<>();
addDogs(cats); // 编译错误,Cat不是Animal超类
2.13 Q13. 何时选择下界或上界通配符?
遵循PECS原则:
- Producer Extends:生产者使用
? extends T
public static void makeLotsOfNoise(List<? extends Animal> animals) { animals.forEach(Animal::makeNoise); // 只读取不修改 }
- Consumer Super:消费者使用
? super T
public static void addCats(List<? super Animal> animals) { animals.add(new Cat()); // 只添加不读取 }
当集合既是生产者又是消费者时,使用无界通配符?
。
2.14 Q14. 泛型类型信息在运行时可用吗?
仅有一种情况:当泛型是类签名的一部分时:
public class CatCage implements Cage<Cat>
可通过反射获取:
(Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
但这种方式很脆弱,依赖具体类层次结构。大多数情况下泛型信息在运行时不可用。