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
,则:
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)