什么是泛型?有什么好处?

Java泛型(generics) 是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。

泛型的好处有两个:

  1. 方便 :消除强制类型转换,提高代码复用性。以 List 为例,使用泛型后取出元素时无需手动向下转型;如果你希望某个 List 只能存放 String,直接在声明时指定 List<String> 即可,编译器会在编译期保证类型匹配,无需为每种类型编写独立的数据结构实现。
  2. 安全:在泛型出现之前,通过 Object 实现的集合在取出元素时必须手动向下转型,转型错误要到运行时才能发现(ClassCastException)。泛型将类型检查提前到编译期,在编译时就能发现类型不匹配的问题,极大增强了程序的安全性。

知识扩展

泛型是如何实现的

Java中的泛型通过类型擦除 的方式来实现,通俗点理解,就是通过语法糖的形式,在 .java -> .class 转换的阶段,将 List<String> 擦除掉转为 List 的手段。换句话说,Java 的泛型只在编译期有效,JVM 在运行期是感知不到泛型的。

什么是类型擦除?

类型擦除的缺点有哪些?

  1. 不能以类型参数差异实现方法重载void foo(List<String>)void foo(List<Integer>) 擦除后签名相同,编译报错。
  2. 泛型类不能在多个 catch 子句中捕获不同类型的异常:因为异常类型在字节码中必须是具体且可区分的。
  3. 泛型类的静态变量只有一份 :由于 Foo<String>Foo<Integer> 经过擦除后对应同一个 Foo 类,其静态字段只有一份,不会因类型参数的不同而有多份副本。

对泛型通配符的理解

✅泛型中上下界限定符extends 和 super有什么区别?

List<?>, List<Object>, List 之间的区别

  1. List<?> 是通配符类型,表示持有某种未知类型的 List;List<Object> 则是明确可以存放 Object 及其子类的 List。可以将 List<String>List<Integer> 赋值给 List<?>,却不能将 List<String> 赋值给 List<Object>(泛型不支持协变)。
  2. 原始类型 List 可以接收任何带类型参数的 List(为了与遗留代码兼容),但将一个 List<String> 赋值给 List<Object> 会编译报错(泛型不变性)。
  3. List<?> 由于不确定元素的具体类型,只能从列表中读取元素(读出的元素类型为 ? 的边界,即 Object),不能向其中添加除了 null 以外的任何元素。

在泛型为 Integer 的 ArrayList 中存放一个 String 类型的对象

通过反射可以绕过编译期的泛型检查:

java 复制代码
public void test() throws Exception {
    ArrayList<Integer> list = new ArrayList<Integer>();
    Method method = list.getClass().getMethod("add", Object.class);
    method.invoke(list, "Java反射机制实例");
    System.out.println(list.get(0));
}

对数组协变和泛型非协变的理解

所谓协变,可以简单理解为因为 Object 是 String 的父类,所以 Object[] 同样是 String[] 的父类,这种情况 Java 是允许的;但是对于泛型来说,List<Object>List<String> 之间没有继承关系。

为什么要这样设计呢?如果泛型允许协变,考虑如下代码:

java 复制代码
List<Object> a = new ArrayList<String>(); // 编译错误:泛型不允许协变
// 若能执行到下行,允许协变就可以把一个 Integer 加进去
// a.add(1);
// String s = a.get(0); // 编译报错:取出的是 Object 而非 String

既然泛型不允许协变,为什么数组允许协变?原因有二:

  1. 数组在设计之初(Java 1.0)还没有泛型的概念,为了兼容性保留了协变特性,例如 Arrays.equals(Object[], Object[]) 等方法可以同时处理 String[]Integer[],是历史遗留的设计选择。
  2. 数组在运行时携带自身的组件类型信息,在尝试存入不兼容类型时会抛出 ArrayStoreException(运行时安全检查),而泛型在运行时类型信息已被擦除,无法做类似检查。数组的运行时类型检查在一定程度上弥补了协变带来的风险。
相关推荐
日取其半万世不竭10 小时前
用 Portainer 可视化管理 Docker:容器再多也不用背命令了
运维·docker·容器
2023自学中10 小时前
Linux 图形系统
linux
kirs_ur11 小时前
ECC & LDPC — SSD 的数据卫士
服务器·数据库·性能优化
mounter62513 小时前
高性能网络技术演进与创新探索:RDMA、eBPF/XDP 深度解析及 LSF/MM/BPF 2023 专题演讲
linux·ebpf·linux kernel·kernel·rdma·xdp
动恰客流统计13 小时前
ReID边缘计算视觉统计:餐饮店客流增长的数字化破局路径
java·大数据·运维·人工智能
名字还没想好☜13 小时前
Python itertools 实战:用 groupby、chain、islice 优雅处理大数据流
linux·windows·python·迭代器·itertools
ziguo112216 小时前
深入浅出 C/C++ 数据类型:从入门到踩坑
linux·c语言·c++·windows·visual studio
无垠的广袤16 小时前
【工业树莓派 CM0 Dev Board】扩展板设计
linux·python·嵌入式硬件·pcb设计·模块化·传感器
会编程的土豆16 小时前
MySQL 入门:库、表、行、主键是什么
linux·数据库·网络协议·http