在Java中,分为 原始类型(Raw Type)泛型类型(Generic Type) 两种不同的类型,主要区别如下:

  • 原始类型:在引入泛型之前,Java集合类(如List、Set、Map等)都是原始类型。它们可以存储任意类型的对象,但是原始类型存储的对象在取出时需要进行强制类型转换,如果转换的目标类型与实际存储的类型不兼容,就会在运行时抛出ClassCastException。这种错误在编译时不会被发现,增加了运行时失败的风险。

  • 泛型类型:Java 5 引入了泛型,允许在定义类、接口和方法时使用类型参数。泛型提供了编译时类型安全检查,可以将运行时错误转移到编译时。例如,List表示这个List只能存储String类型的对象,取出时不需要强制类型转换,并且编译器会确保类型安全。

原始类型是通用的,但是不安全,泛型是有限的通用,相对安全!
在代码审计的时候,对原始类型的使用,也是一个审查项。


两种类型的使用代码示例:

// 原始类型 - 不安全
List rawList = new ArrayList();
rawList.add("hello");
rawList.add(123);  // 可以添加不同类型
String str = (String) rawList.get(0);  // 需要强制转换
Integer num = (Integer) rawList.get(1);  // 运行时可能报错、失败

// 泛型类型 - 安全
List<String> genericList = new ArrayList<>();
genericList.add("hello");
// genericList.add(123);  // 编译错误
String str = genericList.get(0);  // 自动转换,不需要强制转换

自定义泛型类

// 定义泛型类
class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

// 使用
Box rawBox = new Box();          // 原始类型
rawBox.set("test");              // 可以设置任何类型
String s = (String) rawBox.get(); // 需要强制转换

Box<String> genericBox = new Box<>();  // 泛型类型
genericBox.set("test");
String s = genericBox.get();           // 自动获取String类型

类型擦除

什么是类型擦除?
Java的泛型是通过类型擦除实现的,这意味着:

  • 泛型信息只在编译时存在;
  • 运行时所有泛型类型都变为原始类型;
  • List<String> 和 List<Integer> 在运行时都是 List

类型擦除的作用:
类型擦除是Java泛型实现的一个折中方案,它使得泛型代码能够与旧版本Java代码兼容(这个是最主要的作用),同时不会带来运行时性能损失。
但是,类型擦除也带来了一些限制,需要在编程时注意。通过一些设计模式(如工厂模式)和反射,可以在一定程度上绕过这些限制。

类型擦除带来的限制:

  • 无法使用基本类型作为类型参数:
    因为类型擦除后替换为Object,而基本类型不是Object的子类,所以不能使用。
    例如,不能创建Box<int>,而必须使用Box<Integer>。

  • 无法获取泛型类型的具体类:
    由于运行时类型信息被擦除,无法在运行时获取泛型类型的具体类型。例如,不能使用new T(),因为不知道T的具体类型。

  • 无法创建泛型数组:
    不能直接创建泛型数组,例如new T[10],因为数组在创建时需要知道具体的类型,而泛型类型被擦除后无法确定。

  • 方法重载的冲突:
    由于类型擦除,两个重载方法可能擦除后变成相同的方法签名,导致编译错误。

绕过类型擦除的限制:

  • 使用反射:
    通过反射可以在运行时获取泛型类型信息,但需要额外的代码。

  • 使用工厂模式:
    通过传入类型标签(Class对象)来创建实例。

  • 使用泛型数组的变通方法:
    使用Array.newInstance(Class, int)来创建泛型数组。


总结

为什么泛型优于原始类型?

比较维度 原始类型 泛型
类型安全 无,运行时可能出错 编译时检查,更安全
代码可读性 差,需要文档说明类型 好,类型信息一目了然
维护性 差,容易引入错误 好,重构时编译器帮助检查
通用性 任意类型,使用不好容易出类型错误 不支持任意类型,但可通过设计实现安全的通用性
IDE支持 有限的代码补全 智能代码补全和检查

泛型通过容器模式,达到通用性:

// 设计一个通用的容器,但使用时类型安全
public class SafeContainer<T> {
    private T value;
    
    public SafeContainer(T value) {
        this.value = value;
    }
    
    public T getValue() {
        return value;
    }
    
    public void setValue(T value) {
        this.value = value;
    }
    
    // 通用操作
    public <R> R transform(Function<T, R> transformer) {
        return transformer.apply(value);
    }
}

// 使用:既通用又安全
SafeContainer<String> stringContainer = new SafeContainer<>("Hello");
SafeContainer<Integer> intContainer = new SafeContainer<>(123);

String result = stringContainer.getValue();  // 无需转换
Integer number = intContainer.getValue();    // 无需转换

当然还有很多其他设计模式也可以完成通用性......。


泛型其实是一种补丁?

如果从先后循序来看,泛型更像一种对原始类型的补丁(也可以类比为一种语法糖),在编译器层打了一个补丁,为了弥补类型使用不安全这个问题。

posted on 2025-12-17 17:40  Mysticbinary  阅读(3)  评论(0)    收藏  举报