Java 泛型:简单易懂的核心讲解(含实战代码)

Java 泛型(Generics)是 “参数化类型” 的技术 —— 简单说就是给类、接口、方法定义时,不指定具体数据类型,而是用一个 “占位符”(比如 <T>)代替,使用时再传入实际类型(如 StringInteger)。核心价值是 类型安全(避免类型转换错误)+ 代码复用(一套逻辑适配多种类型),是 Java 中提升代码质量的关键特性。

一、先搞懂:为什么需要泛型?(痛点解决)

没有泛型时,用 Object 类型存储数据会有两个致命问题,泛型正是为解决这两个问题而生:

问题 1:类型不安全(编译不报错,运行崩)

java
 
运行
// 没有泛型:List 默认存 Object,能存任意类型
List list = new ArrayList();
list.add("Java");    // 存字符串
list.add(123);       // 存整数(编译不报错)
list.add(true);      // 存布尔值

// 读取时强制转换,类型不匹配直接抛 ClassCastException
String str = (String) list.get(1); // 运行报错:Integer 不能转 String
 

问题 2:代码冗余(每种类型写一套逻辑)

java
 
运行
// 没有泛型:要写多个“几乎一样”的方法
public static int sumInt(List<Integer> list) { /* 求和逻辑 */ }
public static double sumDouble(List<Double> list) { /* 相同求和逻辑 */ }
public static long sumLong(List<Long> list) { /* 重复代码 */ }
 

泛型的解决方案:

  • 类型安全:编译时就检查类型,不允许存入错误类型(比如 List<String> 不能存 Integer);
  • 代码复用:一套逻辑适配多种类型(比如一个 sum 方法搞定所有数字类型求和);
  • 取消强制转换:读取数据时不用手动转类型,编译器自动确认类型。

二、泛型核心语法(3 大应用场景)

泛型的核心是 “类型参数”(比如 <T><E><K,V>),命名无强制规定,但通常用单个大写字母表示:
  • T(Type):表示 “类型”;
  • E(Element):表示集合中的 “元素类型”;
  • K(Key)/V(Value):表示键值对中的键和值;
  • R(Return):表示方法的返回类型。

场景 1:泛型类(给类加类型参数)

定义类时声明类型参数,创建对象时指定具体类型,类中的属性、方法可使用该类型。

语法格式:

java
 
运行
// 类名后加 <类型参数>
class 类名<T> {
    // 用类型参数作为属性类型
    private T data;

    // 用类型参数作为方法参数/返回值
    public T getData() { return data; }
    public void setData(T data) { this.data = data; }
}
 

实战代码:

java
 
运行
// 泛型类:通用的“数据包装类”,适配任意类型
class Box<T> {
    private T content;

    public Box(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }

    public void showType() {
        System.out.println("当前存储的类型:" + content.getClass().getName());
    }
}

// 使用泛型类
public class GenericClassDemo {
    public static void main(String[] args) {
        // 1. 存储 String 类型(创建对象时指定 <String>)
        Box<String> strBox = new Box<>("Hello 泛型"); // Java 7+ 可省略右边的 <String>,简写为 <>
        String str = strBox.getContent(); // 无需强制转换
        strBox.showType(); // 输出:当前存储的类型:java.lang.String

        // 2. 存储 Integer 类型
        Box<Integer> intBox = new Box<>(123);
        Integer num = intBox.getContent();
        intBox.showType(); // 输出:当前存储的类型:java.lang.Integer

        // 3. 编译时类型检查(错误写法,编译直接报错)
        // strBox.setData(456); // 不允许给 String 类型的 Box 存 Integer
    }
}
 

场景 2:泛型方法(给方法加类型参数)

方法单独声明类型参数(和类的类型参数无关),可实现 “单个方法适配多种类型”,是泛型最灵活的用法。

语法格式:

java
 
运行
// 方法返回值前加 <类型参数>(关键!)
public <T> 返回值类型 方法名(T 参数) {
    代码逻辑
}
 

实战代码:

java
 
运行
public class GenericMethodDemo {
    // 泛型方法:打印任意类型的数组(适配 String、Integer、Double 等所有类型数组)
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    // 泛型方法:求任意数字类型的集合总和(限定类型为 Number 子类)
    public static <T extends Number> double sumList(List<T> list) {
        double total = 0.0;
        for (T num : list) {
            total += num.doubleValue(); // Number 类的方法,所有数字类型都有
        }
        return total;
    }

    public static void main(String[] args) {
        // 测试打印数组
        String[] strArr = {"Java", "泛型", "方法"};
        Integer[] intArr = {1, 2, 3, 4};
        Double[] doubleArr = {1.1, 2.2, 3.3};

        printArray(strArr);  // 输出:Java 泛型 方法
        printArray(intArr);  // 输出:1 2 3 4
        printArray(doubleArr);// 输出:1.1 2.2 3.3

        // 测试求和方法
        List<Integer> intList = Arrays.asList(10, 20, 30);
        List<Double> doubleList = Arrays.asList(1.5, 2.5, 3.5);

        System.out.println(sumList(intList));   // 输出:60.0
        System.out.println(sumList(doubleList));// 输出:7.5
    }
}
 

场景 3:泛型接口(给接口加类型参数)

接口声明类型参数,实现类实现接口时需指定具体类型,或继续保留泛型。

语法格式:

java
 
运行
// 接口名后加 <类型参数>
interface 接口名<T> {
    T 方法名(); // 用类型参数作为返回值
}
 

实战代码:

java
 
运行
// 泛型接口:通用的“转换器”接口
interface Converter<T, R> { // 两个类型参数:T(输入类型)、R(输出类型)
    R convert(T input);
}

// 实现类 1:String → Integer 转换器(指定具体类型)
class StringToIntConverter implements Converter<String, Integer> {
    @Override
    public Integer convert(String input) {
        return Integer.parseInt(input); // 字符串转整数
    }
}

// 实现类 2:Integer → String 转换器(指定具体类型)
class IntToStringConverter implements Converter<Integer, String> {
    @Override
    public String convert(Integer input) {
        return "数字:" + input; // 整数转字符串
    }
}

// 使用泛型接口
public class GenericInterfaceDemo {
    public static void main(String[] args) {
        Converter<String, Integer> strToInt = new StringToIntConverter();
        Integer num = strToInt.convert("123");
        System.out.println(num); // 输出:123

        Converter<Integer, String> intToString = new IntToStringConverter();
        String str = intToString.convert(456);
        System.out.println(str); // 输出:数字:456
    }
}
 

三、泛型关键特性(避坑重点)

1. 类型擦除(核心机制)

Java 泛型是 “编译时特性”,编译后会把类型参数 “擦除” 为原始类型(比如 List<String> 擦除后是 ListBox<T> 擦除后是 Box),运行时不存在泛型类型信息。

后果:

  • 不能用 new T() 创建对象(擦除后 T 是 Object,且可能无无参构造);
  • 不能用 instanceof 判断泛型类型(运行时不知道 <T> 是什么);
    java
     
    运行
     
    List<String> list = new ArrayList<>();
    // 错误:编译不通过(运行时 list 是 List,不是 List<String>)
    // if (list instanceof List<String>) {}
     
  • 不能创建泛型数组(比如 new T[10] 错误,可改用 new Object[10] 再强转)。

2. 泛型上限(限定类型参数的范围)

用 <T extends 类/接口> 限制类型参数必须是某个类的子类,或实现某个接口,可调用该类 / 接口的方法。

例子:

java
 
运行
// T 必须是 Number 的子类(Integer、Double、Long 等)
class NumericBox<T extends Number> {
    private T value;

    public double square() {
        // 可调用 Number 的 doubleValue() 方法(因为 T 是 Number 子类)
        return value.doubleValue() * value.doubleValue();
    }
}

// 使用
NumericBox<Integer> intBox = new NumericBox<>();
intBox.setValue(5);
System.out.println(intBox.square()); // 输出:25.0

// 错误:String 不是 Number 子类,编译报错
// NumericBox<String> strBox = new NumericBox<>();
 

3. 泛型通配符(?

当不确定泛型类型,或需要适配多种泛型类型时,用通配符 ? 表示 “任意类型”,常见两种用法:

(1)无界通配符(?):任意类型

java
 
运行
// 打印任意类型的 List(不能修改集合,只能读取)
public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.print(obj + " ");
    }
}

// 调用
List<String> strList = Arrays.asList("a", "b");
List<Integer> intList = Arrays.asList(1, 2);
printList(strList); // 输出:a b
printList(intList); // 输出:1 2
 

(2)上界通配符(? extends 类):某类及其子类

java
 
运行
// 求和:接收 Number 及其子类的 List(Integer、Double 等)
public static double sum(List<? extends Number> list) {
    double total = 0.0;
    for (Number num : list) {
        total += num.doubleValue();
    }
    return total;
}
 

(3)下界通配符(? super 类):某类及其父类

java
 
运行
 
 
 
 
// 添加元素:接收 Integer 及其父类的 List(Integer、Number、Object)
public static void addInt(List<? super Integer> list) {
    list.add(10); // 可添加 Integer 及其子类(如 10 是 Integer)
}
 

四、日常开发常用泛型类(JDK 内置)

Java 集合框架(CollectionListMap 等)都是泛型类,日常开发高频使用:
java
 
运行
 
 
 
 
// 1. List<String>:只能存字符串
List<String> strList = new ArrayList<>();
strList.add("Java");
String s = strList.get(0); // 无需强制转换

// 2. Map<String, Integer>:键是 String,值是 Integer
Map<String, Integer> scoreMap = new HashMap<>();
scoreMap.put("张三", 90);
scoreMap.put("李四", 85);
Integer score = scoreMap.get("张三"); // 无需强制转换

// 3. Set<Integer>:只能存整数(去重)
Set<Integer> numSet = new HashSet<>();
numSet.add(1);
numSet.add(1); // 重复元素,添加失败
 

五、避坑指南(新手常犯错误)

  1. 泛型类型不能是基本类型:比如 List<int> 错误,必须用包装类 List<Integer>(因为类型擦除后是 Object,基本类型不是 Object 子类);
  2. 泛型类的静态方法不能用类的类型参数:静态方法需单独声明泛型(变成泛型方法),因为静态方法属于类,创建对象前就存在,而类的类型参数是创建对象时指定的;
    java
     
    运行
    class Box<T> {
        // 错误:静态方法不能用类的 T
        // public static T getDefault() { return null; }
    
        // 正确:静态泛型方法(单独声明 <T>)
        public static <T> T getDefault() { return null; }
    }
     
     
  3. 泛型不支持协变List<Integer> 不是 List<Number> 的子类,即使 Integer 是 Number 子类(避免存入父类允许但子类不允许的类型);
  4. 通配符 ? 不能修改集合List<?> 只能读取元素(返回 Object),不能添加元素(不知道该加什么类型)。

总结

Java 泛型的核心是 “类型参数化”,核心价值是 类型安全 + 代码复用。新手入门记住 3 个重点:
  • 泛型类 / 接口:创建对象 / 实现接口时指定类型;
  • 泛型方法:方法返回值前加 <T>,灵活适配多种类型;
  • 通配符 ?:适配不确定的泛型类型,配合 extends/super 限制范围。
posted @ 2025-12-07 15:28  小java  阅读(29)  评论(0)    收藏  举报