【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 小时前
请求头设置没有生效
java·后端
凡人叶枫3 小时前
C++中输入、输出和文件操作详解(Linux实战版)| 从基础到项目落地,避坑指南
linux·服务器·c语言·开发语言·c++
亓才孓3 小时前
[JDBC]批处理
java
春日见3 小时前
车辆动力学:前后轮车轴
java·开发语言·驱动开发·docker·计算机外设
锐意无限3 小时前
Swift 扩展归纳--- UIView
开发语言·ios·swift
低代码布道师3 小时前
Next.js 16 全栈实战(一):从零打造“教培管家”系统——环境与脚手架搭建
开发语言·javascript·ecmascript
宋小黑3 小时前
JDK 6到25 全版本网盘合集 (Windows + Mac + Linux)
java·后端
念何架构之路3 小时前
Go进阶之panic
开发语言·后端·golang
7哥♡ۣۖᝰꫛꫀꪝۣℋ3 小时前
Spring-cloud\Eureka
java·spring·微服务·eureka
先跑起来再说3 小时前
Git 入门到实战:一篇搞懂安装、命令、远程仓库与 IDEA 集成
ide·git·后端·elasticsearch·golang·intellij-idea