【问题标题】:Collections.emptyList() returns a List<Object>?Collections.emptyList() 返回一个 List<Object>?
【发布时间】:2008-11-20 20:17:33
【问题描述】:

我在使用 Java 推断泛型类型参数的规则时遇到了一些麻烦。考虑下面的类,它有一个可选的列表参数:

import java.util.Collections;
import java.util.List;

public class Person {
  private String name;
  private List<String> nicknames;
  
  public Person(String name) {
    this(name, Collections.emptyList());
  }
  
  public Person(String name, List<String> nicknames) {
    this.name = name;
    this.nicknames = nicknames;
  }
}

我的 Java 编译器出现以下错误:

Person.java:9: The constructor Person(String, List<Object>) is undefined

但是Collections.emptyList() 返回类型&lt;T&gt; List&lt;T&gt;,而不是List&lt;Object&gt;。添加演员表没有帮助

public Person(String name) {
  this(name,(List<String>)Collections.emptyList());
}

产量

Person.java:9: inconvertible types

使用EMPTY_LIST 代替emptyList()

public Person(String name) {
  this(name, Collections.EMPTY_LIST);
}

产量

Person.java:9: warning: [unchecked] unchecked conversion

而以下更改使错误消失:

public Person(String name) {
  this.name = name;
  this.nicknames = Collections.emptyList();
}

谁能解释一下我在这里遇到了什么类型检查规则,以及解决它的最佳方法?在这个例子中,最终的代码示例是令人满意的,但是对于更大的类,我希望能够在不重复代码的情况下按照这种“可选参数”模式编写方法。

加分:什么时候使用EMPTY_LIST 而不是emptyList() 比较合适?

【问题讨论】:

标签: java generics type-inference


【解决方案1】:

您遇到的问题是,即使emptyList() 方法返回List&lt;T&gt;,您还没有提供类型,所以它默认返回List&lt;Object&gt;。您可以提供类型参数,并让您的代码按预期运行,如下所示:

public Person(String name) {
  this(name,Collections.<String>emptyList());
}

现在,当您进行直接赋值时,编译器可以为您找出泛型类型参数。这称为类型推断。例如,如果您这样做:

public Person(String name) {
  List<String> emptyList = Collections.emptyList();
  this(name, emptyList);
}

那么emptyList() 调用将正确返回List&lt;String&gt;

【讨论】:

  • 知道了。来自 ML 世界,我很奇怪 Java 无法推断出正确的类型:形参的类型和 emptyList 的返回类型显然是统一的。但我猜类型推断器只能采取“婴儿步骤”。
  • 在一些简单的情况下,编译器似乎可以推断出这种情况下缺少的类型参数——但这可能很危险。如果存在具有不同参数的方法的多个版本,您最终可能会调用错误的方法。而第二个可能还不存在……
  • 这种表示法“Collections.emptyList()”真的很奇怪,但很有意义。比 Enum>。 :)
  • 在 Java 8 中不再需要提供类型参数(除非可能的泛型类型有歧义)。
  • 第二个 sn-p 确实很好地显示了类型推断,但当然不会编译。对this 的调用必须是构造函数中的第一条语句。
【解决方案2】:

你想使用:

Collections.<String>emptyList();

如果您查看 emptyList 的源代码,您会发现它实际上只是做了一个

return (List<T>)EMPTY_LIST;

【讨论】:

    【解决方案3】:

    emptyList 方法有这个签名:

    public static final <T> List<T> emptyList()
    

    List 之前的&lt;T&gt; 表示它从分配结果的变量类型推断泛型参数 T 的值。所以在这种情况下:

    List<String> stringList = Collections.emptyList();
    

    然后返回值被List&lt;String&gt; 类型的变量显式引用,因此编译器可以计算出来。在这种情况下:

    setList(Collections.emptyList());
    

    编译器没有明确的返回变量来确定泛型类型,所以它默认为Object

    【讨论】:

      【解决方案4】:

      从 Java 8 开始,这种代码按预期编译,类型参数由编译器推断。

      public Person(String name) {
          this(name, Collections.emptyList()); // Inferred to List<String> in Java 8
      }
      
      public Person(String name, List<String> nicknames) {
          this.name = name;
          this.nicknames = nicknames;
      }
      

      Java 8 中的新功能是表达式的target type 将用于推断其子表达式的类型参数。在 Java 8 之前,仅将分配和参数直接分配给用于类型参数推断的方法。

      在这种情况下,构造函数的参数类型将是Collections.emptyList() 的目标类型,并且将选择返回值类型以匹配参数类型。

      这个机制是在 Java 8 中添加的,主要是为了能够编译 lambda 表达式,但它总体上改进了类型推断。

      Java 在每个版本中都越来越接近正确的Hindley–Milner 类型推断!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-07-11
        • 2016-06-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多