3

我有一个简单的通用方法,它创建一个 n 元素列表并返回它:

import java.util.*;
class A {
    public static <T> List<T> init(int n) {
        List<T> l = new ArrayList<T>();
        while (n --> 0) l.add(null);
        return l;
    }
    public static void main(String[] args) {
        List<String> x = init(5);
        f(x);
    }
    public static void f(List<String> l) {
        System.out.println("l: " + l);
    }
}

它按预期工作:

$ javac A.java && java A
l: [null, null, null, null, null]

但是如果我消除额外的变量:

import java.util.*;
class A {
    public static <T> List<T> init(int n) {
        List<T> l = new ArrayList<T>();
        while (n --> 0) l.add(null);
        return l;
    }
    public static void main(String[] args) {
        f(init(5));
    }
    public static void f(List<String> l) {
        System.out.println("l: " + l);
    }
}

它不再编译

$ javac A.java && java A
A.java:9: f(java.util.List<java.lang.String>) in A cannot be applied to (java.util.List<java.lang.Object>)
        f(init(5));
        ^
1 error

为什么?

但这有效:

import java.util.*;
class A {
    public static <T> List<T> init(int n) {
        List<T> l = new ArrayList<T>();
        while (n --> 0) l.add(null);
        return l;
    }
    public static <T> T id(T t) {
        return t;
    }
    public static void main(String[] args) {
        List<String> x = init(5);
        f(id(x));
    }
    public static void f(List<String> l) {
        System.out.println("l: " + l);
    }
}

为什么?

4

5 回答 5

3

Java 依靠推理来确定类型变量在未显式定义的情况下是什么。

在您的第一个示例中:

List<String> x = init(5);
f(x);

编译器推断您正在调用<String> init,因为x它是一个List<String>.


在您的第二个示例中:

f(init(5));

编译器无法推断您正在调用<String> init,因为您没有明确地告诉它(通过A. <String> init(5)),也没有将它分配给适当的变量。


在您的第三个示例中:

List<String> x = init(5);
f(id(x));

编译器推断您正在调用<List<String>> id返回 a List<String>to f


编译器对泛型推理不太聪明。除非您通过使用变量或直接将它们传递给方法来明确说明类型参数是什么,否则它将无法弄清楚它们。

如果您对细节感到好奇,以下是 JLS 的相关部分:

于 2013-06-23T15:33:38.377 回答
2

T如果你省略了额外的变量,编译器在调用时无法推断类型参数init(5)。它假定TObject编译器错误。

将额外变量声明为List<String> x编译器推断TString.

于 2013-06-23T15:27:53.303 回答
2

一是修复;

f(A.<String>init(5)); // compiles

现在,为什么:编译的原始代码是因为 java 可以通过分配给类型化变量来推断类型。但是当传递给类型化参数时,推理不起作用。

该修复程序使用在调用类型化方法时显式指定类型的语法。

于 2013-06-23T15:50:22.183 回答
1
f(init(5));

f()使用直接从 接收的参数调用init()。但是,init()现在返回 a Listof TT没有指定 Java 只使用它Object,因为它是所有对象的基类。List<Object>is not List<String>,所以方法签名和参数不匹配。

List<String> x = init(5);
f(x);

在这里,您将 放入List<Object>类型的变量List<String>。这会转换它,因为String它当然是Object. 转换也成功,因为null也可以转换String为任何其他类。Thenx的类型与方法签名匹配。

List<String> x = init(5);
f(id(x));

这基本上是一样的。现在,因为x有类型List<String>id()的类型TString。这种方式的返回值id()也是List<String>,它与签名匹配。

于 2013-06-23T15:29:14.573 回答
0

在第一个和第三个示例中,您都说您正在谈论 a List<String>,但在您说的第二个示例中f(init(5)),它可能是 a List<Integer>。我不是 100% 这是唯一的原因,但请检查一下 :)

于 2013-06-23T15:26:05.813 回答