Java8 in 8 methods

Trying to summarize all the novelties of Java 8 is pretty hard, obviously you have the introduction of lambdas, but there are dozens of little gems just waiting you, in this post, i will try to summarize Java 8 in 8 new useful methods.

This post was updated Feb 6th to fix issues in some Java 7 examples thanks to JB Nizet.

List.sort()

java.util.List has now a method sort, yes, I know, it's just crazy in retrospect to think that until 2014, a newbie in Java has to post a question on StackOverflow to be able to sort a list.

List<String> list = ...
list.sort(null);                                    // use natural order
list.sort((s1, s2) -> s1.compareToIgnoreCase(s2));  // sort ignoring case
list.sort(String::compareToIgnoreCase);             // same with a method reference

If sort is called with null, the element of the list must be comparable with themselves, otherwise, you can specify a comparator, either as a lambda or as a method reference.

List.removeIf()

Sometimes you want to iterate on a list to remove let say all the string that have a length divisible by 2 (yes, I know the example is stupid, but I'm sure you see what I mean).
Because you can not mutate the list while iterating on it, you have to use an Iterator and write those lines

Iterator<String> it = list.iterator();
while(it.hasNext()) {
    String s = it.next();
    if (s.length() %2 == 0) {
        it.remove();
    }
}

It's a common code but having to conjure an Iterator for that is still a little scary (an iterator is not something a user of an API should see, it is just there to do the plumbing in the basement).
Hopefully, we now have the method removeIf.

list.removeIf(s -> s.length() %2 == 0);

so now, there is no excuse to use an Iterator explicitly anymore.

Map.getOrDefault()

Let suppose we want to record the number of occurrence of a list of word in a Map, the code below is the code that was given when Java 5 was released as an example (I think it was written by Josh Bloch) because it combines generics and boxing.

Map<String, Long> map = new HashMap<String, Long>();
for(String s: array) {
    Long count = map.get(s);
    map.put(s, (count == null)? 1L: count + 1);
}

here, the fact that Map.get returns null and not the default value of the type (here Long) makes the code quite ugly. Java 8 introduces Map.getOrDefault to allow to specify the default value if there is no corresponding value in the Map.

Map<String, Long> map = new HashMap<>();
for(String s: args) {
    map.put(s, 1 + map.getOrDefault(s, 0L));
}

Map.computeIfAbsent()

Let suppose I want to group a list of Person by their name into a Map. Again, I have to separate the case where there is no value associated to a string or not.

List<Person> people = ...
Map<String, List<Person>> byNameMap = new HashMap<>();
for(Person person: people) {
    String name = person.getName();
    List<Person> persons = byNameMap.get(name);
    if (persons == null) {
      persons = new ArrayList<>();
      byNameMap.put(name, persons);
    }
    persons.add(person);
}

Maybe, we can use getOrDefault ?

Map<String, List<Person>> byNameMap = new HashMap<>();
for(Person person: people) {
    String name = person.getName();
    List<Person> persons = byNameMap.getOrDefault(person.getName(), new ArrayList<>());
    byNameMap.put(name, persons);
    persons.add(person);
}

It works but it's inefficient because we create a new ArrayList at each call of getOrDefault even when a list already exist in the map. What we need is a way to delay the creation of the ArrayList until we really need it.
A lambda is a delayed calculation !

Map<String, List<Person>> byNameMap = new HashMap<>();
for(Person person: people) {
    byNameMap.computeIfAbsent(person.getName(), name -> new ArrayList<>()).add(person);
}

Note that this lambda doesn't use value of variable from outside of the lambda, so the JDK implementation will make it a constant (so please, do not store a lambda in a static final field, you do not need it !).

computeIfAbsent can also be used to implement a cache easily

Map<Integer, FairlyBigObject> map = ...
map.computeIfAbsent(id, id -> DB.findById(FairlyBigObject.class, id)); 

Map.forEach()

Iterating over a Map is not simple as it should with Java < 8, even in PHP, you can do a foreach with two variables, the key and the value.
So instead of

Map<String, List<Person>> byNameMap = ...
for(Map.Entry<String, List<Person>> entry: byNameMap.entrySet()) {
  System.out.println(entry.getKey() + ' ' + entry.getValue());
}

you can use Map.forEach

byNameMap.forEach((name, persons) -> {
  System.out.println(name + ' ' + persons);
});

which is in fact more efficient for maps like IdentityHashMap that doesn't store the key and the value in the same object (because IdentityHashMap.entrySet() has to re-create the Map.Entry on the fly :( ).

Collection.stream(), Arrays.stream()

In fact, you don't need to use computeIfAbsent if you want to do a groupBy, you can transform the collection or the array to a Stream of objects, do transformation on each one and then collect all the results in a List or a Map using a set predefined Collectors.

Map<String, List<Person>> byNameMap =
people.stream().collect(Collectors.groupingBy(Person::getName));

The Stream API is really powerful, if you want more, you can see the presentation
of José Paumard (and Brian Goetz as a sidekick) at Devoxx Part 1 Part 2

Files.lines()

And what about taking a file, split it in lines and then words and compute the number of occurrence of each words. Files.lines let you split a files into lines and closes cleanly the file descriptor when not needed anymore.

Path file = Paths.get("file.txt");
Map<String, Long> histoMap =
    Files.lines(file)
         .flatMap(line -> Arrays.stream(line.split(" ")))
         .collect(Collectors.groupingBy(Function.identity(),
             Collectors.counting()));

You can notice that for this snippet, I have not shown the code we have to write with Java < 8, too many lines :)

Temporal.query()

I would like to finish with a snippet that use the new java.time API, because even if the lambdas tend to catch all the light, this API is important at several levels.

  • I haven't seen a documentation so well written,
    it significantly raises the bar for all future API.
    and in its design notes, there is a set of the verbs
    that every one should use in its own code.

  • It's mostly the work of one man (and contributors)
    not paid for that job, let say just for the glory of Java.

  • The API is conceptually well designed and a joy to use.

Let suppose I want to aggregate several source of data, e.g. a database, some REST service, etc, each source uses its own way to represent the concept of date and time and I want to know the day of week of these temporal objects to display them in a UI.

List<Temporal> temporals = ...
temporals.stream().map(temporal -> temporal.query(DayOfWeek::from)).forEach(System.out::println);

java.time not only provides immutable types to play with date and time but also a meta-description of each of these classes that allows to query the value of several different temporal object greatly simplifying the code of the UI.

This post is already too long, I hope that you have found these methods interesting and learn something new reading that post. You can find the whole code as a gist.

see you soon, cheers,
Remi