46

I have group of students. First I want to group them by the marks. Then I want to further group those sets into same name students together.

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

I am getting an error saying,

Non-static method cannot be refered from a static context.

Yes. I am pretty much aware that I cannot refer to a non-static method without having an instance. But with all these stream operations, I'm a bit confused about what has gone wrong really.

Rather than how to fix this; I really want to know what's going on here. Any of your inputs is appreciated!

Because If I write the below grouping is completely valid;

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

Here is my Student.java class (In case if you need it)

public class Student {
    private String name;
    private int marks;
    // getters, setters, constructor and toString
}
Naman
  • 27,789
  • 26
  • 218
  • 353
Jude Niroshan
  • 4,280
  • 8
  • 40
  • 62

2 Answers2

85

Unfortunately, the error message “Non-static method cannot be refered from a static context.” is just a place-holder for any type mismatch problem, when method references are involved. The compiler simply failed to determine the actual problem.

In your code, the target type Map<Integer, Map<String, List<String>>> doesn’t match the result type of the combined collector which is Map<Integer, List<String>>, but the compiler didn’t try to determine this (stand-alone) result type, as the (nested) generic method invocations incorporating method references requires the target type for resolving the method references. So it doesn’t report a type mismatch of the assignment, but a problem with resolving the method references.

The correct code simply is

Map<Integer, List<String>> groupping = students.stream()
    .collect(Collectors.groupingBy(Student::getMarks, 
             Collectors.mapping(Student::getName, Collectors.toList())));
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I didn't get what you meant by *result type of the combined collector*. What is that? – Jude Niroshan Oct 21 '16 at 10:30
  • 2
    The combined collector is `groupingBy(Student::getMarks, mapping(Student::getName, toList()))`, which would have a result type, if we treat it as stand-alone expression (like all expressions were before Java 8). If the rules were like that, the compiler reported a simple `Map>` cannot be assigned to `Map>>` error message. – Holger Oct 21 '16 at 10:35
  • 2
    Unfortunately, the Java 8 rules aren’t that simple anymore. For so-called “poly expressions”, the target type determines the result type, so you could declare `groupping` as `Map>` without any error. This would retrofit the function types of the method references, e.g. `Student::getMarks` would be `Function` and `Student::getName` would be `Function` then. The strange error message stems from trying to find appropriate methods for your wrong target type. – Holger Oct 21 '16 at 10:35
  • In my case; inner `Collectors.mapping(Student::getName, Collectors.toList()))` will actually return a `Map`, isn't it? But as your solution, you assume that it is a `List` which confuses me. – Jude Niroshan Oct 21 '16 at 10:48
  • 1
    `Collectors.mapping` does *not* return a `Map`. It **map**s the elements before passing them to the other collector, so it maps `Students` to name `String`s before passing them to `Collectors.toList()`, which then returns a `List` (rather than `List` as it would without the mapping). Maybe [the documentation](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#mapping-java.util.function.Function-java.util.stream.Collector-) helps. – Holger Oct 21 '16 at 11:12
  • Never forget this: "Unfortunately, the error message “Non-static method cannot be refered from a static context.”" – Federico Pugnali Nov 21 '17 at 19:43
  • Reading this again after long, I am thinking do you happen to see a way such that the error message conveyed could have been improved by the authors? At least to an extent that the actual non-static method in use could be separated from type mismatches? In terms of code, you can refer to https://github.com/openjdk/jdk to highlight areas of improvement maybe. – Naman Oct 20 '20 at 03:15
  • 1
    @Naman I supposed, it became better, but there’s still a long way to go. The problem is that there’s a formal specification about the behavior of correct programs and a parser theory about how to (efficiently) parse correct programs, but dealing with erroneous input seems to a be secondary topic at best. It’s always surprising how simple errors like mismatching brackets or misplaced separators make compilers go crazy, despite such issues should be simple to spot. But it’s just not the way the compiler work… – Holger Oct 20 '20 at 17:28
4

I think Holger has given a good explanation about the error and why it doesn't make much sense in one run.

Considering your goal, I think this is the solution you need to have.

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

This would simply give you a student list first grouped by marks, then by name. :))

Supun Wijerathne
  • 11,964
  • 10
  • 61
  • 87
  • How would you do if we had this: `Map>>` instead of `Map>>`? `FavoriteBookEnum` being an enum inside `Student` and `School` just a class to with some school info. I'm trying to get a map grouped by favorite books but... not getting anywhere. – dNurb Sep 10 '20 at 19:26
  • For future reference or anyone having a scenario like this -> https://stackoverflow.com/questions/63837641/group-a-map-by-object-property-to-a-new-map just added a question about it. – dNurb Sep 10 '20 at 21:03