Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to solicit more return type information from your Java IDE while processing Java Streams?

Consider the following task:

Given a comma-separated list of cities (state, city, population) generate a list of states and the most populous city in each state along with the population. The list should be sorted on the name of state.

The cities.csv looks like this:

New York, New York, 8491079
Los Angeles, California, 3928864
Chicago, Illinois, 2722389
Houston, Texas, 2239558
Philadelphia, Pennsylvania, 1560297
Phoenix, Arizona, 1537058
San Antonio, Texas, 1436697
San Diego, California, 1381069
Dallas, Texas, 1281047
San Jose, California, 1015785
...

The expected output looks like this:

Alabama, Birmingham [212247]
Alaska, Anchorage [301010]
Arizona, Phoenix [1537058]
Arkansas, Little Rock [197706]
California, Los Angeles [3928864]
...
Texas, Houston [2239558]

My attempt (after trial and error and following the cookbook style processing of Java 8 streams) looks like this:

private static void mostPopulousCityInState() throws IOException {
            Files.lines(Paths.get("cities.csv")).map(City::new)
                    .collect(groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation)))) //1
            .entrySet() //2
            .stream()   //3
            .sorted(Comparator.comparing(e -> e.getKey()))
            .forEach(e -> {
                City city = e.getValue().get();
                System.out.println(e.getKey() + ", " + city.getName() + " [" + city.getPopulation() + "]");
            });
}

(The class City is omitted for brevity, but it has fairly straightforward looking constructor(s) and getters).

This seems alright (although it could be improved). But my questions are more about how to think about Java 8 streams in general:

  1. For instance, can I place my cursor on collect and do some keyboard shortcut that tells me that it returns a Map<String, Optional<City>>? If I did "Navigate to Source" here, then it takes me to generic definition in Stream.java which shows <R, A> R collect(Collector<? super T, A, R> collector); and that is useless in this context. Since the compiler knows about the concrete types at this point in time, is it reasonable to expect that IDE shows them (rather than generic types) to me ?
  2. In general, how do you keep track of the return types in pipelines? My current problem is I get lost among the generic return types. Specifically, what would have made it clear to you that the stream at line //3 above is a Stream of Map.Entry<String, Optional<City>>s?
  3. Does it get better with time or do you need a deeper understanding of Java's type system?
like image 444
Kedar Mhaswade Avatar asked Mar 03 '26 20:03

Kedar Mhaswade


1 Answers

You can do this in a simpler way: you don't need to explicitely sort the map after the group by operation: you can directly use a custom SortedMap with groupingBy(classifier, mapFactory, downstream). In this case, the map factory can be TreeMap::new, which will create a SortedMap in ascending order of the name of the state.

Note also that Files.lines returns a Stream that should be closed in a try-with-resources:

The returned stream encapsulates a Reader. If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream's close method is invoked after the stream operations are completed.

private static void mostPopulousCityInState() throws IOException {
    try (Stream<String> stream = Files.lines(Paths.get("cities.csv"))) {
        stream.map(City::new)
              .collect(Collectors.groupingBy(
                  City::getState,
                  TreeMap::new,
                  Collectors.maxBy(Comparator.comparing(City::getPopulation))
              ))
              .forEach((k, v) -> {
                  City city = v.get();
                  System.out.println(k + ", " + city.getName() + " [" + city.getPopulation() + "]");
              });
    }
}

With this change, you do not need to have a second Stream pipeline that sorts the entries of the map, which makes your other questions a bit moot.

The IDE issue could be specific IntelliJ (no idea there). I tested with the latest Eclipse IDE (Mars.2), and if I hover over the call to collect, it correctly tells me that the return type is TreeMap<String, Optional<City>>. You're right that the IDE should be able to show the correct type information.

entrySet().stream(...) is clear enough that it returns a Stream of Map.Entry element. But to make it easier to understand, you can store the map in a local variable:

Map<String, Optional<City>> map = // result of operation
map.entrySet().stream() // ... etc
like image 182
Tunaki Avatar answered Mar 05 '26 11:03

Tunaki



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!