说说对泛型的理解?
在Java开发过程中,泛型(Generics) 是一个非常基础且重要的语法特性。它不仅提升了代码的复用性与类型安全,还大大增强了集合、工具类等容器的灵活性。本文将结合理论和详细代码示例,从泛型的原理、常见用法、PECS原则等方面,系统讲解什么是Java泛型,以及如何正确高效地使用泛型。
一、什么是泛型
泛型,顾名思义,就是“参数化类型”。通俗的说,泛型允许在定义类、接口、方法时,将类型作为参数进行传递,使代码可以应用于多种不同的类型。
本质作用:
- 让同一份代码适配不同类型的数据
- 在编译期提供类型检查,避免类型转换异常
例子:没有泛型的集合(JDK1.5之前)
List list = new ArrayList();
list.add("string");
list.add(123); // 没有任何报错
// 取出元素需要强转
String str = (String) list.get(0); // 正常
String str2 = (String) list.get(1); // 运行时报ClassCastException
例子:有泛型的集合
List<String> list = new ArrayList<>();
list.add("string");
// list.add(123); // 编译报错!
String str = list.get(0); // 不需要强转,类型安全
二、泛型的基本使用
1. 泛型类
public class Box<T> {private T value;public void setValue(T value) { this.value = value; }public T getValue() { return value; }
}public static void main(String[] args) {Box<Integer> intBox = new Box<>();intBox.setValue(123);int i = intBox.getValue(); // 自动类型检查,无需强转Box<String> strBox = new Box<>();strBox.setValue("hello");String s = strBox.getValue(); // 类型安全
}
2. 泛型方法
public class Utils {// 泛型方法声明在返回值前加 <T>public static <T> void printArray(T[] array) {for (T t : array)System.out.println(t);}
}public static void main(String[] args) {Integer[] nums = {1, 2, 3};String[] strs = {"a", "b"};Utils.printArray(nums); // 自动识别T为IntegerUtils.printArray(strs); // 自动识别T为String
}
3. 泛型接口
public interface Converter<F, T> {T convert(F from);
}public class StringToIntegerConverter implements Converter<String, Integer> {@Overridepublic Integer convert(String from) {return Integer.valueOf(from);}
}
三、通配符 <?> 详解
1. 基础通配符
<?>
代表未知类型。例如,可以接收任意类型的List:
public static void printList(List<?> list) {for(Object o : list) System.out.println(o);
}
2. 上界通配符(extends)
<? extends Number>
代表某种Number的子类型,常用于只读(取数据)场景。
public static void sumList(List<? extends Number> numbers) {double sum = 0;for (Number n : numbers) sum += n.doubleValue();System.out.println("Sum: " + sum);
}
3. 下界通配符(super)
<? super Integer>
代表Integer或其父类类型,常用于只写(存数据)场景。
public static void addIntegers(List<? super Integer> list) {list.add(1);list.add(2);// 不能读取到具体类型(只能当Object用)
}
口诀:
- Producer Extends(PE) —— 只从集合取数据用 extends
- Consumer Super(CS) —— 只向集合存数据用 super
四、泛型常见应用和典型代码
1. 类型擦除现象
Java的泛型是“伪泛型”,编译后会进行类型擦除、转为原始类型。例如:
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();System.out.println(stringList.getClass() == intList.getClass()); // true
意味着“泛型信息只存在于编译期,运行时被擦除”。
2. 泛型不能用于静态变量
public class Demo<T> {// static T field; // 编译不通过,静态变量无法使用泛型类型
}
3. 数组与泛型
// T[] arr = new T[10]; // 错误,不能直接创建泛型数组
Object[] arr = new Integer[10]; // 可行,但存在类型转型风险
五、实战代码解析:泛型工具类与PECS用法
以集合复制为例,演示PECS原则:
// 只读 - 泛型上界
public static double sum(List<? extends Number> list) {double result = 0;for (Number n : list) result += n.doubleValue();return result;
}
// 只写 - 泛型下界
public static <T> void copy(List<? super T> dest, List<T> src) {for (T t : src)dest.add(t);
}public static void main(String[] args) {List<Integer> src = Arrays.asList(1, 2, 3);List<Number> dest = new ArrayList<>();copy(dest, src); // dest可以是Number,可以是Object...System.out.println(sum(dest));
}
六、泛型的好处与注意事项
优点:
- 类型安全,编译阶段预防类型错误
- 代码复用,同一结构适配多种类型
- 可读性强,类型清晰可见、减少强解
注意事项:
- 泛型类型在运行时会被擦除(无实例化类型),不能用
==
或instanceof
判断泛型参数类型 - 泛型类不能直接使用基本类型(如
Box<int>
会报错,只能用Box<Integer>
) - 静态上下文(static变量和方法)不能引用类的泛型参数