泛型

1、泛型是什么?

1.1 泛型的定义

泛型(Generic)是 Java 5 引入的特性,​本质是一种「类型参数化」技术​—— 简单说,就是允许我们在定义类、接口、方法时,不直接指定具体的数据类型,而是用一个「类型占位符」(如 T​、E​、K​、V​)来代替,等到实际使用(创建对象、调用方法)时,再传入具体的类型(如 String​、Integer​)进行约束。

泛型的核心目标是让代码具备通用性,同时在编译期保证类型安全,避免类型转换异常。

1.2 为什么需要泛型

在泛型出现之前,java用Object​来实现通用代码,但是存在两个致命的问题:类型不安全、需要频繁强制转换类型。

(1)无泛型示例

public class NoGenericDemo {
    public static void main(String[] args) {
        //无泛型list
        List list = new ArrayList();

        //可以添加任意类型数据
        list.add("1");
        list.add(true);
        list.add(10);

        //取数据时必须强制转换
        String s = (String) list.get(0); //正常转换
        Integer i = (Integer) list.get(2); //正常转换
        String error = (String) list.get(1); //ClassCastException
    }
}

代码存在的问题:

  • 编译期间无法发现类型错误问题,只有运行时才会抛出ClassCastException(类型转换异常);

  • 必须手动强制转换,代码繁琐,效率低

(2)有泛型的解决方案

public class GenericDemo {
    public static void main(String[] args) {
        //定义泛型List,执行类型为String(编译期间约束,只能存储String类型数据)
        List<String> list = new ArrayList<>();
        
        // 只能添加String类型数据,其他类型编译会报错(类型安全)
        list.add("1");
        list.add("2");
        //list.add(3); //编译会报错,不兼容的类型
        
        /// 获取数据时,会自动进行类型转换
        String s = list.get(0);
        String s1 = list.get(1);
        System.out.println(s);
    }
}

泛型带来的核心价值:

  1. ​编译期类型安全​:限制容器 / 方法只能操作指定类型的数据,非法类型编译直接报错,避免运行时类型转换异常;

  2. ​消除冗余的强制类型转换​:编译器自动完成类型转换,代码更简洁、高效;

  3. ​提升代码通用性​:一套泛型代码可以适配多种具体类型,无需为每种类型编写重复代码。

2、泛型通配符

泛型通配符用 ?​ 表示,称为「未知类型通配符」,它的核心作用是:表示一个未知的泛型类型,用于灵活约束泛型的类型范围,解决泛型类型之间不兼容(无法向上向下转型)的问题。

先看一个泛型不兼容的痛点示例,理解通配符的必要性:

import java.util.ArrayList;
import java.util.List;

public class WildcardNeedDemo {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();
        
        // 错误:List<String> 不能赋值给 List<Object>,泛型不支持直接向上转型
        // List<Object> objectList = stringList;
        
        // 需求:编写一个通用方法,能打印任意类型的 List 集合(String、Integer 等)
        // 若无通配符,需要为每种类型编写重复方法,无法实现通用
        printList(stringList);
        printList(integerList);
    }
    
    // 使用通配符 ?,表示接收任意类型的 List 集合
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.print(obj + " ");
        }
        System.out.println();
    }
}

3、泛型限定符

泛型支持使用通配符 ?​ 表示 “任意类型”,并可以通过 extends​ 指定上限、super​ 指定下限,用于灵活约束泛型类型的范围。

  • ​<? extends T>​:通配符上限,只能接受 T​ 及其子类类型,可以缩小范围;

  • ​<? super T>​:通配符下限,只能接受 T​ 及其父类类型。

2.1限定符的使用

假设有如下三个类

class Animal {
        public String toString(){
            return "I am an animal";
        }
    }
    class Dog extends Animal {
        public String toString(){
            return "I am a dog";
        }
    }
    class Cat extends Animal {
        public String toString(){
            return "I am a cat";
        }
    }
    
    class WhiteCat extends Cat{
        public String toString(){
            return "I am a white cat";
        }
    }

使用限定符extend​实现一个MyArrayList​类

    class MyArrayList<E extends Cat>{
        public void add(E e){
            System.out.println(e);
        }
    }

当我们向MyArrayList​中添加Dog​元素时编译器会提示参数类型错误。

MyArrayList<Cat> list = new MyArrayList<>();
list.add(new Cat());
list.add(new WhiteCat());
//list.add(new Dog()); //类型不匹配

4、类型擦除(泛型的底层实现)

Java 泛型是​编译期特性​,在运行时会发生「类型擦除」—— 编译器在编译时会将泛型占位符(如 T​、E​)替换为其上限类型(默认是 Object​,有指定上限则替换为上限类型),运行时 JVM 无法感知泛型类型的存在。

示例:编译后的泛型代码会被擦除

// 编译前的泛型代码
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0);

// 编译后的字节码(类型擦除后)
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0); // 编译器自动添加强制类型转换

✏️Note

1、泛型中不同字母的使用场景?

1)语法功能层面:E​ 和 T​ 无区别,都是泛型占位符,编译器处理逻辑一致,可互相替换且不影响程序运行;

2)命名规范层面:E​ 专用于集合 / 容器的元素类型,T​ 是通用任意类型,遵循惯例可提升代码可读性;

占位符

全称

核心使用场景

​E​

Element

集合 / 容器中的元素类型(List、Set 等)

​T​

Type

通用任意类型(工具类、响应体、泛型方法)

​K​

Key

键值对中的 “键” 类型(Map<K, V>)

​V​

Value

键值对中的 “值” 类型(Map<K, V>)

​U​/S​

Utility/Second

辅助类型,当需要多个通用类型时使用(如 <T, U>​)

注意

  1. 通配符只能用于「声明类型、方法参数」,不能用于「创建对象、类定义、方法返回值」

  • 正确:List<? extends Number> list = new ArrayList<Integer>();​

  • 错误:new ArrayList<?>​(无法创建通配符类型的对象)

  • 错误:public class GenericClass<?>​(类定义不能使用通配符,只能使用泛型占位符 T/E 等)

  1. 上界通配符「只读」,下界通配符「可写」,无界通配符「仅通用读取」

  • 记住口诀:​extends​ 取上限,只读不写;super​ 取下限,可写只读(Object)​。

  1. 泛型占位符(T/E/K/V)与通配符(?)的区别

  • 泛型占位符:是「类型参数」,用于定义泛型类 / 接口 / 方法,代表一个「确定的未知类型」,使用时需要指定具体类型;

  • 通配符:是「类型通配」,用于使用泛型时,代表一个「不确定的未知类型」,直接用于约束类型范围,无需指定具体类型。

  1. 通配符不能嵌套使用

  • 错误:List<? extends List<? extends Number>>​(虽然编译可能通过,但可读性极差,不推荐)

  • 解决方案:使用泛型占位符封装,提升可读性。

IO流详解:如何轻松理解输入流和输出流 2026-07-13