静态上下文无法访问Collectors中的非静态内容。

46

我有一组学生。首先,我想按照分数将他们分组。然后,我希望将这些集合进一步分组,将相同姓名的学生放在一起。

Map<Integer,Map<String,List<String>>> groupping = students.stream()
                                                    .collect(Collectors.groupingBy(Student::getMarks, 
                                                            Collectors.mapping(Student::getName,Collectors.toList())));

我遇到了一个错误,错误信息为:

非静态方法无法从静态上下文中引用。

是的,我知道没有实例时不能引用非静态方法。但是在所有这些流操作中,我有点困惑到底出了什么问题。

与其说我想知道如何修复这个问题;我更想知道这里到底发生了什么。欢迎提供您的任何见解!

因为如果我写下面的分组代码,它是完全有效的;

Map<Integer,List<Student>> m = students.stream().
        collect(Collectors.groupingBy(Student::getMarks));

这里是我的Student.java类(如果您需要)

public class Student {
    private String name;
    private int marks;
    // getters, setters, constructor and toString
}

你试图在Map<String,List<String>>中存储什么?我指的是你将要存储在List<String>中的String对象是什么?是学生姓名的列表吗? - Supun Wijerathne
@SupunWijerathne 实际上,我的意图是将“学生”存储在最内层的“列表”中。 - Jude Niroshan
所以它应该是一个List<Student>,对吧?:)) - Supun Wijerathne
2个回答

86

很遗憾,“非静态方法无法从静态上下文中引用”这个错误信息只是针对涉及到方法引用的任何类型不匹配问题的占位符。编译器只是未能确定实际的问题。

在你的代码中,目标类型Map<Integer, Map<String, List<String>>>与合并收集器的结果类型Map<Integer, List<String>>不匹配,但编译器没有尝试确定这个(独立的)结果类型,因为(嵌套的)泛型方法调用涉及方法引用需要目标类型来解析方法引用。所以它不会报告赋值的类型不匹配,而是报告解析方法引用的问题。

正确的代码简单地是

Map<Integer, List<String>> groupping = students.stream()
    .collect(Collectors.groupingBy(Student::getMarks, 
             Collectors.mapping(Student::getName, Collectors.toList())));

我不明白你所说的“合并收集器的结果类型”是什么意思。那是什么? - Jude Niroshan
2
组合收集器是 groupingBy(Student::getMarks, mapping(Student::getName, toList())),如果我们将其视为独立表达式(就像 Java 8 之前的所有表达式一样),它将具有结果类型。如果规则是这样的,编译器会报告一个简单的 Map<Integer, List<String>> 无法分配给 Map<Integer,Map<String,List<String>>> 的错误消息。 - Holger
2
不幸的是,Java 8规则不再那么简单了。对于所谓的“多态表达式”,目标类型决定了结果类型,因此您可以将groupping声明为Map<Object,List<CharSequence>>而没有任何错误。这将使方法引用的函数类型得到改进,例如Student::getMarks将成为Function<Student,Object>,而Student::getName将成为Function<Student,CharSequence>。奇怪的错误消息源于尝试为您的错误目标类型查找适当的方法。 - Holger
1
Collectors.mapping 不会返回一个 Map。它在将元素传递给其他收集器之前对它们进行映射,因此它将 Students 映射为名称 String,然后将它们传递给 Collectors.toList(),这样就返回了一个 List<String>(而不是没有映射时的 List<Student>)。也许文档可以帮助你。 - Holger
1
@Naman 我想,情况已经有所改善,但还有很长的路要走。问题在于对于正确程序行为的正式规范和关于如何(高效地)解析正确程序的解析器理论,但处理错误输入似乎最多只是一个次要的话题。令人惊讶的是,像不匹配的括号或放错位置的分隔符这样的简单错误会使编译器变得混乱,尽管这些问题应该很容易发现。但编译器就是不这样工作... - Holger
显示剩余3条评论

4

我认为Holger对错误的解释很好,也让我们知道了为什么在一次运行中它不太合理。

考虑到你的目标,我认为这是你需要的解决方案。

 Map<Integer, Map<String, List<Student>>> grouping = students.stream().collect(Collectors.groupingBy(Student::getMarks,
                Collectors.groupingBy(Student::getName)));

这将简单地为您提供一个按成绩分组,然后按姓名排序的学生列表。:))

如果我们有这个:Map<FavoriteBookEnum,Map<School,List<Student>>>而不是Map<Integer,Map<String,List<Student>>>,你会怎么做? FavoriteBookEnum是在Student内部的枚举,而School只是一个带有一些学校信息的类。我正在尝试按最喜欢的书籍分组地获取地图,但是...没有任何进展。 - dNurb
供日后参考或任何遇到类似情况的人 -> https://stackoverflow.com/questions/63837641/group-a-map-by-object-property-to-a-new-map 只是添加了一个相关问题。 - dNurb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接