【Java 基础】泛型<T>

1. 为什么要有泛型

1.1 无泛型的痛点

在 JDK1.5 之前,集合容器类的元素类型被设计为Object,导致两个核心问题:

任何类型都可以添加到集合中,类型不安全;

读取出来的对象需要强转,繁琐,且可能有ClassCastException。

java

运行

复制代码
import java.util.ArrayList;
import java.util.List;

public class ArrayListTest {
    public static void main(String[] args) {
        List list = new ArrayList();
        // 需求:存放学生成绩(Integer类型)
        list.add(78);
        list.add(66);
        list.add(19);
        list.add(60);
        // 问题1:类型不安全------可以添加任意类型(如String)
        list.add("Tom");

        for (Object score : list) {
            // 问题2:必须强转,且可能抛出ClassCastException
            Integer stuScore = (Integer) score;
            System.out.print(stuScore);
            int result = stuScore.compareTo(60);
            if (result > 0) {
                System.out.println("-优秀");
            } else if (result == 0) {
                System.out.println("-中等");
            } else {
                System.out.println("-差生");
            }
        }
    }
}

运行上述代码会直接抛出ClassCastException,因为"Tom"无法转为Integer

1.2 泛型的解决方案

JDK1.5 引入 "参数化类型",允许创建集合时指定元素类型,从根源解决问题:

复制代码
import java.util.ArrayList;
import java.util.List;

public class ArrayListTest {
    public static void main(String[] args) {
        // 明确指定List只能存储Integer类型
        List<Integer> list = new ArrayList();
        list.add(78);
        list.add(66);
        list.add(19);
        list.add(60);
        // 编译报错:类型不匹配,无法添加String
        // list.add("Tom");

        // 无需强转,直接使用Integer类型
        for (Integer stuScore : list) {
            System.out.print(stuScore);
            int result = stuScore.compareTo(60);
            if (result > 0) {
                System.out.println("-优秀");
            } else if (result == 0) {
                System.out.println("-中等");
            } else {
                System.out.println("-差生");
            }
        }
    }
}

1.3 泛型的核心好处

  1. 类型安全:编译期校验元素类型,避免非法类型插入
  2. 简化代码:无需强制类型转换
  3. 代码复用:一套逻辑适配多种数据类型

2. 泛型类

2.1 定义格式

java

复制代码
修饰符 class 类名<类型参数> { }
// 类型参数常用占位符:T(Type)、E(Element)、K(Key)、V(Value)

2.2 实战案例

java

复制代码
class Generic<T> {
    private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}

public class GenericTest {
    public static void main(String[] args) {
        // 非泛型用法(不推荐):需强转,不安全
        Generic generic = new Generic();
        generic.setContent(19);
        if ((Integer) generic.getContent() >= 18) { // 强制类型转换
            System.out.println("已成年,可以...");
        }

        // 泛型用法(推荐):编译期校验,无需强转
        Generic<Integer> generic2 = new Generic<>();
        generic2.setContent(19);
        // generic2.setContent("19"); // 编译报错:类型不匹配
        if (generic2.getContent() >= 18) { // 直接使用Integer类型
            System.out.println("已成年,可以...");
        }
    }
}

2.3 典型应用场景

泛型类的核心价值是一套逻辑处理多种类型数据。

Java 内置的ArrayList就是经典的泛型类:

java

复制代码
// JDK源码核心逻辑(简化)
public class ArrayList<T> {
    private T[] elements;

    public boolean add(T e) {
        // 添加元素逻辑...
    }

    public T get(int index) {
        // 获取元素逻辑...
    }
    // 其他通用方法...
}

// 复用同一套逻辑处理不同类型
List<String> stringList = new ArrayList<>(); // 存储字符串
List<Integer> intList = new ArrayList<>();   // 存储整数
List<Person> personList = new ArrayList<>(); // 存储自定义对象

3. 泛型方法

3.1 定义格式

java

复制代码
修饰符 <类型参数> 返回值类型 方法名(类型参数 变量名) { }
// 注意:泛型方法的类型参数需在返回值类型前声明

3.2 实战案例

java

复制代码
class GenericUtil {
    // 泛型方法:适配任意类型的打印逻辑
    public <T> void show(T t) {
        System.out.println("数据:" + t);
    }
}

public class GenericMethodTest {
    public static void main(String[] args) {
        GenericUtil util = new GenericUtil();
        util.show("林青霞"); // 字符串类型
        util.show(30);       // 整数类型
        util.show(true);     // 布尔类型
        util.show(12.34);    // 浮点类型
    }
}

3.3 典型应用场景

JDK 的Collections工具类中几乎全是泛型方法,例如reverse(反转集合):

java

复制代码
// JDK源码核心逻辑
public class Collections {
    // 泛型方法:反转任意类型的List集合
    public static <T> void reverse(List<T> list) {
        int size = list.size();
        for (int i = 0, j = size - 1; i < j; i++, j--) {
            swap(list, i, j); // 调用泛型swap方法
        }
    }

    // 泛型方法:交换List中任意位置的元素
    public static <T> void swap(List<T> list, int i, int j) {
        final List<T> l = list;
        l.set(i, l.set(j, l.get(i)));
    }
}

// 调用示例:适配任意类型List
List<String> strList = Arrays.asList("A", "B", "C");
Collections.reverse(strList); // 结果:[C, B, A]

List<Integer> intList = Arrays.asList(1, 2, 3);
Collections.swap(intList, 0, 2); // 结果:[3, 2, 1]

4. 泛型接口

4.1 定义格式

java

复制代码
修饰符 interface 接口名<类型参数> { }

4.2 实战案例

java

复制代码
// 泛型接口
interface GenericInterface<T> {
    T show(T t);
}

// 实现接口时指定具体类型(Integer)
class GenericImpl implements GenericInterface<Integer> {
    @Override
    public Integer show(Integer t) {
        return t * 2;
    }
}

public class GenericInterfaceTest {
    public static void main(String[] args) {
        GenericInterface<Integer> impl = new GenericImpl();
        Integer result = impl.show(2);
        System.out.println(result); // 输出:4

        // impl.show("2"); // 编译报错:类型不匹配
    }
}

4.3 类型参数规范(T/E/K/V)

泛型的类型参数是占位符,有约定俗成的命名规范:

占位符 含义(英文) 用途场景
T Type(类型) 通用类型参数(类、接口、方法)
E Element(元素) 集合中的元素类型(如 List<E>)
K Key(键) 映射中的键类型(如 Map<K,V>)
V Value(值) 映射中的值类型(如 Map<K,V>)

4.4 典型应用:List 接口

Java 集合框架的List接口是泛型接口的经典应用:

java

复制代码
// JDK源码核心逻辑
public interface List<E> extends Collection<E> {
    boolean add(E e); // 添加元素(E为元素类型)
    E get(int index); // 获取元素(返回E类型)
    E set(int index, E element); // 修改元素
    // 其他方法...
}

// 使用示例
List<String> strList = new ArrayList<>();
strList.add("Java"); // 只能添加String类型
String str = strList.get(0); // 无需强转,直接返回String
// strList.add(123); // 编译报错:类型不匹配

5. 类型通配符(<?>)

5.1 为什么需要通配符?

泛型不支持多态,直接赋值会编译报错:

java

复制代码
// 编译错误:List<Integer>不能赋值给List<Number>
List<Number> list = new ArrayList<Integer>();

为了解决 "泛型集合的父子类关系" 问题,引入类型通配符<?>

5.2 通配符分类及用法

5.2.1 无界通配符(<?>)
  • 含义:表示元素类型未知的泛型集合
  • 作用:作为所有泛型集合的父类(如 List<?> 是 List<String>、List<Integer>的父类)
  • 限制:不能添加元素(编译器无法确定元素类型)

java

复制代码
public class WildcardTest {
    public static void main(String[] args) {
        List<?> list1 = new ArrayList<Object>();
        List<?> list2 = new ArrayList<Number>();
        List<?> list3 = new ArrayList<Integer>();
        
        // list1.add(3); // 编译报错:无法确定元素类型,禁止添加
    }
}
5.2.2 上限通配符(<? extends 类型>)
  • 含义:表示 "类型及其子类"(如<? extends Number> 代表 Number或者其子类型 )
  • 适用场景:读取数据(如遍历集合),不适合写入数据

java

复制代码
// 语法格式
List<? extends Number> list = new ArrayList<Integer>();

// 实战案例:读取任意Number子类的集合
public void printElements(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n); // 安全读取,无需强转
    }
}

// 调用示例
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Long> longList = Arrays.asList(10L, 20L);
printElements(intList);  // 合法
printElements(longList); // 合法
5.2.3 下限通配符(<? super 类型>)
  • 含义:表示 "类型及其父类"(如<? super Integer> 代表 Integer或者其父类型)
  • 适用场景:写入数据(如向集合添加元素),读取时只能返回 Object

java

复制代码
// 语法格式
List<? super Integer> list = new ArrayList<Number>();

// 实战案例:向集合添加Integer及其子类元素
public void addIntegers(List<? super Integer> list) {
    list.add(10);        // 合法:Integer类型
    list.add(20);        // 合法:Integer类型
    // list.add(30L); // 编译报错:Long不是Integer的子类
}

// 调用示例
List<Number> numList = new ArrayList<>();
addIntegers(numList); // 结果:numList = [10, 20]

5.3 JDK 源码应用:Collections.max ()

上限通配符在 JDK 源码中广泛应用,例如Collections.max()方法:

java

复制代码
// JDK源码核心逻辑
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
    Iterator<? extends T> i = coll.iterator();
    T candidate = i.next();
    while (i.hasNext()) {
        T next = i.next();
        if (next.compareTo(candidate) > 0) {
            candidate = next;
        }
    }
    return candidate;
}

// 调用示例:兼容Integer、Long等Number子类集合
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Long> longList = Arrays.asList(10L, 20L);
Number max1 = Collections.max(intList);  // 合法
Number max2 = Collections.max(longList); // 合法

6. 可变参数(与泛型结合)

6.1 定义格式

java

复制代码
修饰符 返回值类型 方法名(数据类型... 变量名) { }
  • 本质:变量是一个数组
  • 规则:可变参数必须放在参数列表最后

6.2 实战案例

java

复制代码
public class VarargsTest {
    public static void main(String[] args) {
        System.out.println(sum());          // 0
        System.out.println(sum(10));        // 10
        System.out.println(sum(10, 20));    // 30
        System.out.println(sum(10, 20, 30));// 60
        
        int[] intArr = {10, 20, 30, 40};
        System.out.println(sum(intArr));    // 100
    }

    // 可变参数方法:计算任意个整数的和
    public static int sum(int... a) {
        int sum = 0;
        for (int i : a) {
            sum += i;
        }
        return sum;
    }
}

6.3 泛型 + 可变参数的应用

JDK 工具类中大量结合泛型和可变参数,例如:

  1. Arrays.asList(T... a):数组转集合
  2. List.of(E... elements):创建不可变 List
  3. Set.of(E... elements):创建不可变 Set

java

复制代码
// 1. 数组转集合
List<String> strList = Arrays.asList("A", "B", "C");

// 2. 创建不可变List
List<Integer> intList = List.of(1, 2, 3);

// 3. 创建不可变Set
Set<String> strSet = Set.of("X", "Y", "Z");

7. 练习:编译报错分析

题目:以下代码为什么编译报错?如何修正?

java

运行

复制代码
// 报错代码1
public static void addStrings(List<? extends Object> list) {
    list.add("aaa");          // 报错
    list.add(new Object());   // 报错
}

// 报错代码2
public void addString(List<? super A> list) {
    list.add(new A());        // 合法?
    list.add(new Circle());   // 合法?(Circle继承A)
    list.add(new GeometricObject()); // 报错?
    list.add(new Object());   // 报错?
}

分析与修正

报错 1:List<? extends Object>
  • 原因:上限通配符<? extends Object>表示 "Object 及其子类",编译器无法确定具体类型,禁止添加任何元素(除了 null)
  • 修正:若需添加元素,改用下限通配符<? super String>

java

运行

复制代码
public static void addStrings(List<? super String> list) {
    list.add("aaa");          // 合法
    // list.add(new Object()); // 仍报错:Object不是String的子类
}
报错 2:List<? super A>
  • 规则:下限通配符<? super A>仅允许添加A及其子类
  • 修正后:

java

运行

复制代码
public void addString(List<? super A> list) {
    list.add(new A());        // 合法:A是本身
    list.add(new Circle());   // 合法:Circle是A的子类
    // list.add(new GeometricObject()); // 报错:GeometricObject是A的父类
    // list.add(new Object());   // 报错:Object是A的父类
}

总结

泛型是 Java 中解决 "类型安全" 和 "代码复用" 的核心特性,核心要点:

  1. 泛型类 / 接口:一套代码适配多种类型(如 ArrayList)
  2. 泛型方法:独立于类的泛型逻辑(如 Collections 工具类)
  3. 类型通配符:解决泛型集合的父子类关系(<?> / <? extends T> / <? super T>)
  4. 可变参数:简化多参数传递,常与泛型结合使用

掌握泛型能让你的代码更安全、更简洁,尤其在集合操作和框架开发中不可或缺。建议结合 JDK 源码(如 ArrayList、Collections)深入理解,多动手实践才能熟练运用~

相关推荐
她说..2 小时前
FIND_IN_SET()方法
xml·java·spring boot
Tony Bai2 小时前
AI 时代,Go 语言会“失宠”还是“封神”?—— GopherCon 2025 圆桌深度复盘
开发语言·人工智能·后端·golang
花间相见2 小时前
【JAVA开发】—— Maven核心用法与实战指南
java·python·maven
爱吃的强哥2 小时前
Springboot 使用 SSE推送消息到客户端(Electron)
java·spring boot·electron
Elias不吃糖2 小时前
Java Stream 流(Stream API)详细讲解
java·stream·
寻星探路2 小时前
【全景指南】JavaEE 深度解析:从 Jakarta EE 演进、B/S 架构到 SSM 框架群实战
java·开发语言·人工智能·spring boot·ai·架构·java-ee
七夜zippoe2 小时前
微服务架构演进实战 从单体到微服务的拆分原则与DDD入门
java·spring cloud·微服务·架构·ddd·绞杀者策略
tc&2 小时前
新虚拟机安装 Go 环境:问题总结与解决方案
开发语言·后端·golang
洛_尘2 小时前
JAVA EE初阶8:网络原理 - HTTP_HTTPS(重要)
java·http·java-ee