泛型是 Java 5 引入的核心特性,它通过在编译阶段约束数据类型,解决了集合存储数据的类型混乱与强制转换风险问题。本文将结合实战代码,从泛型的基本概念、核心优势、使用方式到高级特性,全面梳理泛型的知识体系,助你轻松掌握这一重要语法。
一、泛型的基本认知:为什么需要泛型?
在泛型出现之前,集合存储数据时默认以Object类型接收,这会导致类型管理混乱与运行时异常风险。通过对比 “无泛型” 与 “有泛型” 的场景,可直观理解泛型的价值。
1. 无泛型的痛点
如LearnGenerics.java中所示,未指定泛型的集合可存储任意引用类型数据,但在遍历与使用时存在明显弊端:
// 无泛型的ArrayList,默认存储Object类型
ArrayList list = new ArrayList();
list.add(123); // 存储Integer
list.add("abc"); // 存储String
list.add(new Student("小韩")); // 存储Student
// 遍历需强制转换,存在类型转换异常风险
Iterator it = list.iterator();
while (it.hasNext()) {
Object obj = it.next();
// 若误将String转为Integer,运行时会抛出ClassCastException
Integer num = (Integer) obj;
}
无泛型的核心问题:
- 类型混乱:集合无法约束存储数据的类型,可能混合存储多种类型;
- 运行时风险:取出数据时需强制转换,转换错误仅在运行时暴露,难以提前排查。
2. 泛型的定义与语法
泛型本质是 “参数化类型”,即把数据类型作为参数传递,在编译阶段明确约束集合或方法可操作的类型。
(1)基本语法
泛型的格式为 <数据类型>,需注意以下两点:
- 泛型只能是引用数据类型(如Integer、String、自定义类等),不能是基本数据类型(int、char等);
- 若不指定泛型,默认等同于 <Object>,退化到无泛型的场景。
(2)有泛型的优势
使用泛型后,可解决无泛型的痛点,如LearnGenerics.java所体现的:
// 指定泛型为Student,仅允许存储Student及其子类对象
ArrayList<Student> studentList = new ArrayList<>();
studentList.add(new Student("小韩"));
studentList.add(new Student("小李"));
// 遍历无需强制转换,编译阶段已确定类型
for (Student s : studentList) {
System.out.println(s.getName()); // 直接调用Student的方法,无类型转换风险
}
泛型的核心优势:
- 统一类型:强制集合或方法仅操作指定类型的数据,避免类型混乱;
- 提前排查错误:将类型不匹配的错误从 “运行时” 提前到 “编译时”,降低异常风险;
- 简化代码:无需手动强制转换,代码更简洁易读。
注意:Java 中的泛型是 “伪泛型”,编译后会通过 “类型擦除” 将泛型信息移除,统一替换为Object类型(或泛型的上边界类型),仅在编译阶段提供类型检查。
二、泛型的核心使用:类、方法与接口
泛型可应用于类、方法、接口三种场景,分别解决不同层级的类型不确定问题。
1. 泛型类:类级别的类型约束
当一个类中存在 “数据类型不确定的成员变量或方法参数” 时,可定义为泛型类,使类的整体操作与指定类型绑定。
(1)定义格式
修饰符 class 类名<泛型标识> {
// 泛型标识可作为成员变量类型
private 泛型标识 变量名;
// 泛型标识可作为方法参数或返回值类型
public 泛型标识 方法名(泛型标识 参数) {
return 参数;
}
}
- 泛型标识通常使用大写字母表示(如T、E、K、V),仅为标识作用,无实际含义;
- T(Type):表示具体的类型;E(Element):常用于集合中的元素类型;K(Key)、V(Value):常用于键值对类型。
(2)使用示例
// 定义泛型类Box,泛型标识为T
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用泛型类:创建对象时指定具体类型
public class GenericClassDemo {
public static void main(String[] args) {
// 指定T为String,Box仅操作String类型
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello Generics");
String str = stringBox.getContent();
// 指定T为Integer,Box仅操作Integer类型
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
Integer num = intBox.getContent();
}
}
2. 泛型方法:方法级别的类型约束
当仅需在单个方法中约束类型,而无需整个类都绑定类型时,可定义泛型方法。泛型方法分为两种:“使用类泛型” 与 “自定义方法泛型”。
(1)使用类泛型的方法
若方法所在的类是泛型类,可直接使用类定义的泛型标识,适用于 “类中多个方法需共用同一泛型” 的场景:
class Box<T> {
// 使用类泛型T作为方法参数和返回值
public T process(T input) {
return input;
}
}
(2)自定义泛型的方法
若方法无需依赖类的泛型,可在方法声明上独立定义泛型,仅对当前方法生效,格式如下:
修饰符 <泛型标识> 泛型标识 方法名(泛型标识 参数) {
// 方法体
}
示例:定义一个泛型方法,打印任意类型的数组:
public class GenericMethodDemo {
// 自定义泛型T,仅对printArray方法生效
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
String[] strArray = {"Java", "泛型", "方法"};
Integer[] intArray = {1, 2, 3};
// 调用时自动推断泛型类型,无需显式指定
printArray(strArray); // 输出:Java 泛型 方法
printArray(intArray); // 输出:1 2 3
}
}
3. 泛型接口:接口级别的类型约束
泛型接口与泛型类类似,在接口定义时指定泛型标识,实现类需通过两种方式确定具体类型。
(1)定义格式
interface 接口名<泛型标识> {
泛型标识 方法名();
}
(2)实现泛型接口的两种方式
① 实现类直接指定具体类型
适用于 “实现类明确知道接口泛型类型” 的场景:
// 定义泛型接口Generator
interface Generator<T> {
T generate();
}
// 实现类指定T为String
class StringGenerator implements Generator<String> {
@Override
public String generate() {
return "随机字符串";
}
}
② 实现类延续泛型,创建对象时再指定类型
适用于 “实现类无法确定泛型类型,需由使用者决定” 的场景:
// 实现类延续接口的泛型T
class ObjectGenerator<T> implements Generator<T> {
private T obj;
public ObjectGenerator(T obj) {
this.obj = obj;
}
@Override
public T generate() {
return obj;
}
}
// 使用时指定泛型类型为Integer
public class GenericInterfaceDemo {
public static void main(String[] args) {
ObjectGenerator<Integer> intGenerator = new ObjectGenerator<>(123);
System.out.println(intGenerator.generate()); // 输出:123
}
}
三、泛型的高级特性:继承与通配符
泛型的继承特性与通配符是泛型使用的重点与难点,直接影响代码的灵活性与兼容性。
1. 泛型的继承特性:泛型不具备继承性,数据具备继承性
泛型本身不支持继承,即ArrayList<Cat>不是ArrayList<Animal>的子类,即使Cat是Animal的子类;但泛型集合中存储的数据支持继承,即ArrayList<Animal>可存储Animal及其子类对象。
示例(结合GenericityCase.java的类结构):
// Cat是Animal的子类,Dog是Animal的子类
ArrayList<Animal> animalList = new ArrayList<>();
animalList.add(new Cat()); // 允许:Cat是Animal的子类
animalList.add(new Dog()); // 允许:Dog是Animal的子类
// 错误:泛型不具备继承性,ArrayList<Cat>不能赋值给ArrayList<Animal>
ArrayList<Animal> errorList = new ArrayList<Cat>(); // 编译报错
2. 泛型通配符:解决泛型的兼容性问题
当需要定义一个方法,使其能接收 “泛型为某个继承体系中任意类型” 的集合时,需使用泛型通配符?。通配符分为 “上限通配符” 与 “下限通配符”,对应不同的使用场景。
(1)上限通配符:? extends E
表示 “只能接收 E 及其子类类型”,适用于 “读取数据为主” 的场景(如遍历集合),是最常用的通配符类型。
示例(对应GenericityCase.java的method1与method2):
// 定义Animal的继承体系:Animal → Cat → LihuaCat/BosiCat;Animal → Dog → TaidiDog/HashiqiDog
// 方法1:仅接收泛型为Cat及其子类的ArrayList(如ArrayList<Cat>、ArrayList<LihuaCat>)
public static void method1(ArrayList<? extends Cat> list) {
// 可读取数据(多态生效,可调用Cat的方法)
for (Cat cat : list) {
cat.eat();
}
// 不可添加数据(无法确定具体是Cat的哪个子类,避免类型混乱)
// list.add(new LihuaCat()); // 编译报错
}
// 方法2:仅接收泛型为Dog及其子类的ArrayList(如ArrayList<Dog>、ArrayList<HashiqiDog>)
public static void method2(ArrayList<? extends Dog> list) {
for (Dog dog : list) {
dog.eat();
}
}
// 使用示例
public class WildcardDemo {
public static void main(String[] args) {
ArrayList<LihuaCat> lihuaList = new ArrayList<>();
ArrayList<HashiqiDog> hashiqiList = new ArrayList<>();
method1(lihuaList); // 允许:LihuaCat是Cat的子类
method2(hashiqiList); // 允许:HashiqiDog是Dog的子类
// method1(hashiqiList); // 报错:HashiqiDog不是Cat的子类
}
}
(2)下限通配符:? super E
表示 “只能接收 E 及其父类类型”,适用于 “添加数据为主” 的场景(如向集合中插入元素)。
示例:定义一个方法,仅允许向泛型为Cat及其父类的集合中添加Cat对象:
// 方法:接收泛型为Cat及其父类的ArrayList(如ArrayList<Cat>、ArrayList<Animal>)
public static void addCat(ArrayList<? super Cat> list) {
// 可添加Cat及其子类对象(父类集合可存储子类对象,符合多态)
list.add(new Cat());
list.add(new LihuaCat());
// 不可添加Cat的父类对象(如Animal),避免类型混乱
// list.add(new Animal()); // 编译报错
// 读取数据时仅能以Object类型接收(无法确定具体父类类型)
for (Object obj : list) {
System.out.println(obj);
}
}
// 使用示例
public class SuperWildcardDemo {
public static void main(String[] args) {
ArrayList<Animal> animalList = new ArrayList<>();
ArrayList<Cat> catList = new ArrayList<>();
addCat(animalList); // 允许:Animal是Cat的父类
addCat(catList); // 允许:Cat是自身的类型
// addCat(new ArrayList<LihuaCat>()); // 报错:LihuaCat是Cat的子类,不符合下限要求
}
}
(3)通配符使用原则
- 若方法需读取集合数据,且不关心具体类型(仅需使用父类方法),用? extends 父类;
- 若方法需添加集合数据,且需保证添加元素的类型安全,用? super 子类;
- 若方法既需读取又需添加,建议不使用通配符,直接指定具体泛型类型。
四、实战案例:泛型通配符的实际应用
结合GenericityCase.java的类结构,演示如何利用泛型通配符实现 “分类管理动物” 的功能:
1. 需求场景
定义两个方法,分别用于管理 “所有猫科动物” 和 “所有犬科动物”,支持传入任意猫 / 狗的子类集合,并遍历调用其eat()方法。
2. 完整代码实现
import java.util.ArrayList;
// 定义动物继承体系(省略getter/setter/构造方法)
class Animal {
private String name;
public void eat() {}
}
class Cat extends Animal {}
class LihuaCat extends Cat {
@Override
public void eat() {
System.out.println(getName() + "吃猫粮");
}
}
class BosiCat extends Cat {
@Override
public void eat() {
System.out.println(getName() + "吃进口猫粮");
}
}
class Dog extends Animal {}
class HashiqiDog extends Dog {
@Override
public void eat() {
System.out.println(getName() + "吃狗粮");
}
}
// 泛型通配符实战
public class GenericityPractice {
// 管理所有猫科动物(Cat及其子类)
public static void manageCats(ArrayList<? extends Cat> cats) {
System.out.println("=== 猫科动物进食 ===");
for (Cat cat : cats) {
cat.eat();
}
}
// 管理所有犬科动物(Dog及其子类)
public static void manageDogs(ArrayList<? extends Dog> dogs) {
System.out.println("=== 犬科动物进食 ===");
for (Dog dog : dogs) {
dog.eat();
}
}
public static void main(String[] args) {
// 创建猫科集合
ArrayList<LihuaCat> lihuaList = new ArrayList<>();
lihuaList.add(new LihuaCat("小花"));
lihuaList.add(new LihuaCat("小白"));
ArrayList<BosiCat> bosiList = new ArrayList<>();
bosiList.add(new BosiCat("波斯"));
// 创建犬科集合
ArrayList<HashiqiDog> hashiqiList = new ArrayList<>();
hashiqiList.add(new HashiqiDog("哈士奇"));
// 调用管理方法
manageCats(lihuaList); // 允许:LihuaCat是Cat的子类
manageCats(bosiList); // 允许:BosiCat是Cat的子类
manageDogs(hashiqiList); // 允许:HashiqiDog是Dog的子类
}
}
3. 运行结果与说明
=== 猫科动物进食 ===
小花吃猫粮
小白吃猫粮
=== 猫科动物进食 ===
波斯吃进口猫粮
=== 犬科动物进食 ===
哈士奇吃狗粮
- 通过? extends Cat和? extends Dog的通配符约束,确保方法仅接收指定继承体系
五总结
泛型的设计核心是 “在编译阶段保障类型安全”,通过泛型类、泛型方法、泛型接口的灵活组合,可覆盖绝大多数类型不确定的开发场景;而掌握 “泛型不继承” 原则与通配符的使用场景,是实现泛型灵活适配的关键。在实际开发中,需根据业务需求选择合适的泛型应用方式,平衡类型安全与代码灵活性,充分发挥泛型的价值。
Java泛型实战:类、方法与接口应用
382

被折叠的 条评论
为什么被折叠?



