Java基础夯实——泛型全解析
泛型是一种允许定义类,接口,方法时不预先加载指定的具体类型,而是使用时确定的一种特性
目录
使用情景
泛型类
泛型接口
泛型方法
使用规范
不能使用基本数据类型
不能在静态字段和方法中使用泛型
通配符与边界
无界通配符:? 表示位置类型
上界通配符:?extends 类型 表示限定指定的泛型类型必须是extends关键字后类型或其子类
下界通配符:?super 类型 表示限定指定的泛型类型必须是super关键字后类型或其父类
核心作用
类型安全
消除强制类型转换
代码复用
实现机制:泛型擦除
擦除过程核心规则(技术原理)
无界类型参数:直接在编译阶段被擦除为Object
上界单一边界类型参数:擦除为上界类型
上界多边界类型参数:擦除为第一个边界
下界类型参数:擦除为最具体的合法类型
桥接方法生成机制
擦除机制导致的影响
泛型类型在运行时不能访问
无法创建泛型实例或数组
使用情景
泛型类
泛型类是指带有类型参数的类
//泛型类的创建
public class 类名<泛型类型1,泛型类型2,……>{//类的成员
}
其中,<>中的泛型类型可以有多个,用逗号分隔。这些泛型类型在类被使用时会被具体的类型所替代
//泛型类——例
//泛型类定义
public class Box<T> {private T content;public void set(T content) {this.content = content;}public T get() {return content;}
}
//泛型类使用
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String content = stringBox.get(); // 无需强制类型转换Box<Integer> integerBox = new Box<>();
integerBox.set(100);
Integer number = integerBox.get(); // 无需强制类型转换
泛型接口
泛型接口是指带有参数类型的接口
//泛型接口的创建
public interface 接口名<泛型类型1,泛型类型2,……>{// 接口方法
}
在实现泛型接口时可以指定泛型类型,也可以保留泛型,用泛型类实现泛型接口
//泛型接口——例
//泛型接口定义
public interface Generator<T> {T generate();
}
//泛型接口指定泛型类型的实现类
public class StringGenerator implements Generator<String> {@Overridepublic String generate() {return "Generated String";}
}
//泛型接口保留泛型的实现类
public class NumberGenerator<T extends Number> implements Generator<T> {private T number;public NumberGenerator(T number) {this.number = number;}@Overridepublic T generate() {return number;}
}
泛型方法
泛型方法是指使用泛型参数的方法,可以在普通类或者泛型类中定义,也可以在泛型类中定义
//泛型方法的定义
public <泛型类型1, 泛型类型2, ...> 返回类型 方法名(参数列表) {// 方法体
}
泛型方法会在调用时指定具体的类型
//泛型方法——例
public class GenericMethodExample {public static <T> void printArray(T[] array) {for (T element : array) {System.out.print(element + " ");}System.out.println();}public static void main(String[] args) {Integer[] intArray = {1, 2, 3, 4, 5};String[] stringArray = {"Hello", "World"};printArray(intArray); // 输出: 1 2 3 4 5printArray(stringArray); // 输出: Hello World}
}
使用规范
不能使用基本数据类型
泛型类型的参数不能是基本数据类型,必须使用应用数据类型(int等基本数据类型需要使用对应的包装类,如Integer)
不能在静态字段和方法中使用泛型
由于泛型类和类的泛型字段都需要在实例化的时候给定,在静态字段或者方法中使用泛型的话可能在完全没有给定具体类型的情况下调用方法或字段,明显不符合泛型设计,所以被禁止使用
通配符与边界
类型通配符是在使用泛型时表示未知类型的一种方式,用 ? 表示
配合 extends 或者 super 关键字可以对其上界或者下界进行限定,主要有三种形式:
无界通配符:? 表示位置类型
上界通配符:?extends 类型 表示限定指定的泛型类型必须是extends关键字后类型或其子类
下界通配符:?super 类型 表示限定指定的泛型类型必须是super关键字后类型或其父类
//通配符使用——例
import java.util.ArrayList;
import java.util.List;public class WildcardExample {// 无界通配符public static void printList(List<?> list) {for (Object element : list) {System.out.print(element + " ");}System.out.println();}// 上界通配符public static double sumOfList(List<? extends Number> list) {double sum = 0.0;for (Number n : list) {sum += n.doubleValue();}return sum;}// 下界通配符public static void addNumbers(List<? super Integer> list) {for (int i = 1; i <= 5; i++) {list.add(i);}}public static void main(String[] args) {List<Integer> intList = List.of(1, 2, 3);List<Double> doubleList = List.of(1.5, 2.5, 3.5);printList(intList); // 输出: 1 2 3printList(doubleList); // 输出: 1.5 2.5 3.5System.out.println(sumOfList(intList)); // 输出: 6.0System.out.println(sumOfList(doubleList)); // 输出: 7.5List<Object> objectList = new ArrayList<>();addNumbers(objectList);System.out.println(objectList); // 输出: [1, 2, 3, 4, 5]}
}
核心作用
类型安全
通过类型参数化把类型检查提前到编译阶段,确保只允许正确类型的对象被添加到泛型集合或传递给泛型方法,减少运行时会出现的ClassCastException异常
//类型安全——例
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译错误:无法将 Integer 放入 List<String>
String str = list.get(0); // 无需强制转换,编译时已确保类型安全
消除强制类型转换
泛型允许编译器自动推断类型,避免手动编写强制类型转换代码
类型参数在编译后被擦除,但编译器会在生成字节码时插入必要的类型转换
//消除强制类型转换——例
Map<String, String> map = new HashMap<>();
map.put("key", "value");
String value = map.get("key"); // 无需强制转换,编译器自动处理
代码复用
泛型通过参数化类型实现通用算法,使同一个类或方法可以处理多种数据类型,无需为每种类型重复编写代码
//代码复用——例
//通用数组交换方法例
public static <T> void swap(T[] array, int i, int j) {T temp = array[i];array[i] = array[j];array[j] = temp;
}
// 使用示例
Integer[] intArray = {1, 2, 3};
swap(intArray, 0, 2); // 交换 Integer 数组
String[] strArray = {"a", "b", "c"};
swap(strArray, 0, 2); // 同一方法处理 String 数组//通用泛型实现容器
public class Box<T> {private T content;public void set(T content) { this.content = content; }public T get() { return content; }
}
// 使用示例
Box<Integer> intBox = new Box<>();
intBox.set(100);
Box<String> strBox = new Box<>();
strBox.set("hello");
实现机制:泛型擦除
泛型擦除是Java泛型实现的核心机制,它在编译阶段移除了所有泛型的类型信息,使代码在运行使与非泛型版本保持兼容
擦除过程核心规则(技术原理)
无界类型参数:直接在编译阶段被擦除为Object
//无界类型参数擦除
class Box<T> {private T value; // 擦除后变为 private Object value;
}
上界单一边界类型参数:擦除为上界类型
//上界类型参数擦除
class Box<T extends Number> {private T value; // 擦除后变为 private Number value;
}
上界多边界类型参数:擦除为第一个边界
//多边形类型参数擦除
class Box<T extends Comparable<T> & Serializable> {private T value; // 擦除后变为 private Comparable<T> value;
}
下界类型参数:擦除为最具体的合法类型
//下界类型参数擦除
void addNumbers(List<? super Integer> list) {list.add(10); // 擦除后:list.add(Object obj)
}
这里的 <? super Integer> 被擦除为Object,因为 super Integer 表示可接收 Integer 、Number 、Object 等类型,只有 Object 可以覆盖全部类型
但编译器会在调用 add 方法时确保参数类型为 Integer 或其子类,不论你实现的时候指定的是 Number 还是 Object ,因为编译器不关心你实现时指定是具体类型,只确保写入元素的绝对安全性(真正起到减少ClassCastException异常的作用)
所以就算你实现时指定的是 Number 类型,也不能添加非 Integer 极其子类以外类型的数据(如 Doube),即使他们满足你指定的 Number 类型
桥接方法生成机制
当泛型接口被实现时,可能由于泛型擦除导致方法签名冲突:
//泛型擦除导致的方法签名冲突
interface Comparable<T> {int compareTo(T o);
}
class MyString implements Comparable<String> { //这里的String编译后被擦除为Object类,而compareTo方法的String o参数不改变,导致签名冲突@Overridepublic int compareTo(String o) { return 0;}
}
为了解决此冲突,编译器会在编译时自动生成桥接方法,将Object的o对象转换成符合方法参数的String类型:
//编译后代码
class MyString implements Comparable {// 用户实现的方法public int compareTo(String o) { return 0; }// 编译器生成的桥接方法public synthetic bridge int compareTo(Object o) {return compareTo((String) o);}
}
擦除机制导致的影响
泛型类型在运行时不能访问
//不能访问——例
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();System.out.println(stringList.getClass() == intList.getClass()); // true
// 编译是泛型类型都自动擦除为Object类,返回类型均为 ArrayList.class
无法创建泛型实例或数组
由于擦除后的泛型实例构造方法完全等价于Object类构造方法,明显不合预期,所以实例化泛型类型被禁止
由于擦除后的泛型数组类型信息丢失,可能导致加入类型冲突的安全问题,所以泛型数组的创建也被禁止
要获取实例化的泛型类型只能通过class<T>反射得到
//无法创建
public class Factory<T> {// 错误:无法创建泛型数组// private T[] array = new T[10];// 错误:无法实例化泛型类型// public T create() { return new T(); }// 正确做法:通过 Class<T> 实例化public T create(Class<T> clazz) throws Exception {return clazz.getDeclaredConstructor().newInstance();}
}