找回密码
 立即注册
首页 业界区 业界 原始类型与泛型对比笔记

原始类型与泛型对比笔记

晖顶蝇 2 小时前
目录

  • 两种类型的使用代码示例:
  • 自定义泛型类
  • 类型擦除
  • 总结

    • 泛型其实是一种补丁?


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

  • 原始类型:在引入泛型之前,Java集合类(如List、Set、Map等)都是原始类型。它们可以存储任意类型的对象,但是原始类型存储的对象在取出时需要进行强制类型转换,如果转换的目标类型与实际存储的类型不兼容,就会在运行时抛出ClassCastException。这种错误在编译时不会被发现,增加了运行时失败的风险。
  • 泛型类型:Java 5 引入了泛型,允许在定义类、接口和方法时使用类型参数。泛型提供了编译时类型安全检查,可以将运行时错误转移到编译时。例如,List表示这个List只能存储String类型的对象,取出时不需要强制类型转换,并且编译器会确保类型安全。
原始类型是通用的,但是不安全,泛型是有限的通用,相对安全!
在代码审计的时候,对原始类型的使用,也是一个审查项。
两种类型的使用代码示例:
  1. // 原始类型 - 不安全
  2. List rawList = new ArrayList();
  3. rawList.add("hello");
  4. rawList.add(123);  // 可以添加不同类型
  5. String str = (String) rawList.get(0);  // 需要强制转换
  6. Integer num = (Integer) rawList.get(1);  // 运行时可能报错、失败
  7. // 泛型类型 - 安全
  8. List<String> genericList = new ArrayList<>();
  9. genericList.add("hello");
  10. // genericList.add(123);  // 编译错误
  11. String str = genericList.get(0);  // 自动转换,不需要强制转换
复制代码
自定义泛型类
  1. // 定义泛型类
  2. class Box<T> {
  3.     private T value;
  4.     public void set(T value) { this.value = value; }
  5.     public T get() { return value; }
  6. }
  7. // 使用
  8. Box rawBox = new Box();          // 原始类型
  9. rawBox.set("test");              // 可以设置任何类型
  10. String s = (String) rawBox.get(); // 需要强制转换
  11. Box<String> genericBox = new Box<>();  // 泛型类型
  12. genericBox.set("test");
  13. String s = genericBox.get();           // 自动获取String类型
复制代码
类型擦除

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

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

  • 无法使用基本类型作为类型参数:
    因为类型擦除后替换为Object,而基本类型不是Object的子类,所以不能使用。
    例如,不能创建Box,而必须使用Box。
  • 无法获取泛型类型的具体类:
    由于运行时类型信息被擦除,无法在运行时获取泛型类型的具体类型。例如,不能使用new T(),因为不知道T的具体类型。
  • 无法创建泛型数组:
    不能直接创建泛型数组,例如new T[10],因为数组在创建时需要知道具体的类型,而泛型类型被擦除后无法确定。
  • 方法重载的冲突:
    由于类型擦除,两个重载方法可能擦除后变成相同的方法签名,导致编译错误。
绕过类型擦除的限制:

  • 使用反射:
    通过反射可以在运行时获取泛型类型信息,但需要额外的代码。
  • 使用工厂模式:
    通过传入类型标签(Class对象)来创建实例。
  • 使用泛型数组的变通方法:
    使用Array.newInstance(Class, int)来创建泛型数组。
总结

为什么泛型优于原始类型?
比较维度原始类型泛型类型安全无,运行时可能出错编译时检查,更安全代码可读性差,需要文档说明类型好,类型信息一目了然维护性差,容易引入错误好,重构时编译器帮助检查通用性任意类型,使用不好容易出类型错误不支持任意类型,但可通过设计实现安全的通用性IDE支持有限的代码补全智能代码补全和检查泛型通过容器模式,达到通用性:
  1. // 设计一个通用的容器,但使用时类型安全
  2. public class SafeContainer<T> {
  3.     private T value;
  4.    
  5.     public SafeContainer(T value) {
  6.         this.value = value;
  7.     }
  8.    
  9.     public T getValue() {
  10.         return value;
  11.     }
  12.    
  13.     public void setValue(T value) {
  14.         this.value = value;
  15.     }
  16.    
  17.     // 通用操作
  18.     public <R> R transform(Function<T, R> transformer) {
  19.         return transformer.apply(value);
  20.     }
  21. }
  22. // 使用:既通用又安全
  23. SafeContainer<String> stringContainer = new SafeContainer<>("Hello");
  24. SafeContainer<Integer> intContainer = new SafeContainer<>(123);
  25. String result = stringContainer.getValue();  // 无需转换
  26. Integer number = intContainer.getValue();    // 无需转换
复制代码
当然还有很多其他设计模式也可以完成通用性......。
泛型其实是一种补丁?

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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册