Menu
CoddyTech

泛型入门

本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 53 课,共 87 课。

想象一下,你需要创建一个用于存储值并在之后取回它的类。如果没有泛型,你就得为每种类型分别编写一个类:一个用于 Integer,一个用于 String,一个用于 Double,依此类推。这会导致代码重复,也让维护变得麻烦。

泛型通过让你编写适用于任何类型的类和方法,同时保持类型安全,解决了这个问题。你可以定义一个占位符(称为类型参数),使用类时,它会被实际类型替换:

// 不使用泛型:使用 Object,需要类型转换
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // 需要手动进行类型转换

// 使用泛型:类型安全,无需类型转换
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // 无需类型转换

尖括号 <T> 表示类型参数。当你写 List<String> 时,就是在告诉编译器,这个列表只会包含字符串。如果你尝试添加整数,编译器会立即捕获错误,而不会等到运行时才失败。

泛型有两个主要好处:它们不需要显式类型转换,并且能在编译时而不是运行时发现类型错误。这样可以让你的代码更安全、更简洁。在接下来的课中,我们将探索如何创建自己的泛型类和方法。

challenge icon

挑战

简单

我们来构建一个简单的存储系统,通过对比旧方法(使用 Object 并进行类型转换)和新方法(使用泛型并确保类型安全),展示泛型的强大之处。

你将创建 3 个文件,亲自看看它们的区别:

  • OldBox.java:创建一个类,用泛型出现之前的方式存储值。你的 OldBox 应该有一个类型为 Object 的私有字段 content。添加一个接收 Object 并将其存储起来的构造函数,以及一个返回 Object 的 getContent() 方法。这种方法可行,但在获取值时需要进行类型转换。
  • GenericBox.java:现在用泛型创建现代且类型安全的版本。你的 GenericBox 声明中应该使用类型参数 T。它需要一个类型为 T 的私有字段 content、一个接收 T 值的构造函数,以及一个返回类型为 T 的 getContent() 方法。使用这个类时不需要进行类型转换!
  • Main.java:并排演示这两种方法。你会收到两个输入:一个单词(String)和一个数字(整数)。

    首先,使用旧方法:创建一个存储该单词的 OldBox,然后将其转换为 String 再取出并打印:OldBox (with cast): [value]

    接下来,使用泛型方法:创建一个存储同一单词的 GenericBox<String>,不进行类型转换就取出该单词,并打印:GenericBox (no cast): [value]

    然后,演示泛型如何处理整数:创建一个存储该数字的 GenericBox<Integer>,取出该数字并打印:GenericBox Integer: [value]

    最后,打印一个空行,然后打印:Type safety: Generics catch errors at compile time!

你会依次收到两个输入:一个单词(String)和一个数字(以 String 形式表示的整数,你需要对其进行解析)。

注意关键区别:使用 OldBox 时,你必须进行类型转换 (String) box.getContent(),并希望得到的类型正确。使用 GenericBox 时,编译器知道类型,因此不需要进行类型转换。这就是泛型的核心优势:确保类型安全,同时省去类型转换的麻烦!

亲手试试

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String word = scanner.nextLine();
        int number = Integer.parseInt(scanner.nextLine());
        
        // TODO: 创建一个存储 word 的 OldBox
        // 将取出的值强制转换为 String,然后打印:"OldBox (with cast): [value]"
        
        // TODO: 创建一个存储 word 的 GenericBox<String>
        // 无需强制类型转换,取出值并打印:"GenericBox (no cast): [value]"
        
        // TODO: 创建一个存储 number 的 GenericBox<Integer>
        // 取出值并打印:"GenericBox Integer: [value]"
        
        // TODO: 先打印一个空行,然后打印:
        // "Type safety: Generics catch errors at compile time!"
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Java 在线编译器