1. 简介

在本篇短文中,我们将深入探讨 Java 中的 Raw Types(原始类型),了解它们的本质以及为何要尽量避免使用。

如果你写 Java 代码时还在用 List list = new ArrayList(); 这种写法,那你可能正在使用原始类型。虽然它看起来简单,但背后隐藏了不少坑。

2. 什么是原始类型?

原始类型指的是 泛型类或接口在使用时未指定类型参数 的情况。例如:

List list = new ArrayList(); // 原始类型

而不是:

List<Integer> listIntgrs = new ArrayList<>(); // 参数化类型

在这里,List<Integer> 是接口 List<E> 的参数化类型,而 List 是其对应的原始类型。

原始类型在对接遗留非泛型代码时可能有用,但在现代 Java 开发中,不推荐使用。原因主要有以下几点:

表达性差
缺乏类型安全
运行时才暴露问题

3. 表达性差

原始类型无法像参数化类型那样清晰地传达其用途。比如:

  • List<String> 明确表示这是一个字符串列表;
  • List 则什么信息都不提供。

我们来看 List 接口中的 get(int index) 方法定义:

E get(int index);

在参数化类型中,比如 List<String>,这个方法返回的就是 String
但在原始类型中,它返回的是 Object,你需要手动进行类型转换。

这就带来了额外的工作,也增加了出错的可能性。而且,这种错误不会在编译时被发现。

4. 缺乏类型安全

使用原始类型会退化到泛型出现前的 Java 行为:可以添加任何类型的对象。这在混合使用参数化类型和原始类型时特别危险。

举个例子:

public void methodA() {
    List<String> parameterizedList = new ArrayList<>();
    parameterizedList.add("Hello Folks");
    methodB(parameterizedList);
}

public void methodB(List rawList) { // 原始类型!
    rawList.add(1);
}

上面这段代码 可以编译通过(带警告),但运行时会在一个 List<String> 中插入了一个 Integer

编译器警告如下:

Note: RawTypeDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

5. 运行时问题频发

因为缺乏类型安全,这种错误往往在运行时才会暴露出来。

我们来改写上面的例子,在 methodA 中尝试获取第 1 个元素:

public void methodA() {
    List<String> parameterizedList = new ArrayList<>();
    parameterizedList.add("Hello Folks");
    methodB(parameterizedList);
    String s = parameterizedList.get(1); // 这里会出问题
}

运行时会抛出如下异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

这个错误本来可以在编译阶段被发现,但因为用了原始类型,直到运行时才暴露

6. 总结

原始类型虽然语法简单,但存在严重的类型安全问题和运行时风险。它们:

  • 不具备表达性,API 难以理解;
  • 缺乏类型检查,容易混入错误数据;
  • 错误延迟到运行时才暴露,排查成本高。

建议:始终使用参数化类型
避免使用原始类型,除非你真的在对接遗留代码

否则,你的代码可能在运行时“爆炸”,而你却毫无准备。


原始标题:Raw Types in Java | Baeldung