简体   繁体   English

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

[英]Java Generics: Stream.map() returns "capture of ?" instead of "?"

I am trying to build a List of Classes that implement a certain interface called Interface :我正在尝试构建一个实现某个名为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();

I added as comment the type of the elements in the Stream after map() is called.我在调用 map() 后添加了 Stream 中元素的类型作为注释。

the code iterates over all the entries in the map, and if their value is equal to myValue , then:代码遍历 map 中的所有条目,如果它们的值等于myValue ,则:

  • first, gets the instance of type Interface (which is the key of the entry)首先,获取接口类型的实例(这是条目的键)
  • then, gets the class Interface.然后,获取class接口。

myMap is defined as: myMap定义为:

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

The error I get is:我得到的错误是:

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>>'

I am clearly missing something about how Generics work in Java, but I am at a loss here.我显然遗漏了一些关于 Generics 如何在 Java 中工作的信息,但我在这里不知所措。 I suppose it's something related to the fact that the compiler cannot correctly reify my "?"我想这与编译器无法正确具体化我的“?”这一事实有关。 wildcard.通配符。

Any help, heads-up or hint would be very appreciated.任何帮助、提醒或提示将不胜感激。

Update:更新:

As @Slaw has pointed out in the comments, in this case getClass() is capable to provide the information about the generic type to the compiler.正如@Slaw在评论中指出的那样,在这种情况下getClass()能够向编译器提供有关泛型类型的信息。

According to the documentation :根据文档

The actual result type is Class<? extends |X|>实际结果类型是Class<? extends |X|> Class<? extends |X|> where |X| Class<? extends |X|>其中|X| is the erasure of the static type of the expression on which getClass is called.是调用 getClass 的表达式的 static 类型的擦除。

Hence, at compile type, we would have ? extends Interface因此,在编译类型时,我们会有? extends Interface ? extends Interface and the of the observed behavior is related solely to peculiarities of type inference in Java. ? extends Interface和观察到的行为仅与 Java 中类型推断的特性有关。

In this case, when we are chaining methods after map() operation, the compiler fails to infer the type of the method reference Interface::getClass correctly based on the resulting type returned by the stream.在这种情况下,当我们在map()操作之后链接方法时,编译器无法根据 stream 返回的结果类型正确推断方法引用Interface::getClass的类型。

If we substitute toList , which expects elements of type T and produces List<T> , with collect(Collectors.toList()) , in which collector is of type Collector<? super T, A, R>如果我们用collect(Collectors.toList())替换toList ,它需要T类型的元素并生成List<T> ,其中收集器的类型是Collector<? super T, A, R> Collector<? super T, A, R> , the compiler would be able to do its job (here's a proof ): 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<capture of ? extends Interface>>
    .distinct()
    .collect(Collectors.toList());

But to make type inference working with toList() we need to provide the generic type explicitly .但是为了使类型推断与toList()一起工作,我们需要显式地提供泛型类型。

For instance, this code would compile, because the type of Interface::getClass could be inferred from the assignment context (here there are no operations after map() , hence myStream directly says what should be the return type of map() ):例如,这段代码可以编译,因为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();

A more handy way would be to use a so-called Type Witness :更方便的方法是使用所谓的Type Witness

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()));

Output: Output:

ClasA
ClasB

Dummy classes:虚拟类:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM