Arj*_*jun 8 java generics java-stream
在过去的几天里,我一直试图了解java泛型.据我所知,Java泛型不是协变的,因此List<Object>不能与其他泛型List的赋值兼容
但是在下面的程序中,nameAndPhone.collect()方法返回一个类型的List,List<NamePhone>当我用程序替换引用变量List<NamePhone> npList时List<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)
什么得到由返回的类型参数的collect方法并不需要是同一类型的流.这里,结果类型R与流类型不同T.
Run Code Online (Sandbox Code Playgroud)<R,A> R collect?(Collector<? super T,A,R> collector)
接下来,Java 8及更高版本改进了目标类型推断.这意味着编译器将使用目标类型来推断类型参数.在这种情况下,当你有
List<NamePhone> npList = nameAndPhone.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
编译器看到NamePhone并推断该类型作为类型参数R到collect(和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在任何一种情况下都具有运行时类型.
您是对的,这些类型与赋值不兼容。
如有疑问,这可以很容易地得到验证:
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>. 但请注意,该方法本身具有额外的通用参数,即R和A。这里相关的一个是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)
那么你将有以下类型分配:
T是StreamNamePhoneT是CollectorNamePhoneR的值为collectList<NamePhone>但你也可以写
List<Object> npList = nameAndPhone.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
在这种情况下
T是StreamNamePhoneT是CollectorObjectR的值为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)
| 归档时间: |
|
| 查看次数: |
134 次 |
| 最近记录: |