我的学习记录泛型

深入理解Java泛型:泛型类、泛型方法与泛型接口

一篇记录我学习Java泛型知识的博客。


为什么需要泛型?

在泛型出现之前,Java中的集合(如ArrayList)只能存储Object类型的对象,这意味着:

  • 类型不安全 :你可以向集合中放入任何类型的对象,取出时却需要强制转换,一旦类型不匹配,就会在运行时抛出ClassCastException
  • 代码冗余:针对不同类型的数据,我们经常需要编写大量重复的代码。

泛型(Generics)是Java 5引入的一个重要特性,它允许我们在定义类、接口和方法时使用类型参数 (Type Parameter),使得代码可以适用于多种数据类型,同时在编译期进行类型检查,从根本上解决了上述问题。

简单来说,泛型就是"类型的模板",让我们写一份代码,服务于多种类型。

一、泛型类(Generic Class)

泛型类是最常见的泛型使用方式。我们在类名后面加上尖括号<>,里面声明一个或多个类型参数,通常用大写字母表示(如TEKV)。### 基本语法

csharp 复制代码
public class 类名<类型参数列表> {
    // 可以使用类型参数定义字段、方法参数、返回值等
}

经典示例:Box 盒子类

假设我们要设计一个"盒子",可以装任何东西,但一旦装进去,取出时类型必须明确。

csharp 复制代码
public class Box<T> {
    private T content;

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

    public T get() {
        return content;
    }

    public static void main(String[] args) {
        // 存放String
        Box<String> stringBox = new Box<>();
        stringBox.set("Hello Generic");
        String str = stringBox.get();  // 无需强制转换
        System.out.println(str);

        // 存放Integer
        Box<Integer> intBox = new Box<>();
        intBox.set(123);
        Integer num = intBox.get();
        System.out.println(num);
    }
}

关键点:

实例化时,我们指定了具体类型(如Box),编译器会替换掉所有T的位置。

如果没有指定类型,则默认为Object(原始类型,但不推荐)。

泛型类中的静态成员不能使用类的类型参数,因为静态成员属于类本身,而类型参数是在实例化时才确定的。

二、泛型方法(Generic Method)

泛型方法是指在方法声明中定义自己的类型参数。它和泛型类没有必然联系------即使类不是泛型类,我们也可以定义泛型方法。

基本语法

arduino 复制代码
public <类型参数列表> 返回值类型 方法名(参数列表) {
    // 方法体
}

示例:一个通用的数组转List工具

ini 复制代码
public class GenericMethodDemo {

    // 这是一个泛型方法,类型参数为T
    public static <T> List<T> arrayToList(T[] array) {
        List<T> list = new ArrayList<>();
        for (T element : array) {
            list.add(element);
        }
        return list;
    }

    public static void main(String[] args) {
        String[] strArray = {"A", "B", "C"};
        List<String> strList = arrayToList(strArray); // 类型推断自动识别
        System.out.println(strList);

        Integer[] intArray = {1, 2, 3};
        List<Integer> intList = arrayToList(intArray);
        System.out.println(intList);
    }
}

泛型方法与泛型类的区别

  • 泛型类的类型参数作用于整个类,所有实例方法都可以使用它。

  • 泛型方法的类型参数只作用于该方法本身 ,且优先于类的类型参数(如果同名,以方法自己的为准)。

    三、泛型接口(Generic Interface)

泛型接口与泛型类的定义类似,在接口名后加上类型参数。

基本语法

csharp 复制代码
public interface 接口名<类型参数列表> {
    // 抽象方法可以使用类型参数
}

示例:生产者接口

定义一个Producer<T>接口,用于生产某种类型的产品。 java

csharp 复制代码
public interface Producer<T> {
    T produce();
}

// 实现类1:生产字符串
public class StringProducer implements Producer<String> {
    @Override
    public String produce() {
        return "Hello, I'm a string.";
    }
}

// 实现类2:生产随机整数(实现时指定具体类型)
public class IntegerProducer implements Producer<Integer> {
    @Override
    public Integer produce() {
        return new Random().nextInt(100);
    }
}

// 实现类3:仍然保留泛型(让子类再指定类型)
public class GenericProducer<T> implements Producer<T> {
    private T item;
    public GenericProducer(T item) { this.item = item; }
    @Override
    public T produce() { return item; }
}

实现方式总结

  1. 实现类直接指定具体类型(如StringProducer)。
  2. 实现类仍然是泛型类,把类型参数留给实例化时决定(如GenericProducer)。

四、通配符(Wildcard)与类型边界

在深入泛型时,我们常常会遇到通配符?,它用于表示未知类型,通常用在方法参数中,以增加灵活性。

  • 无界通配符<?> 表示任意类型,但只能读不能写(除了null)。
  • 上界通配符<? extends T> 表示T或T的子类,用于读取。
  • 下界通配符<? super T> 表示T或T的父类,用于写入。

示例:遍历任意类型的List

typescript 复制代码
public static void printList(List<?> list) {
    for (Object obj : list) { // 因为类型未知,只能用Object接收
        System.out.println(obj);
    }
}

PECS原则(Producer Extends, Consumer Super)

  • 如果参数是生产者(提供数据),用extends
  • 如果参数是消费者(消费数据),用super
    这是Java集合框架中广泛使用的原则。

最佳实践

  • 类型参数命名规范:T(Type)、E(Element)、K(Key)、V(Value)、S,U等。
  • 优先使用泛型方法而非在类上声明泛型(除非整个类都需要)。
  • 使用通配符时注意PECS原则。
  • 编写API时,尽量使用泛型提高类型安全性。

总结

通过这次学习,我深刻体会到泛型是Java静态类型系统的一大飞跃。它不仅让代码更安全(编译期检查),而且更通用(一份代码适应多种类型)。掌握泛型类、泛型方法和泛型接口的用法,以及通配符和类型擦除的底层原理,是写出高质量Java代码的基础。

在实际开发中,无论是集合框架、工具类还是设计模式(如工厂、建造者),泛型都无处不在。希望这篇博客能帮助你理清泛型的脉络,让你在编码时更加得心应手。

相关推荐
wuqingshun3141598 小时前
SpringBoot是如何实现自动配置的
java·spring boot·后端
zhouhui0018 小时前
AI帮我写了个Spring Boot校验,线上漏掉了这组边界条件
java·spring boot·redis·ai编程
JRedisX8 小时前
Java从零手写企业级内存数据库
java
萧瑟余晖8 小时前
JDK 20 新特性详解
java·开发语言
hdsoft_huge8 小时前
SpringBoot系列17:日志体系Logback配置,日志分割、脱敏、异常堆栈收集规范(生产级落地)
java·spring boot·logback
吃饱了得干活8 小时前
扫码登录如何实现?从原理到实战
java·spring boot·redis
JRedisX8 小时前
JRedisX 项目骨架搭建指南 从零开始 — 手把手教你搭
java
zx1154509 小时前
Java 反射 (SpringBoot篇)
java·架构
2301_794461579 小时前
Activiti/BPMN 2.0 的 4 种网关
java·服务器·开发语言