JavaSE基础——第十三章 泛型
泛型(Generics)是 Java 5 引入的一项重要特性,它允许在编译时指定类、接口或方法的数据类型,从而提高代码的类型安全性和重用性。泛型的核心目标是在编译时检测类型错误,避免运行时因类型转换导致的 ClassCastException。
1. 泛型的基本概念
(1) 为什么需要泛型?
在没有泛型之前,Java 使用 Object 类来实现通用容器(如 List、Map),但这种方式存在两个问题:
- 类型不安全:需要手动强制类型转换,容易引发运行时错误
- 代码冗余:需要为每种数据类型编写相似的代码

示例(无泛型的问题):
List list = new ArrayList();   // 泛型默认是Object
list.add("Hello");
list.add(123);  // 编译通过,但运行时可能出错String str = (String) list.get(1);  // 运行时抛出 ClassCastException
使用泛型改进后:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add(123);  // 编译时报错:类型不匹配
String str = list.get(0);  // 无需强制转换
(2) 泛型的作用
- 类型安全:编译时检查类型,避免运行时错误
- 消除强制类型转换:代码更简洁
- 提高代码复用性:同一套逻辑可适用于多种数据类型。
2. 泛型的使用场景
泛型可以用于以下三种情况:
- 泛型类(Generic Class)
- 泛型接口(Generic Interface)
- 泛型方法(Generic Method)
3. 泛型类(Generic Class)
泛型类是在类名后通过 <T> 声明类型参数,T 可以是任意标识符,只能是引用类型【例如只能是Integer,而不能是int】。
(1) 定义泛型类
public class Box<T> {private T content;public void setContent(T content) {this.content = content;}public T getContent() {return content;}
}
(2) 使用泛型类
Box<String> stringBox = new Box<>();   // 编译器会自动进行类型推断
stringBox.setContent("Hello");
String str = stringBox.getContent();  // 无需强制转换Box<Integer> intBox = new Box<>();
intBox.setContent(123);
int num = intBox.getContent();
4. 泛型接口(Generic Interface)
泛型接口与泛型类类似,在接口名后声明类型参数。
- ⭐接口中的成员都是final static修饰的,不能使用泛型类型
(1) 定义泛型接口
public interface Repository<T> {void save(T entity);T findById(int id);
}
(2) 实现泛型接口
泛型接口的类型,在继承接口或实现接口时确定,如果不指定类型的话则默认为Object类。
public class UserRepository implements Repository<User> {@Overridepublic void save(User user) { /* ... */ }@Overridepublic User findById(int id) { /* ... */ }
}
5. 泛型方法(Generic Method)
泛型方法是在方法返回值前声明类型参数,即使所在类不是泛型类,也可以定义泛型方法。
- 如果修饰符后没有<T,R…>,那么该方法不是泛型方法,而是使用了类声明的泛型;如果类没有声明,则不能使用
- 泛型方法,既可以使用类声明的泛型,也可以使用自己声明的泛型
(1) 定义泛型方法
public class ArrayUtils {public static <T> void printArray(T[] array) {   // 此处的泛型是方法级别的,与类的泛型无关for (T item : array) {System.out.print(item + " ");}}
}
(2) 调用泛型方法
Integer[] intArray = {1, 2, 3};
String[] strArray = {"A", "B", "C"};ArrayUtils.printArray(intArray);  // 输出: 1 2 3
ArrayUtils.printArray(strArray);  // 输出: A B C
6. 泛型通配符(Wildcards)
为了增强泛型的灵活性,Java 提供了通配符 ?,主要用于以下场景:
- <?>:无界通配符,表示任意类型。
- <? extends T>:上界通配符,表示- T或其子类。
- <? super T>:下界通配符,表示- T或其父类。
(1) 无界通配符 <?>
public void printList(List<?> list) {for (Object item : list) {System.out.println(item);}
}
(2) 上界通配符 <? extends T>
public double sum(List<? extends Number> numbers) {double total = 0;for (Number num : numbers) {total += num.doubleValue();}return total;
}
(3) 下界通配符 <? super T>
public void addNumbers(List<? super Integer> list) {list.add(1);list.add(2);
}
7. 泛型擦除(Type Erasure)
Java 的泛型是编译时特性,在运行时会被擦除,替换为原始类型(Object 或指定的上界)。
(1) 泛型擦除示例
// 编译前
List<String> list = new ArrayList<>();
list.add("Hello");// 编译后(泛型擦除)
List list = new ArrayList();  // 泛型被擦除
list.add("Hello");  // 实际存储的是 Object
String value = (String) list.get(0);  // 强制类型转换
(2) 泛型擦除的影响
- 无法在运行时获取泛型类型(如 - T.class会报错)- List<String> list = new ArrayList<>(); if (list instanceof List<String>) { // 编译错误!// ... }Class<T> clazz = T.class; // 编译错误!
- 不能创建泛型数组(如 - new T[10]会报错)- T[] array = new T[10]; // 编译错误!
解决方案:
- 通过反射或传递 - Class<T>参数绕过限制- public class Box<T> {private Class<T> type;private T value;public Box(Class<T> type) {this.type = type;}public boolean isInstance(Object obj) {return type.isInstance(obj); // 运行期检查类型} }Box<String> box = new Box<>(String.class); box.isInstance("Hello"); // true box.isInstance(123); // false
- 使用 - List代替数组(如- List<T>)
8. 泛型限制与注意事项
- 基本类型不能作为泛型参数(如 List<int>错误,需用List<Integer>)
- 不能实例化泛型类型(如 new T()错误)
- 泛型不具备继承性:List<Object> list = new ArrayList<String>();×
- 静态成员不能使用泛型类型(因为泛型属于实例)
- 泛型类型不能用于异常处理(如 catch (MyException<T> e)错误)

- 理解: - 为什么泛型数组不能直接初始化?- 在 Java 中,不能直接创建泛型数组,例如: - T[] array = new T[10]; // 编译错误!- 原因:泛型擦除(Type Erasure)- 运行期 T会被擦除成Object或边界类型:- 如果 T是无界泛型(如<T>),运行期会变成Object。
- 如果 T是有界泛型(如<T extends Number>),运行期会变成Number。
- 数组在运行期必须知道具体类型(如 String[]、Integer[]),但T被擦除后无法确定具体类型,导致无法安全创建数组。
- 在new时不能确定类型,就无法在内存中开空间。
 
- 如果 
- 数组是协变的(Covariant),泛型是不变的(Invariant):- 数组: - String[]是- Object[]的子类型,可以向上转型:- Object[] arr = new String[10]; // 合法
- 泛型: - List<String>不是- List<Object>的子类型:- List<Object> list = new ArrayList<String>(); // 编译错误!
- 如果允许 - new T[],可能会导致类型安全问题:- T[] arr = new T[10]; // 假设允许 Object[] objArr = arr; // 数组协变,可以向上转型 objArr[0] = 123; // 运行时可能抛出 ArrayStoreException(如果 T 是 String)
 
 - 如何绕过限制?- 使用 - Object[]并强制转换(不安全,需手动确保类型正确):- T[] array = (T[]) new Object[10]; // 编译警告:未检查的类型转换- 适用于无界泛型(<T>),但如果T是具体类型(如String),运行期可能出错。
 
- 适用于无界泛型(
- 通过反射创建数组(需要 - Class<T>参数):- @SuppressWarnings("unchecked") public <T> T[] createArray(Class<T> clazz, int size) {return (T[]) Array.newInstance(clazz, size); // 安全,但需要额外参数 }- 示例: - String[] strArr = createArray(String.class, 10); // 正确创建 String[]
 
 - 为什么静态方法不能使用类的泛型?- 在 Java 中,静态方法不能直接使用类的泛型参数,例如: - class Box<T> {public static void print(T value) { // 编译错误!System.out.println(value);} }- 原因:泛型属于实例,静态方法属于类- 泛型参数 T是实例级别的:- 每个 Box<String>和Box<Integer>的T不同。
- 但静态方法属于类(Box),不依赖实例,因此无法确定T的具体类型。
 
- 每个 
- 泛型擦除导致运行期无法区分:- 运行期 Box<String>和Box<Integer>的静态方法是同一个,无法知道T是String还是Integer。
 
- 运行期 
 - 如何解决?- 让静态方法自己声明泛型(与类的泛型无关): - class Box<T> {public static <U> void print(U value) { // 合法System.out.println(value);} }- 示例: - Box.print("Hello"); // U 推断为 String Box.print(123); // U 推断为 Integer
 
- 使用类泛型参数的变通方法(需传入 - Class<T>):- class Box<T> {private Class<T> type;public Box(Class<T> type) {this.type = type;}public static <U> void print(Box<U> box, U value) { // 合法System.out.println(value);} }
 
- 运行期 
- 💻例题:  - import java.util.*;public class Main {public static void main(String[] args) {ArrayList<Employee> list = new ArrayList<>();list.add(new Employee("white", 45633, new MyDate(2002, 2, 3)));list.add(new Employee("andy", 36325, new MyDate(1980, 2, 3)));list.add(new Employee("andy", 62234, new MyDate(1987, 2, 3)));System.out.println(list);list.sort(new Comparator<Employee>() {@Overridepublic int compare(Employee o1, Employee o2) {// 先比较名字int res = o1.getName().compareTo(o2.getName());if (res != 0) {return res;}// 比较生日return o1.getBirthday().compareTo(o2.getBirthday());}});System.out.println(list);} }class Employee {private String name;private int sal;private MyDate birthday;public Employee(String name, int sal, MyDate birthday) {this.name = name;this.sal = sal;this.birthday = birthday;}public void setName(String name) {this.name = name;}public void setSal(int sal) {this.sal = sal;}public void setBirthday(MyDate birthday) {this.birthday = birthday;}public String getName() {return name;}public int getSal() {return sal;}public MyDate getBirthday() {return birthday;}@Overridepublic String toString() {return "Employee{" +"name='" + name + '\\'' +", sal=" + sal +", birthday=" + birthday +'}';} }class MyDate implements Comparable<MyDate>{private int year;private int month;private int day;public MyDate(int year, int month, int day) {this.year = year;this.month = month;this.day = day;}public void setYear(int year) {this.year = year;}public void setMonth(int month) {this.month = month;}public void setDay(int day) {this.day = day;}public int getYear() {return year;}public int getMonth() {return month;}public int getDay() {return day;}@Overridepublic String toString() {return "MyDate{" +"year=" + year +", month=" + month +", day=" + day +'}';}@Overridepublic int compareTo(MyDate o) {int res = this.year - o.getYear();if (res != 0) {return res;}res = this.month - o.getMonth();if (res != 0) {return res;}res = this.day - o.getDay();return res;} }- 输出结果: - [Employee{name='white', sal=45633, birthday=MyDate{year=2002, month=2, day=3}}, Employee{name='andy', sal=36325, birthday=MyDate{year=1980, month=2, day=3}}, Employee{name='andy', sal=62234, birthday=MyDate{year=1987, month=2, day=3}}] [Employee{name='andy', sal=36325, birthday=MyDate{year=1980, month=2, day=3}}, Employee{name='andy', sal=62234, birthday=MyDate{year=1987, month=2, day=3}}, Employee{name='white', sal=45633, birthday=MyDate{year=2002, month=2, day=3}}]
9.JUnit测试框架

JUnit 是 Java 最流行的 单元测试框架,用于编写和运行可重复的自动化测试。它由 Kent Beck 和 Erich Gamma 开发,是 xUnit 家族的一员(类似 C++ 的 CppUnit、Python 的 PyTest)。JUnit 5 是最新版本(2017 年发布),而 JUnit 4 仍然广泛使用。
- 用@Test注解标注要测试的方法后,键入Alt+Enter,不用调用方法,直接可以运行





- 💻例题:  - import org.junit.jupiter.api.Test;import java.util.*;public class Main {public static void main(String[] args) {}@Testpublic void test() {DAO<User> dao = new DAO<>();dao.save("001", new User(1, 10, "jack"));dao.save("002", new User(2, 18, "king"));dao.save("003", new User(3, 38, "smith"));List<User> list = dao.list();System.out.println(list);} }class DAO<T> {public Map<String, T> map = new HashMap<>();public void save(String id, T entity) {map.put(id, entity);}public T get(String id) {return map.get(id);}public void update(String id, T entity) {map.put(id, entity);}// 遍历map,将所有value封装到ArrayList并返回public List<T> list() {ArrayList<T> v = new ArrayList<>();for (String key: map.keySet()) {v.add(map.get(key));}return v;}public void delete(String id) {map.remove(id);} }class User {private int id;private int age;private String name;public User(int id, int age, String name) {this.id = id;this.age = age;this.name = name;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setId(int id) {this.id = id;}public String getName() {return name;}public int getAge() {return age;}public int getId() {return id;}@Overridepublic String toString() {return "User{" +"id=" + id +", age=" + age +", name='" + name + '\\'' +'}';} }- @Test方法通常不写成静态方法(- static)的原因主要有以下几点:- JUnit 的设计规范 - JUnit(包括 JUnit 5/Jupiter)默认要求测试方法是非静态的。这是因为测试框架通常需要实例化测试类,并在运行时调用测试方法。如果测试方法是静态的,JUnit 可能无法正确识别和执行它们。 
- 测试实例状态 - 测试方法可能需要访问测试类的实例变量(如 - dao在- test()方法中的使用)。如果方法是静态的,它无法直接访问非静态成员变量,除非这些变量也是静态的,但这通常不是好的实践,因为静态变量会在测试之间共享状态,可能导致测试污染(Test Pollution)。
- 测试隔离性 - 每个测试方法应该独立运行,避免共享状态。如果使用静态变量,多个测试方法可能意外修改同一数据,导致测试结果不可靠。而非静态方法和实例变量可以确保每个测试方法运行时有独立的环境。 
- 框架兼容性 - 虽然 JUnit 5 允许静态 - @Test方法(通过- @TestInstance(Lifecycle.PER_CLASS)配置),但这是特殊情况,通常不推荐。大多数测试框架和工具默认假设测试方法是非静态的。
 
10.Java泛型 vs C++模板
(1)核心相似点
- 目的:都支持编写可复用的、类型无关的代码
- 语法:都使用尖括号 < >表示泛型/模板参数(如List<T>或vector<T>)
(2)主要区别
| 特性 | Java 泛型 (Generics) | C++ 模板 (Templates) | 
|---|---|---|
| 实现机制 | 类型擦除(编译后泛型信息被擦除) | 代码生成(编译时为每种类型生成具体代码) | 
| 运行时类型检查 | 无(类型信息在运行时不可见) | 有(模板实例化后保留完整类型信息) | 
| 性能影响 | 无额外运行时开销(类型擦除) | 可能增加代码体积(多份实例化代码) | 
| 类型安全 | 编译时检查(避免类型错误) | 编译时检查(但错误信息可能复杂) | 
| 适用场景 | 容器类(如 List<T>)、通用算法 | 容器类(如 vector<T>)、元编程、高性能计算 | 
| 支持基本类型 | 不支持(需用包装类如 Integer) | 支持(如 vector<int>) | 
| 扩展功能 | 有限(无模板元编程) | 支持特化(Specialization)、SFINAE 等高级特性 | 
(3) 代码示例对比
ⅠJava 泛型
// 泛型类
class Box<T> {private T value;public void set(T value) { this.value = value; }public T get() { return value; }
}// 使用
Box<String> box = new Box<>();
box.set("Hello");
String s = box.get();  // 类型安全
Ⅱ C++ 模板
// 模板类
template<typename T>
class Box {
public:T value;void set(T value) { this->value = value; }T get() { return value; }
};// 使用
Box<std::string> box;
box.set("Hello");
std::string s = box.get();  // 类型安全// 支持基本类型
Box<int> intBox;
intBox.set(42);
