Java 泛型使用教程

简介

Java 泛型是 JDK 5 引入的一项特性,它提供了编译时类型安全检测机制,允许在编译时检测出非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型的好处:

  • 编译期检查类型安全
  • 避免强制类型转换(cast
  • 代码更通用,更易重用

泛型的基本使用

泛型类

public class Box {
    private T content;

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

    public T get() {
        return content;
    }
}

使用:

Box stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get(); // 无需强转

泛型方法

public class Util {
    public static  void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element);
        }
    }
}

使用:

String[] arr = {"A", "B", "C"};
Util.printArray(arr);

泛型接口

public interface Converter {
    T convert(F from);
}

public class StringToIntegerConverter implements Converter {
    public Integer convert(String from) {
        return Integer.parseInt(from);
    }
}

泛型中的通配符:?

? 通配符

表示任意类型:

public void printList(List list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

? extends T(上界通配符)

表示 TT 的子类(只读,不能写):

public void printNumbers(List list) {
    for (Number num : list) {
        System.out.println(num);
    }
}

不能添加元素:

list.add(10); // ❌ 报错

? super T(下界通配符)

表示 TT 的父类(可以写入,但取出只能当作 Object):

public void addNumbers(List list) {
    list.add(10); // ✅ OK
}

类型擦除(Type Erasure)

泛型在编译后会被擦除,JVM 不知道泛型类型,全部变成 Object

List list = new ArrayList<>();
List list2 = new ArrayList<>();
System.out.println(list.getClass() == list2.getClass()); // true

正因如此:

  • 不能使用 new T()
  • 不能使用 T.class
  • 不能判断 instanceof T

常见的泛型类库例子

  • List:存储 T 类型的集合
  • Map:泛型键值对
  • Optional:包装返回值
  • Comparable:排序比较接口
  • Function:函数式接口
  • Callable:异步任务的返回类型
  • Future:异步计算的结果

进阶技巧

泛型数组不允许:

List[] lists = new List[10]; // ❌ 编译错误

泛型静态方法必须声明

public static  void print(T value) {
    System.out.println(value);
}

实际项目中 Java 泛型的用法大全

通用返回封装类(统一 API 响应格式)

public class Result {
    private int code;
    private String message;
    private T data;

    public Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static  Result success(T data) {
        return new Result<>(200, "Success", data);
    }

    public static  Result fail(String message) {
        return new Result<>(500, message, null);
    }

    // getter/setter omitted
}

通用分页结果类

public class PageResult {
    private List records;
    private long total;

    public PageResult(List records, long total) {
        this.records = records;
        this.total = total;
    }
}

结合 Spring 使用泛型的示例

通用 Service 接口

public interface BaseService {
    T findById(ID id);
    List findAll();
    T save(T entity);
    void deleteById(ID id);
}

抽象 Service 实现类

public abstract class AbstractBaseService implements BaseService {

    @Autowired
    protected JpaRepository repository;

    @Override
    public T findById(ID id) {
        return repository.findById(id).orElse(null);
    }

    @Override
    public List findAll() {
        return repository.findAll();
    }

    @Override
    public T save(T entity) {
        return repository.save(entity);
    }

    @Override
    public void deleteById(ID id) {
        repository.deleteById(id);
    }
}

具体 Service

@Service
public class UserService extends AbstractBaseService {
    // 可以扩展额外业务逻辑
}

Java 泛型 与 C#.net 泛型比较

Java 泛型 与 C# (.NET) 泛型有很多相似之处,C# 泛型的设计部分参考了 Java。但它们在类型擦除、协变/逆变、约束、运行时行为等方面有显著的不同。

特性 Java 泛型 C# (.NET) 泛型
类型擦除 是(编译期擦除) 否(保留类型信息)
运行时可反射获取泛型类型
基本类型支持 不直接支持(需使用包装类如 Integer 支持,例如 List
泛型约束 限制(只能 extends,不支持多个约束) 强大(支持 where T : class, new(), 多个接口)
协变/逆变支持 通过通配符 ? extends/super 直接使用 out/in 关键字
泛型数组 不支持(如 new T[] 编译错误) 支持
泛型方法 支持 支持

类型擦除 vs 保留类型

  • Java
List list = new ArrayList<>();
List intList = new ArrayList<>();
System.out.println(list.getClass() == intList.getClass()); // true

Java 在编译时擦除了泛型信息,ListList 其实是同一个字节码类。

  • C#
List list = new List();
List intList = new List();
Console.WriteLine(list.GetType() == intList.GetType()); // false

C# 会为不同泛型参数生成不同的类实例,因此保留类型信息。

泛型约束

  • Java
public class Repository {
    public void save(T entity) { }
}
  • C#
public class Repository where T : BaseEntity, new() {
    public void Save(T entity) { }
}

C# 支持更丰富的泛型约束,例如要求是引用类型 class、值类型 struct、必须有无参构造函数 new() 等。

协变与逆变(Covariance & Contravariance)

  • Java 使用通配符
List numbers;  // 只能读取,不能添加
List integers;  // 只能添加 Integer
  • C# 使用 in / out
interface ICovariant { }
interface IContravariant { }

C# 在接口中用 in/out 明确支持协变逆变,且更强大、更类型安全。

泛型数组

  • Java
T[] array = new T[10]; // 编译错误
  • C#
T[] array = new T[10]; // 合法

基本类型泛型

  • Java
List list = new ArrayList<>(); // 编译错误
List list = new ArrayList<>(); // 正确
  • C#
List list = new List(); // 正确,泛型支持值类型

总结

特性 Java C#
类型安全 ✔️ ✔️
灵活性 ❌(类型擦除限制) ✔️(运行时保留泛型)
泛型数组 ✔️
基本类型支持 ❌(需包装) ✔️
泛型约束 一般 强大
协变逆变 复杂、通配符语法 简洁、原生支持
性能 需装箱 无装箱(对值类型更快)

协变逆变详解

协变(Covariance)和逆变(Contravariance)是泛型类型系统中用于处理子类和父类之间的泛型关系的重要机制。

Java 的协变(Covariant)与逆变(Contravariant)

Java 使用 通配符(wildcards) 来支持:

协变(? extends T) — 只读
  • 意思是:某个未知类型是 T 的子类
  • 常用于“只能读”的场景
public void readAnimals(List animals) {
    for (Animal animal : animals) {
        System.out.println(animal);
    }
}

可以传入 List、List 或 List,但不能添加新元素。

animals.add(new Cat()); // ❌ 编译错误
逆变(? super T) — 只写
  • 意思是:某个未知类型是 T 的父类
  • 常用于“只能写”的场景
public void addCats(List cats) {
    cats.add(new Cat());     // ✅ 合法
    // cats.add(new Animal()); // ❌ 不安全
}

可以传入 List、List、List,但不能安全地读取具体类型:

Object obj = cats.get(0); // 只能作为 Object 使用

C# 中的协变与逆变

  • C# 协变(out)
interface IReadOnlyList {
    T Get(int index);
}

IReadOnlyList animals = new List(); // ✅ 协变成功
  • C# 逆变(in)
interface IWriter {
    void Write(T value);
}

IWriter writer = new AnimalWriter(); // ✅ 逆变成功

完整示例:协变 & 逆变

import java.util.*;

class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }

public class VarianceDemo {

    public static void main(String[] args) {
        List cats = new ArrayList<>();
        cats.add(new Cat());

        readAnimals(cats); // ✅ 协变
        addCats(cats);     // ✅ 逆变
    }

    // 协变:读取
    public static void readAnimals(List animals) {
        for (Animal a : animals) {
            System.out.println("Animal: " + a);
        }
        // animals.add(new Dog()); // ❌ 编译错误
    }

    // 逆变:写入
    public static void addCats(List animals) {
        animals.add(new Cat()); // ✅
    }
}

总结

特性 协变 逆变
Java 关键字 ? extends T ? super T
C# 关键字 out T in T
适合场景 读取数据 写入数据
是否可写
是否可读 ❌(只能当 Object)

你可能感兴趣的:(java)