模式切换
泛型
泛型的出现使得类、接口、方法可以接受任意类型的参数,提高了代码的复用性,解决类型安全问题,提供了编译时类型检查,不会因为将对象置于某个容器中而失去其类型。在 Java 集合框架中,泛型被广泛应用,如 ArrayList<T>
、HashMap<K, V>
等。
在没有出现泛型之前,Java 也提供了对 Object 引用进行向上转型及向下转型的操作,使得对 Object 的引用可以“任意化”操作。但某些强制类型转换的错误也许不会被编译器捕捉,而在运行后出现异常,并且类型转换是需要消耗资源的。可见强制类型转换是不安全的,因此在 Java 1.5 中引入泛型机制。
例如在下面示例的写法中存在类型安全问题,容易导致 ClassCastException
:
java
ArrayList list = new ArrayList();
list.add("Hello");
list.add(123); // 可能导致运行时错误
String str = (String) list.get(0); // 需要手动强制转换
泛型分类
根据泛型的使用范围,泛型可以分为泛型类、泛型接口和泛型方法。
泛型类
Java 泛型可以用来定义类,使其能够适用于不同的数据类型。
语法:
java
public class ClassName<T> {
// ...
}
定义泛型类时,一般类型名称使用 T
表示,而容器的元素则使用 E
表示。
示例:
java
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
java
Box<String> b1 = new Box<String>();
box.setValue("Hello");
String str = b1.getValue(); // 不需要强制转换
Box<Integer> b2 = new Box<Integer>();
box.setValue(123);
int num = b2.getValue(); // 不需要强制转换
泛型接口
泛型接口的定义与泛型类类似,只是在接口名后面添加泛型声明。
语法:
java
public interface InterfaceName<T> {
// ...
}
示例:
java
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
java
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
java
Pair<String, Integer> pair = new OrderedPair<>("Alice", 25);
泛型方法
泛型方法可以在方法中使用泛型类型,使得方法能够适用于不同的数据类型。
语法:
java
public <T> void methodName(T t) {
// ...
}
示例:
java
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
java
Integer[] intArray = {1, 2, 3};
String[] strArray = {"Hello", "World"};
printArray(intArray);
printArray(strArray);
泛型的常规用法
声明多个泛型
java
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;
}
}
java
Pair<String, Integer> pair = new Pair<>("Alice", 25);
声明数组类型
java
public class Box<T> {
private T[] value;
public void setValue(T[] value) {
this.value = value;
}
public T[] getValue() {
return value;
}
}
java
Box<String> box = new Box<>();
box.setValue(new String[] {"Alice", "Bob", "Charlie"});
声明容器元素
使用 K
、V
代表容器中的键值对:
java
public class MutiMap<K, V> {
private Map<K, List<V>> map = new HashMap<>();
public void put(K key, V value) {
List<V> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(value);
}
public List<V> get(K key) {
return map.get(key);
}
}
java
MutiMap<String, Integer> map = new MutiMap<>();
map.put("Alice", 25);
List<Integer> list = map.get("Alice");
表 常用的被泛型化的集合类
泛型通配符
未知类型通配符
通配符 ?
表示未知类型,用于增强泛型的灵活性。通配符主要用于泛型类、泛型方法和泛型集合,使代码可以适用于不同类型的泛型对象。
通配符 ?
可以在泛型方法或泛型类的参数中表示任意类型,但不能用于泛型类的声明。例如下面的示例中使用普通泛型和通配符的对比:
使用具体类型的泛型
javaList<String> list = new ArrayList<>(); list.add("Hello");
使用通配符
List<?>
可以引用任何类型的泛型List<T>
,但不能添加元素(除了null
),因为编译器无法确定?
的实际类型。javaList<?> list = new ArrayList<String>(); list = new ArrayList<Integer>(); // 允许不同泛型类型的对象
上界通配符
上界通配符(? extends UpperBound
):? extends UpperBound
限定泛型只能是 UpperBound
或 UpperBound
的子类,主要用于读取数据。
图 上界通配符的使用示意图
示例:
java
public static void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
java
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
printNumbers(intList); // ✔ 允许
printNumbers(doubleList); // ✔ 允许
特点:
- 允许
List<Integer>
、List<Double>
等Number
子类列表作为参数传递。 - 不能向
list
添加新元素(null
除外),因为无法确定具体类型:javalist.add(100); // ❌ 编译错误 list.add(null); // ✔ 允许
- 适用于只读操作,如遍历、计算等。
下界通配符
下界通配符(? super LowerBound
):? super LowerBound
限定泛型只能是 LowerBound
或 LowerBound
的父类,主要用于写入数据。
图 下界通配符的使用示意图
示例:
java
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
}
java
List<Number> numberList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
addNumbers(numberList); // ✔ 允许
addNumbers(objectList); // ✔ 允许
特点:
- 允许向
list
添加Integer
或其子类对象。 - 不能读取成
Integer
,因为list
可能是List<Object>
,只能赋值给Object
:javaObject obj = list.get(0); // ✔ 允许 Integer num = list.get(0); // ❌ 编译错误
- 适用于写入操作,如添加数据。
泛型擦除
泛型擦除是 Java 泛型实现中的一种机制,旨在确保泛型代码与旧版本的非泛型代码兼容。Java 在编译时使用泛型擦除来处理泛型类型信息,使得泛型代码在运行时与非泛型代码表现一致。
工作原理
类型参数替换:
- 无界类型参数(如
<T>
)会被替换为Object
。 - 有界类型参数(如
<T extends Number>
)会被替换为边界类型(如Number
)。
- 无界类型参数(如
类型检查和桥接方法:
- 编译器在编译时进行类型检查,确保类型安全。
- 生成桥接方法以保持多态性。
示例
无界类型参数
java
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
编译后:
java
public class Box {
private Object item;
public void setItem(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
}
有界类型参数
java
public class Box<T extends Number> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
编译后:
java
public class Box {
private Number item;
public void setItem(Number item) {
this.item = item;
}
public Number getItem() {
return item;
}
}
泛型擦除的影响
- 运行时类型信息丢失:泛型类型信息在编译后被擦除,运行时无法获取具体类型参数。
- 类型转换:编译器自动插入类型转换代码,确保类型安全。
- 无法创建泛型类型的实例:由于类型信息在运行时不可用,无法直接实例化泛型类型。
- 无法创建泛型数组:数组需要明确的类型信息,而泛型擦除导致无法创建泛型数组。
示例:类型转换
java
Box<Integer> box = new Box<>();
box.setItem(123);
Integer item = box.getItem(); // 编译器插入类型转换
编译后:
java
Box box = new Box();
box.setItem(123);
Integer item = (Integer) box.getItem(); // 自动插入的类型转换
桥接方法
桥接方法用于保持多态性,确保子类重写父类方法时类型安全。
java
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
public class IntegerBox extends Box<Integer> {
@Override
public void setItem(Integer item) {
super.setItem(item);
}
@Override
public Integer getItem() {
return super.getItem();
}
}
编译后,IntegerBox
会生成桥接方法:
java
public class IntegerBox extends Box {
public void setItem(Integer item) {
super.setItem(item);
}
// 桥接方法
public void setItem(Object item) {
setItem((Integer) item);
}
public Integer getItem() {
return (Integer) super.getItem();
}
// 桥接方法
public Object getItem() {
return getItem();
}
}