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

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(运行时安全检查),而泛型在运行时类型信息已被擦除,无法做类似检查。数组的运行时类型检查在一定程度上弥补了协变带来的风险。
相关推荐
未济5 天前
linux 配置环境变量
linux
傲世仙尊5 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
虎头金猫5 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
_艾伦 耶格尔.5 天前
进程间通信
linux
AI职业加油站5 天前
AI智能体应用工程师证书:政策红利下的职业新风口
大数据·运维·人工智能·学习·职场发展
Liuqy-055 天前
Linux IO编程——静态库、动态库
linux
此冬歌咏5 天前
K8s 节点故障实战:优雅驱逐 31 秒,硬故障 331 秒,以及那个永远 Pending 的 Pod
运维·k8s
彧azz5 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
-梅5 天前
linux(8) 软硬链接
linux·运维·服务器
Wang's Blog5 天前
Java 项目实战: 外卖平台-文件下载与ServletOutputStream回写浏览器
服务器·项目开发