Java 泛型:Stream.map() 返回“捕获?” 代替 ”?”

ral*_*los 8 java generics java-stream

我正在尝试构建一个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();
Run Code Online (Sandbox Code Playgroud)

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

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

  • 首先,获取type 的实例Interface(这是条目
  • 然后,得到Class实施Interface

myMap定义为:

Map<Interface, Integer> myMap = new HashMap<>()
Run Code Online (Sandbox Code Playgroud)

我收到的错误:

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>>'
Run Code Online (Sandbox Code Playgroud)

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

Ale*_*nko 11

正如@Slaw在评论中指出的那样,在这种情况下getClass()能够向编译器提供有关泛型类型的信息。

根据文档

实际结果类型是调用 getClass 的表达式的静态类型的擦除位置Class<? extends |X|>|X|

因此,在编译时,我们会有一个类型,并且观察到的行为的原因仅与 Java 中类型推断? extends Interface的特性相关。

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

如果我们替换toList,它需要类型的元素T并生成List<T>,与collect(Collectors.toList()),其中收集器的类型为Collector<? super T, A, R>,编译器将能够完成其工作(这是一个证明):

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());
Run Code Online (Sandbox Code Playgroud)

但是为了使类型推断起作用,我们需要显式地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();
Run Code Online (Sandbox Code Playgroud)

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

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()));
Run Code Online (Sandbox Code Playgroud)

输出:

ClasA
ClasB
Run Code Online (Sandbox Code Playgroud)

虚拟类:

interface Interface {}
class ClasA implements Interface {}
class ClasB implements Interface {}
Run Code Online (Sandbox Code Playgroud)

  • 注意:“_实际结果类型是 Class&lt;? extends |X|&gt;,其中 |X| 是调用 getClass 的表达式的静态类型的擦除_” – [Javadoc](https://docs.oracle.com /en/java/javase/18/docs/api/java.base/java/lang/Object.html#getClass()) (3认同)
  • @AlexanderIvanchenko 这里不涉及类型擦除,因为我们正在谈论编译时,当泛型仍然存在时。人们会期望 `Interface::getClass` 返回一个 `Class&lt;? 扩展接口&gt;`。从那里开始,认为您会得到一个“Stream&lt;Class&lt;?”是有道理的。从“map(Interface::getClass)”调用扩展Interface&gt;&gt;`。显然情况并非如此,但这是一个可以理解的期望。 (2认同)