原始类型与泛型对比笔记

目录


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

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

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

原始类型是通用的,但是不安全,泛型是有限的通用,相对安全!

在代码审计的时候,对原始类型的使用,也是一个审查项。


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

java 复制代码
// 原始类型 - 不安全
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);  // 自动转换,不需要强制转换

自定义泛型类

java 复制代码
// 定义泛型类
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支持 有限的代码补全 智能代码补全和检查

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

java 复制代码
// 设计一个通用的容器,但使用时类型安全
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();    // 无需转换

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


泛型其实是一种补丁?

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

相关推荐
江上清风山间明月1 个月前
YAML语法详解
语法·yaml
Trouvaille ~1 个月前
【C++篇】C++11新特性详解(一):基础特性与类的增强
c++·stl·c++11·类和对象·语法·默认成员函数·初始化列表
gis分享者1 个月前
如何在 Shell 脚本中如何使用条件判断语句?(中等)
面试·shell·脚本·语法·使用·判断·条件
缘三水2 个月前
【C语言】17.字符函数和字符串函数
c语言·开发语言·语法
小李不困还能学2 个月前
markdown语法以及快捷键大全
markdown·语法
缘三水2 个月前
【C语言】16.指针(6)进阶篇目——数组与指针笔试题目详解
c语言·开发语言·指针·语法
缘三水2 个月前
【C语言】15.指针(5)
c语言·开发语言·指针·语法
缘三水2 个月前
【C语言】14.指针(4)
c语言·开发语言·指针·语法
缘三水2 个月前
【C语言】13.指针(3)
c语言·开发语言·指针·语法