泛型
泛型基础
什么是泛型?
答案: 泛型(Generics)是JDK5引入的特性,允许在定义类、接口、方法时使用类型参数,提供编译时类型安全检查。
优点:
- 类型安全:编译时检查类型
- 消除强制转换:不需要手动转型
- 代码复用:一套代码适用多种类型
示例:
java
// 不使用泛型
List list = new ArrayList();
list.add("hello");
list.add(123);
String str = (String) list.get(0); // 需要强制转换
String str2 = (String) list.get(1); // 运行时ClassCastException
// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译错误
String str = list.get(0); // 不需要转换泛型的命名规范?
答案:
| 字母 | 含义 |
|---|---|
| E | Element(元素) |
| T | Type(类型) |
| K | Key(键) |
| V | Value(值) |
| N | Number(数字) |
| ? | 通配符 |
泛型类
如何定义泛型类?
答案:
java
// 单个类型参数
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 使用
Box<String> box1 = new Box<>();
box1.set("hello");
String value1 = box1.get();
Box<Integer> box2 = new Box<>();
box2.set(123);
Integer value2 = box2.get();
// 多个类型参数
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
// 使用
Pair<String, Integer> pair = new Pair<>("age", 20);泛型接口
如何定义泛型接口?
答案:
java
// 泛型接口
public interface Generator<T> {
T generate();
}
// 实现方式1:指定具体类型
public class StringGenerator implements Generator<String> {
@Override
public String generate() {
return "hello";
}
}
// 实现方式2:保留泛型
public class GenericGenerator<T> implements Generator<T> {
@Override
public T generate() {
// 实现
return null;
}
}
// 使用
Generator<String> gen1 = new StringGenerator();
Generator<Integer> gen2 = new GenericGenerator<>();泛型方法
如何定义泛型方法?
答案:
java
// 泛型方法
public class GenericMethod {
// 静态泛型方法
public static <T> T getMiddle(T... args) {
return args[args.length / 2];
}
// 普通泛型方法
public <T> void print(T value) {
System.out.println(value);
}
// 多个类型参数
public <K, V> void put(K key, V value) {
System.out.println(key + " = " + value);
}
}
// 使用
String middle = GenericMethod.getMiddle("a", "b", "c");
GenericMethod gm = new GenericMethod();
gm.print("hello");
gm.print(123);
gm.put("name", "Tom");注意:
- 泛型方法的类型参数在返回值前声明
- 泛型方法可以在普通类中定义
- 泛型方法的类型参数与类的类型参数无关
java
public class Box<T> {
private T value;
// 这不是泛型方法,T是类的类型参数
public void set(T value) {
this.value = value;
}
// 这是泛型方法,E是方法的类型参数
public <E> void print(E element) {
System.out.println(element);
}
}泛型通配符
泛型通配符有哪些?
答案:
1. 无界通配符 <?>
java
public void print(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 可以接受任何类型的List
print(new ArrayList<String>());
print(new ArrayList<Integer>());2. 上界通配符 <? extends T>
java
// 只能读取,不能写入(除了null)
public double sum(List<? extends Number> list) {
double total = 0;
for (Number num : list) {
total += num.doubleValue();
}
return total;
}
// 可以接受Number及其子类
sum(new ArrayList<Integer>());
sum(new ArrayList<Double>());
// sum(new ArrayList<String>()); // 编译错误3. 下界通配符 <? super T>
java
// 只能写入,读取只能用Object接收
public void addNumbers(List<? sger> list) {
list.add(1);
list.add(2);
// Integer num = list.get(0); // 编译错误
Object obj = list.get(0); // 只能用Object接收
}
// 可以接受Integer及其父类
addNumbers(new ArrayList<Integer>());
addNumbers(new ArrayList<Number>());
addNumbers(new ArrayList<Object>());PECS原则是什么?
答案: PECS:Producer Extends, Consumer Super
- Producer Extends:如果需要从集合中读取数据(生产者),使用
<? extends T> - Consumer Super:如果需要向集合中写入数据(消费者),使用
<? super T>
java
// 生产者:读取数据
public void copy(List<? extends Number> src, List<? super Number> dest) {
for (Number num : src) { // 从src读取
dest.add(num); // 向dest写入
}
}
// 使用
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> dest = new ArrayList<>();
copy(src, dest);泛型擦除
什么是泛型擦除?
答案: Java的泛型是伪泛型,在编译时会进行类型擦除,运行时不保留泛型信息。
擦除规则:
- 无界类型
<T>擦除为Object - 有界类型
<T extends Number>擦除为Number - 保留第一个边界
java
// 编译前
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 编译后(类型擦除)
public class Box {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}验证:
java
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
// 运行时都是ArrayList,泛型信息被擦除泛型擦除的影响?
答案:
1. 不能创建泛型数组
java
// 编译错误
List<String>[] array = new ArrayList<String>[10];
// 可以这样
List<?>[] array = new ArrayList<?>[10];
List<String>[] array = new ArrayList[10];2. 不能使用instanceof
java
// 编译错误
if (obj instanceof List<String>) {
}
// 可以这样
if (obj instanceof List<?>) {
}3. 不能创建泛型实例
java
public class Box<T> {
// 编译错误
public void create() {
T obj = new T();
}
// 可以通过反射
public T create(Class<T> clazz) throws Exception {
return clazz.newInstance();
}
}4. 静态方法不能使用类的泛型
java
public class Box<T> {
// 编译错误
public static void print(T value) {
}
// 可以定义泛型方法
public static <E> void print(E value) {
}
}5. 不能重载
java
public class Box {
// 编译错误:擦除后方法签名相同
public void print(List<String> list) {
}
public void print(List<Integer> list) {
}
}泛型最佳实践
泛型使用建议?
答案:
1. 优先使用泛型
java
// 不推荐
List list = new ArrayList();
// 推荐
List<String> list = new ArrayList<>();2. 使用菱形语法
java
// JDK7之前
List<String> list = new ArrayList<String>();
// JDK7+
List<String> list = new ArrayList<>();3. 返回值使用具体类型
java
// 不推荐
public List<?> getList() {
return new ArrayList<String>();
}
// 推荐
public List<String> getList() {
return new ArrayList<>();
}4. 合理使用通配符
java
// 只读取
public void print(List<? extends Number> list) {
}
// 只写入
public void add(List<? super Integer> list) {
}5. 避免原始类型
java
// 不推荐
List list = new ArrayList();
// 推荐
List<Object> list = new ArrayList<>();练习题
- 泛型的类型擦除是什么?有什么影响?
List<?>和List<Object>的区别?- 为什么不能创建泛型数组?
- 如何获取泛型的实际类型?