128. Java 泛型 - 类型推断和泛型方法

128. Java 泛型 - 类型推断和泛型方法

1. 什么是类型推断?

类型推断Type Inference)是 Java 编译器的能力,它可以在方法调用时自动推断出泛型类型,而无需程序员显式指定类型参数。这意味着,在调用泛型方法时,我们通常不需要手动提供类型参数,编译器会根据方法的参数类型返回值类型等信息推断出合适的泛型类型。

示例:基本的类型推断

来看一个简单的泛型方法 pick

static <T> T pick(T a1, T a2) {
    return a2;
}

这里的 <T> 代表一个泛型类型参数,它将在方法调用时由 Java 编译器自动推断。

类型推断示例:

Serializable s = pick("d", new ArrayList<String>());

在这行代码中,方法 pick("d", new ArrayList<String>()) 传入了两个不同的参数:

  • "d"String 类型
  • new ArrayList<String>()ArrayList<String> 类型

Java 编译器会推断出最具体的公共类型,在这里 StringArrayList<String> 共同的父类型是 Serializable,所以 pick 方法的返回值被推断为 Serializable

2. 泛型方法中的类型推断

泛型方法的类型推断允许我们像调用普通方法一样调用泛型方法,而无需在尖括号 <> 中指定类型参数。

来看一个实际的例子,BoxDemo 类演示了如何使用泛型方法来操作 Box<U> 类型的列表:

BoxDemo 类:

import java.util.ArrayList;
import java.util.List;

class Box<T> {
    private T value;

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

    public T get() {
        return value;
    }
}

public class BoxDemo {

    // 泛型方法:向列表中添加 Box<U> 类型的对象
    public static <U> void addBox(U u, List<Box<U>> boxes) {
        Box<U> box = new Box<>(); // 创建 Box<U> 实例
        box.set(u);
        boxes.add(box); // 将 Box<U> 添加到列表
    }

    // 泛型方法:输出列表中的所有 Box<U> 内容
    public static <U> void outputBoxes(List<Box<U>> boxes) {
        int counter = 0;
        for (Box<U> box : boxes) {
            U boxContents = box.get();
            System.out.println("Box #" + counter + " contains [" +
                    boxContents.toString() + "]");
            counter++;
        }
    }

    public static void main(String[] args) {
        List<Box<Integer>> listOfIntegerBoxes = new ArrayList<>();

        // 方式 1:显式指定类型参数 <Integer>
        BoxDemo.<Integer>addBox(10, listOfIntegerBoxes);

        // 方式 2:省略类型参数,编译器自动推断
        BoxDemo.addBox(20, listOfIntegerBoxes);
        BoxDemo.addBox(30, listOfIntegerBoxes);

        // 输出 Box 列表的内容
        BoxDemo.outputBoxes(listOfIntegerBoxes);
    }
}

运行结果:

Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]

3. 类型推断的工作方式

BoxDemo 类中,addBox 是一个泛型方法:

public static <U> void addBox(U u, List<Box<U>> boxes) { ... }

这个方法接受:

  • 一个类型为 U 的元素 u
  • 一个存储 Box<U> 的列表 boxes

方式 1:显式指定类型参数

BoxDemo.<Integer>addBox(10, listOfIntegerBoxes);

这里,我们在调用 addBox 时手动指定了 <Integer>,明确告诉编译器 U 应该是 Integer

方式 2:省略类型参数,自动推断

BoxDemo.addBox(20, listOfIntegerBoxes);

在这里,我们没有显式写 <Integer>,但编译器通过方法参数 20(它是 Integer)推断出了 U 的类型。因此,addBox 方法会被当作 addBox(Integer u, List<Box<Integer>> boxes) 进行编译。

4. 为什么类型推断很重要?

类型推断使代码更加简洁,减少了重复性。如果没有类型推断,我们在每次调用泛型方法时都需要手动提供类型参数,这会增加代码的冗余。例如,如果 Java 不支持类型推断,那么我们必须这样写:

BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.<Integer>addBox(Integer.valueOf(20), listOfIntegerBoxes);
BoxDemo.<Integer>addBox(Integer.valueOf(30), listOfIntegerBoxes);

但有了类型推断,我们只需要这样写:

BoxDemo.addBox(10, listOfIntegerBoxes);
BoxDemo.addBox(20, listOfIntegerBoxes);
BoxDemo.addBox(30, listOfIntegerBoxes);

这样不仅使代码更加清晰,也减少了不必要的重复信息

5. 什么时候编译器无法推断类型?

虽然 Java 编译器通常可以成功推断泛型方法的类型参数,但在某些情况下,它可能无法确定具体的类型,比如:

示例 1:类型不匹配

BoxDemo.addBox(null, listOfIntegerBoxes); // 编译错误

这里,null 没有具体的类型信息,编译器无法推断 U 的类型,因此会报错。

示例 2:返回值丢失

var result = pick("string", 123); // 编译错误

如果 pick 方法的两个参数类型不同(如 StringInteger),编译器无法推断 T 的具体类型。

示例 3:泛型方法调用返回值未使用

pick("Hello", "World"); // 可能编译警告

如果泛型方法的返回值没有赋值给变量,编译器可能会发出警告。

6. 结论

  • 类型推断 使 Java 泛型方法的调用更加简洁,不需要手动指定类型参数。
  • 编译器可以根据方法的参数类型和返回值推断泛型类型,减少重复的 <T> 指定。
  • 在某些情况下,编译器无法推断类型,比如 null 作为参数或不同类型参数混用时,需要手动指定类型参数。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 3,252评论 0 5
  • 为了让我有一个更快速、更精彩、更辉煌的成长,我将开始这段刻骨铭心的自我蜕变之旅!从今天开始,我将每天坚持阅...
    李薇帆阅读 1,874评论 0 2
  • 似乎最近一直都在路上,每次出来走的时候感受都会很不一样。 1、感恩一直遇到好心人,很幸运。在路上总是...
    时间里的花Lily阅读 1,333评论 0 1
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 501评论 0 1
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 516评论 0 0