Guava库中的Lists.newArraylist()如何工作?

7
我正在努力理解 Lists.newArrayList() 如何知道返回的列表类型。我看到了函数 newArrayList()源代码,但它仅仅返回了泛型类型 E 的 ArrayList
public static <E> ArrayList<E> newArrayList() {
  return new ArrayList<E>();
}

然而,当我调用该函数时,我没有传递任何这样的信息。
List<String> testList = Lists.newArrayList();

它如何知道我想要什么类型的ArrayList

我了解泛型和TypeToken,但无法通过代码进行关联。


2
请勿在Stack Overflow上发布实时代码示例。相反,从头开始重新编写一个自包含且简化的代码示例,以精确地重现您想要询问的问题。其他所有内容都太广泛了。此外,请不要在Stack Overflow上提出调试请求和个人辅导请求。您需要做的就是制定一个具体的编程问题,这就是您的入场卡。 - Engineer2021
1
这些Guava API已经被JDK 7所取代。https://plus.google.com/118010414872916542489/posts/TtFEKhU8qnG - wolfcastle
2
我不会说那些工厂方法已经过时了,它们更加具有描述性(例如Lists.newArrayListWithCapacity(5)而不是new ArrayList<>(5)),而且对于Lists.newArrayList("a", "b", "c")也没有更好的替代方法。 - Tavian Barnes
感谢@staticx 我尝试添加了一些代码以使问题更清晰。这个问题是针对一个常用的库的,如果不知道它处理什么,我就不能更具体了。所以我希望这不仅仅是个人辅导,而且足够具体。 Stack是唯一可以获得可靠答案的地方,适合这样的查询。 - tunetopj
Guava APIs比JDK7集合更好。它们不会因新语法而破坏向后兼容性(旧版本的Cobertura、Checkstyle和其他工具将在<>上出现故障),而FACTORY METHOD比语法糖更好。 - Visionary Software Solutions
4个回答

15

因为编译器可以从变量声明中推断出类型。

例如:

List<String> list = Lists.newArrayList(),

编译器将理解集合的类型(即 E 的类型)将为 <String>,因为您希望获得一个字符串列表。

使用Java7和钻石操作符,可以避免重写整个<>参数,这非常有用。

List<String> list = new ArrayList<>();

(假设它是一个List<List<String>>,你应该重写为List<List<String>>

我为您找到了以下内容:

Java编译器利用目标类型来推断泛型方法调用的类型参数。表达式的目标类型是Java编译器根据表达式出现的位置所期望的数据类型。考虑如下声明的`Collections.emptyList()`方法:

static <T> List<T> emptyList();

考虑以下赋值语句:
List<String> listOne = Collections.emptyList();

这个语句期望一个 List 实例;这个数据类型是目标类型。因为 emptyList 方法返回 List 类型的值,编译器推断类型参数 T 必须是 String 值。这在 Java SE 7 和 8 中都有效。或者,你可以使用类型证明并指定 T 的值如下:

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

然而,在这个上下文中并不需要这样做。虽然在其他情况下需要这样做。考虑以下方法:
void processStringList(List<String> stringList) {
    // process stringList
}

假设您想使用空列表调用processStringList方法。在Java SE 7中,以下语句无法编译:
processStringList(Collections.emptyList());

Java SE 7编译器会生成类似以下的错误信息:
List<Object> cannot be converted to List<String>

编译器需要一个类型参数T的值,所以它从Object值开始。因此,调用Collections.emptyList返回一个List类型的值,该值与processStringList方法不兼容。因此,在Java SE 7中,必须按以下方式指定类型参数的值:
processStringList(Collections.<String>emptyList());

在Java SE 8中,这已经不再是必需的。目标类型的概念已经扩展到包括方法参数,例如方法processStringList的参数。在这种情况下,processStringList需要一个类型为List的参数。方法Collections.emptyList返回一个List值,因此使用List作为目标类型,编译器推断出类型参数T具有String值。因此,在Java SE 8中,以下语句可以编译:
processStringList(Collections.emptyList());

有关lambda表达式中目标类型的更多信息,请参见目标类型化。

阅读此文档


1
这里需要注意的一点是,Collections.emptyList() 是一个不可变的空列表,因此不能用作替换 OP 所使用的方法。 - skiwi

4
它可以通过类型推断实现。简单来说,你可以进行这样的赋值:
```` ``` ``var variable = "string";`` ````
List<String> list = newArrayList();

newArrayList()可能是类似以下内容的东西:

static <E> List<E> newArrayList() {
    return new ArrayList<>();
}

在这里,编译器需要确定E类型变量的类型。为此,它将使用赋值的左侧,其中包含List<String>,因此E == String。现在它知道了类型变量,一切都很好。
与使用guava执行这样简单的任务相反,我建议你只需编写以下内容:
List<String> list = new ArrayList<>();

我认为这种方法更加简洁,不需要依赖外部文件。

值得一提的是,钻石操作符是在Java 7中引入的,而Guava的目标是Java 6+。在许多情况下,Guava的作者建议使用后来可用的JDK改进。 - Ray

2

简而言之,答案就是类型推断

以下是具体解释。你不仅仅调用了Lists.newArrayList();同时将其赋值给一个变量,例如:

List<Person> people = Lists.newArrayList();

既然您在变量声明中指定了类型参数,Java就能够“推断”(找出)您在新ArrayList中想要的类型参数,因此您无需指定它们。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接