List <Object>变量与Java中的List <String>等其他泛型列表兼容

Arj*_*jun 8 java generics java-stream

在过去的几天里,我一直试图了解java泛型.据我所知,Java泛型不是协变的,因此List<Object>不能与其他泛型List的赋值兼容

但是在下面的程序中,nameAndPhone.collect()方法返回一个类型的List,List<NamePhone>当我用程序替换引用变量List<NamePhone> npListList<Object> npList仍然编译而没有警告.

我尝试使用类似的方法返回List<String>,并使用List<Object>引用变量不会导致任何错误.

为什么List<Object>赋值与List<NamePhone>此兼容?

import java.util.*;
import java.util.stream.*;

class NamePhoneEmail
{
    String name;
    String phonenum;
    String email;

    NamePhoneEmail(String n, String p, String e)
    {
        name = n;
        phonenum = p;
        email = e;
    }
}

class NamePhone
{
    String name;
    String phonenum;

    NamePhone(String n, String p)
    {
        name = n;
        phonenum = p;
    }
}

public class CollectDemo
{
    public static void main(String[] args)
    {

        ArrayList<NamePhoneEmail> myList = new ArrayList<>();
        myList.add(
            new NamePhoneEmail("Larry", "555-5555", "Larry@HerbSchildt.com"));

        myList.add(
            new NamePhoneEmail("James", "555-4444", "James@HerbSchildt.com"));

        Stream<NamePhone> nameAndPhone =
            myList.stream().map((a) -> new NamePhone(a.name, a.phonenum));

        List<NamePhone> npList = nameAndPhone.collect(Collectors.toList());
    }
}
Run Code Online (Sandbox Code Playgroud)

rge*_*man 8

什么得到由返回的类型参数collect方法并不需要是同一类型的流.这里,结果类型R与流类型不同T.

<R,A> R collect?(Collector<? super T,A,R> collector)
Run Code Online (Sandbox Code Playgroud)

接下来,Java 8及更高版本改进了目标类型推断.这意味着编译器将使用目标类型来推断类型参数.在这种情况下,当你有

List<NamePhone> npList = nameAndPhone.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

编译器看到NamePhone并推断该类型作为类型参数Rcollect(和Collectors.toList()).

当你改变它

List<Object> npList = nameAndPhone.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

编译器查看Object并推断该类型为类型参数R.

这可以按预期编译和工作,因为你当然可以将任何类型的对象(包括a NamePhone)放入一个List<Object>.

这不是一个List<NamePhone>与之兼容的作业List<Object>.发生的事情是,当你说List<Object> npList,从来没有一个List<NamePhone>,只有一个List<Object>.

请注意,列表中的对象NamePhone在任何一种情况下都具有运行时类型.


Mar*_*o13 4

您是对的,这些类型与赋值兼容。

如有疑问,这可以很容易地得到验证:

List<Object> a = null;
List<NamePhone> b = null;
a = b;                      // Error!
Run Code Online (Sandbox Code Playgroud)

在这种情况下它似乎兼容赋值的原因是目标类型推断。推理过程可能很复杂 - 特别是在本例中,它涉及Collector具有三个类型参数的 a 。

我将尝试在这里充实相关部分:

该方法的签名collect如下:

<R, A> R collect(Collector<? super T, A, R> collector);
Run Code Online (Sandbox Code Playgroud)

这是在Stream<T>实例上调用的。就您而言,这是一个Stream<NamePhone>. 但请注意,该方法本身具有额外的通用参数,即RA。这里相关的一个是R返回类型

传入Collector的 是该方法创建的toList,如下所示:

public static <T> Collector<T, ?, List<T>> toList()
Run Code Online (Sandbox Code Playgroud)

它也是通用的。类型参数基本上将根据调用方法的上下文进行“替换”。

所以当你写这个时:

List<NamePhone> npList = nameAndPhone.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

那么你将有以下类型分配:

  1. TStreamNamePhone
  2. TCollectorNamePhone
  3. 该方法R的值为collectList<NamePhone>

但你也可以写

List<Object> npList = nameAndPhone.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

在这种情况下

  1. TStreamNamePhone
  2. TCollectorObject
  3. 该方法R的值为collectList<Object>

请注意,这仅是可能的,因为该collect方法接受Collector<? super T, ...>. 如果它期望一个Collector<T, ...>. 这基本上意味着您可以使用 中的元素Stream并将它们收集到新的中List,只要所需列表的类型参数是流中元素的超类型。


从概念上讲,这是有道理的,因为它在某种程度上类似于

List<Integer> integers = ...;
List<Number> numbers = ...;
for (Integer i : integers) numbers.add(i); // This should work as well!
Run Code Online (Sandbox Code Playgroud)