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