【问题标题】:Java Generics: Stream.map() returns "capture of ?" instead of "?"Java 泛型:Stream.map() 返回 \"capture of ?\" 而不是 \"?\"
【发布时间】:2022-08-09 10:45:10
【问题描述】:

我正在尝试构建一个List 的类,这些类实现了一个名为Interface 的特定接口:

List<Class<? extends Interface>> myList= myMap.entrySet().stream()
    .filter(entry -> entry.getValue().equals(myValue))
    .map(Map.Entry::getKey)   // Stream<Interface>
    .map(Interface::getClass) // Stream<Class<capture of ? extends Interface>>
    .distinct()
    .toList();

我在调用 map() 之后添加了 Stream 中元素的类型作为注释。

代码遍历映射中的所有条目,如果它们的值等于myValue,则:

  • 首先,获取实例Interface 类型(即钥匙入口)
  • 然后,获取实现InterfaceClass

myMap 定义为:

Map<Interface, Integer> myMap = new HashMap<>()

我得到的错误:

Incompatible types.
Found: \'java.util.List<java.lang.Class<capture<? extends application.interfaces.Interface>>>\',
required: \'java.util.List<java.lang.Class<? extends application.interfaces.Interface>>\'

我显然错过了一些关于如何泛型在Java中工作,但我在这里不知所措。我想这与编译器无法正确具体化我的? 通配符这一事实有关。

  • 可能,你的意思是entry.getValue().equals(myValue)不是entry.getValue() == myValue
  • 是的,感谢您的关注,我会更新帖子

标签: java generics java-stream


【解决方案1】:

作为@斯劳在 cmets 中指出,在这种情况下,getClass() 能够向编译器提供有关泛型类型的信息。

根据documentation

实际结果类型是Class&lt;? extends |X|&gt;,其中|X| 是调用getClass 的表达式的静态类型的擦除。

因此,在编译时,我们将有一个类型 ? extends Interface 并且观察到的行为的原因仅与类型推断在爪哇。

在这种情况下,当我们在map() 操作之后链接方法时,编译器无法根据流返回的结果类型正确推断方法引用Interface::getClass 的类型。

如果我们用collect(Collectors.toList())(其中收集器的类型为Collector&lt;? super T, A, R&gt;)替换toList,它需要T类型的元素并产生List&lt;T&gt;,编译器将能够完成它的工作(这里是@987654322 @):

List<Class<? extends Interface>> myList = myMap.entrySet().stream()
    .filter(entry -> Objects.equals(entry.getValue(), myValue))
    .map(Map.Entry::getKey)   // Stream<Interface>
    .map(Interface::getClass) // Stream<Class<? extends Interface>>
    .distinct()
    .collect(Collectors.toList());

但是要使类型推断与toList() 一起工作,我们需要提供泛型类型明确的.

比如这段代码可以编译,因为Interface::getClass的类型可以从赋值上下文中推断出来(这里map()后面没有操作,所以myStream直接说map()的返回类型应该是什么) :

Stream<Class<? extends Interface>> myStream = myMap.entrySet().stream()
    .filter(entry -> Objects.equals(entry.getValue(), myValue))
    .map(Map.Entry::getKey)
    .map(Interface::getClass);

List<Class<? extends Interface>> myList = myStream.distinct().toList();

更方便的方法是使用所谓的类型见证

Map<Interface, Integer> myMap = Map.of(new ClasA(), 1, new ClasB(), 1);
        
int myValue = 1;
        
List<Class<? extends Interface>> myList = myMap.entrySet().stream()
    .filter(entry -> Objects.equals(entry.getValue(), myValue))
    .map(Map.Entry::getKey)                               // Stream<Interface>
    .<Class<? extends Interface>>map(Interface::getClass) // Stream<Class<? extends Interface>>
    .distinct()
    .toList();
        
myList.forEach(c -> System.out.println(c.getSimpleName()));

输出:

ClasA
ClasB

虚拟类:

interface Interface {}
class ClasA implements Interface {}
class ClasB implements Interface {}

【讨论】:

  • 谢谢,我什至不知道 Type Witnesses 存在。我注意到您修改了 filter() 中的谓词。是不是有什么特别的原因?
  • 笔记: ”实际结果类型是 Class<?扩展 |X|> 其中 |X|是调用 getClass 的表达式的静态类型的擦除"——Javadoc
  • @Slaw 是的,在 Java 中,泛型被删除为 Object 类型。所以擦除类型引用中的X只不过是Object。并且? extends Object (? extends X) 实际上与未知类型? 相同。
  • @ralos 我在阅读您的初始版本(使用身份比较==)时对其进行了修改。您可以使用equals()Objects.equals()。后者有个小优势,对null没有敌意。
  • @AlexanderIvanchenko 此处不涉及类型擦除,因为我们正在谈论编译时,此时泛型仍然存在。人们会期望Interface::getClass 返回Class&lt;? extends Interface&gt;。从那里开始,认为您会从map(Interface::getClass) 电话中获得Stream&lt;Class&lt;? extends Interface&gt;&gt; 是有道理的。显然情况并非如此,但这是可以理解的期望。
猜你喜欢
  • 1970-01-01
  • 2022-10-07
  • 2021-06-28
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多