CodeGym /课程 /JAVA 25 SELF /Generics:为什么需要,基础语法

Generics:为什么需要,基础语法

JAVA 25 SELF
第 26 级 , 课程 4
可用

1. “原始”集合(raw types)的问题

简单回顾一下历史。直到 Java 5,所有集合都是“来者不拒”的。它们存放的是 Object,编译器并不会检查你到底往里放了什么。想放字符串?可以。数字?也行。Cat?也能放。

// “原始”集合(raw types)示例,Java 5 之前
List list = new ArrayList();
list.add("Hello");
list.add(42);
list.add(new Object());

问题在于“取出并使用”时才会暴露:

String s = (String) list.get(0); // OK,这是字符串
String s2 = (String) list.get(1); // 砰!ClassCastException

编译器不吭声,但你会在运行时得到 ClassCastException。这就像一个标着“苹果”的盒子,里面放着杯子、香蕉和刺猬。

为什么不好?

  • 错误只会在运行时才暴露。
  • 类型混乱:不得不手动进行类型转换(cast)。
  • 代码可读性差且更危险。

解决方案——泛型(Generics)

Generics(泛型)是一种允许为类、接口和方法引入类型参数的机制。也就是说,你可以对集合说:“只存放字符串”,编译器会严格强制执行。

List<String> words = new ArrayList<>();
words.add("Hello");
words.add("World");
// words.add(42); // 编译错误!不能向 List<String> 添加 int

现在编译器不会允许你往列表里放入字符串以外的东西。错误在程序启动前的编译阶段就会被捕获。

泛型的核心思想:
为集合(以及其他结构)提供类型安全,使错误在编译期而不是在运行时被捕获。

2. 泛型语法:代码中如何表达

在尖括号中指定类型

创建集合时,请在 <> 中指定元素类型:

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // 错误:不能向字符串列表添加数字

String first = names.get(0); // 不需要类型转换!

经典用法:

  • List<String> — 字符串列表
  • List<Integer> — 整数列表
  • Set<Double> — 浮点数集合
  • Map<String, Integer> — 键为 String,值为 Integer

为什么不能只写 List

能写,但你会失去泛型的全部好处,编译器也会给出警告:

List list = new ArrayList(); // raw type — 不推荐!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Hello,ClassCastException!

现代 Java 代码一律使用泛型。

菱形操作符 <>

从 Java 7 开始,如果右侧类型可由上下文推断,可不再显式写出:

List<String> list = new ArrayList<>(); // 编译器会自行推断这里是 <String>

3. 在不同集合中使用泛型

ListSetMap 的示例

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);

Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");

Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 23);
ages.put("Bob", 31);

自定义类的示例

class Student {
    String name;
    int age;
    // ...
}

List<Student> students = new ArrayList<>();
students.add(new Student());

4. 实用细节

泛型的优势

类型安全。 编译器会确保集合中只出现指定类型的元素。

无需类型转换。 以前:String s = (String) list.get(0);。现在:String s = list.get(0);

代码更可读、更可靠。 运行时的意外更少。

泛型的限制

不能使用原始类型(primitive)。 泛型只适用于对象而非原始类型(intdoubleboolean)。请使用包装类:IntegerDoubleBoolean

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int 会自动装箱为 Integer(autoboxing)

关于类型擦除(type erasure)的简述

在 Java 中,泛型是通过类型擦除机制实现的:编译后,类型参数信息会被擦除,运行时 JVM 并不知道它曾是 List<String> 而不只是 List。这样做是为了向后兼容。

结果:不能使用带具体类型实参的 instanceof 来检查类型。

List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // 编译错误!

试图添加其他类型的元素——编译错误

List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // 编译错误:incompatible types: int cannot be converted to String

Map<String, Integer> map = new HashMap<>();
map.put("Cat", 5);
// map.put(3, "Elephant"); // 错误:键必须是 String,值必须是 Integer

这很棒:错误在编译阶段就会被捕获。

不仅仅是集合

泛型也可以用于自定义的类和方法。比如一个通用的“盒子”:

class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());

在集合中使用泛型是标准做法,但你也会在其他地方见到它们,例如 Stream APIOptional

5. 使用泛型时的常见错误

错误 #1:使用“原始”集合。 例如 List list = new ArrayList(); 会失去类型安全。务必指定类型参数,例如 List<String>

错误 #2:尝试使用原始类型。 不能写 List<int>,请使用 List<Integer>

错误 #3:从集合读取时手动进行类型转换。 如果你使用了泛型,就不需要形如 (String) list.get(i) 的转换。如果还需要——说明某处破坏了类型约束。

错误 #4:以为类型参数在运行时可用。 由于类型擦除,不能像 List<String> 那样配合具体类型参数使用 instanceof 检查。

错误 #5:在一个集合中混用不同类型。 如果声明了 List<String>,就不要尝试添加 Integer —— 编译器不会放行,这正是我们想要的。

1
任务
JAVA 25 SELF, 第 26 级, 课程 4
已锁定
我的数字图书馆目录 📚✍️
我的数字图书馆目录 📚✍️
1
任务
JAVA 25 SELF, 第 26 级, 课程 4
已锁定
魔法通用箱子 📦✨
魔法通用箱子 📦✨
1
调查/小测验
集合和 generics第 26 级,课程 4
不可用
集合和 generics
集合和 generics
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION