Why Java 8?
A preview of our new research guide: The DZone Guide to the Java Ecosystem, from MVB Trisha Gee.
Join the DZone community and get the full member experience.
Join For FreeThis article is featured in the DZone Guide to the Java Ecosystem. Get your free copy for more insightful articles, industry statistics, and more.
Quick View:
- In many cases, Java 8 will improve application performance without any specific work or tuning.
- Lambda expressions, the Streams API, and new methods on existing classes are some of the key productivity improvements.
- Java 8’s new
Optional
type gives developers significant flexibility when dealing with null values, reducing the likelihood of NullPointerExceptions
Java 8 came out early last year—and Java 7 is now end of life—making Java 8 the only Oracle-supported option until Java 9 comes out at the end of next year. However, since organizations value stability over trendiness, many of us are still working with Java 7, or even 6.
Let’s look at some features of Java 8, and provide some arguments to persuade your organization to upgrade.
It’s Faster
Here’s a selling point that might please your boss, the business, or the operations guys: you’ll probably find Java 8 runs your application faster. Generally speaking, applications that have moved to Java 8 see some sort of speed improvement without any specific work or tuning. This may not apply to an application that has been highly tuned to a specific JVM, but there are a number of reasons why Java 8 performs better:
Performance Improvements in Common Data Structures: Benchmarks of the ever-popular HashMap show that performance is better in Java 8. These sorts of improvements are very compelling—you don’t need to learn the new Streams API or lambda syntax or even change your existing code to get speed improvements in your application.
Garbage Collector Improvements: Often “Java Performance” is synonymous with “Garbage Collection,” and it is certainly true that poor garbage collection performance will impact an application’s performance. Java 8 has substantial changes to GC that improve performance and simplify tuning. The most well known of these changes is the removal of PermGen and the introduction of Metaspace.
Fork/Join Speed Improvements: The fork/join framework was new in Java 7, and was the latest effort to simplify concurrent programming using the JVM. A lot of work went into improving it further for Java 8. Fork/join is now the framework that’s used under the covers for parallel operations in the Streams API (more on this later).
In addition, there are plenty more changes in Java 8 to support concurrency, and Oracle has summarized some of the performance improvements in JDK 8.
Fewer Lines of Code
Java is regularly accused of being heavy on boilerplate code. Java 8 addresses some of these issues by embracing a more functional style for the new APIs, focusing on what you want to achieve and not how to do it.
Lambda Expressions
Lambda expressions in Java 8 are not just syntactic sugar over Java’s existing anonymous inner classes—the pre-Java 8 method of passing behavior around. Lambdas take advantage of Java 7’s under-the-hood changes, so they perform well. To see examples of where using lambda expressions can simplify your code, read on.
New Methods on Our Favorite Collections
While Lambdas and Streams (which we’ll cover next) are probably the top two selling points of Java 8, what’s less well known is that changes in Java 8 have allowed the language developers to add new methods to existing classes without compromising backwards compatibility. The result is that the new methods, combined with lambda expressions, allow us to drastically simplify our code. Take, for example, the common case of figuring out if an element already exists in a Map and creating a new one if not. Before Java 8, you might write something like:
private final Map<CustomerId, Customer> customers = new HashMap<>();
public void incrementCustomerOrders(CustomerId customerId) {
Customer customer = customers.get(customerId);
if (customer == null) {
customer = new Customer(customerId);
customers.put(customerId, customer);
}
customer.incrementOrders();
}
This operation of “check if the item is in the map; if not, create it and add it” is so common that there’s a new method on Map
to support it: computeIfAbsent
. This method takes as its second argument a lambda that states how to create the missing item:
public void incrementCustomerOrders(CustomerId customerId) {
Customer customer = customers.computeIfAbsent(customerId,
id -> new Customer(id));
customer.incrementOrders();
}
In fact, there’s another new feature in Java 8 called method references that makes this even shorter:
public void incrementCustomerOrders(CustomerId customerId) {
Customer customer = customers.computeIfAbsent(customerId, Customer::new);
customer.incrementOrders();
}
Map
and List
both have new methods in Java 8. It’s worth checking them out to see how many lines of code they can save you.
Streams API
The Streams API gives you flexibility to query and manipulate your data. This is a powerful tool. Check out some of the articles or books on the subject for a more complete view. Building fluent queries for your data is interesting in a Big Data world, but is just as useful for common operations. Let’s say, for example, that you have a list of books and you want to get a list of unique authors for these books, in alphabetical order:
public List<Author> getAllAuthorsAlphabetically(List<Book> books) {
List<Author> authors = new ArrayList<>();
for (Book book : books) {
Author author = book.getAuthor();
if (!authors.contains(author)) {
authors.add(author);
}
}
Collections.sort(authors, new Comparator<Author>() {
public int compare(Author o1, Author o2) {
return o1.getSurname().compareTo(o2.getSurname());
}
});
return authors;
}
In the code above, we first iterate through the list of books, adding the book’s author to the author list if it hasn’t seen it before; then we sort the authors alphabetically by surname. This is exactly the sort of operation that streams have been designed to solve elegantly:
public List<Author> getAllAuthorsAlphabetically(List<Book> books) {
return books.stream()
.map(book -> book.getAuthor())
.distinct()
.sorted((o1, o2) -> o1.getSurname().compareTo(o2.getSurname()))
.collect(Collectors.toList());
}
Not only is this fewer lines of code, it’s arguably more descriptive—a developer coming to this code later can read it and understand that 1) it’s getting authors from the books, 2) it’s only interested in unique authors, and 3) the list that is returned is sorted by author surname. Combine the Streams API with other new features—method references and new methods on Comparator—and you get an even more succinct version:
public List<Author> getAllAuthorsAlphabetically(List<Book> books) {
return books.stream()
.map(Book::getAuthor)
.distinct()
.sorted(Comparator.comparing(Author::getSurname))
.collect(Collectors.toList());
}
Here it’s even more obvious that the sorted method orders by the author’s surname.
Easy to Parallelize
We spoke about better out-of-the-box performance, and in addition to those earlier mentioned features, Java 8 can explicitly make use of more CPU cores. By simply replacing the method stream in the examples above with parallelStream
, the JVM will split the operation into separate jobs and use fork/join to run them on multiple cores. However, parallelization is not a magic incantation to make everything faster. Doing operations in parallel always requires more work—splitting up operations and recombining results—and will therefore not always take less time. But this option is very interesting for areas that are suitable for parallelization.
Minimize Null Pointers
Another new feature of Java 8 is the new Optional
type. This type is a way of explicitly stating “I might have a value, or I might be null.” Which means an API can now be explicit about either returning values that might be null vs. values that will always be non-null, minimizing the chances of running into a NullPointerException
.
What’s nice about Optional
is the way you tell it to deal with nulls. For example, if we’re looking for a particular book in a list, the new findFirst()
method returns an Optional
, which tells us it’s not guaranteed to find a value. Given this optional value, we can then decide what to do if it’s null. If we wanted to throw a custom Exception, we can use orElseThrow
:
public Book findBookByTitle(List<Book> books, String title) {
Optional<Book> foundBook = books.stream()
.filter(book -> book.getTitle().equals(title))
.findFirst();
return foundBook.orElseThrow(() -> new BookNotFoundException("Did not find book with title " + title));
}
Or you could return some other book:
return foundBook.orElseGet(() -> getRecommendedAlternativeBook(title));
Or we could return an Optional
so that callers of the method can make their own decision on what to do if the book is not found.
In Summary
Java 8 was a big release for Java, with syntax changes, new methods and types, and under-the-cover changes that will help your application even if you don’t use the new language features. Java 7 is no longer supported by Oracle, so organizations are being pushed to migrate to Java 8. The good news is that Java 8 has many benefits for your business, your existing application, and for developers looking to improve their productivity.
For more insights on microservices, JVM languages, and more trends in Java, get your free copy of the DZone Guide to the Java Ecosystem!
Opinions expressed by DZone contributors are their own.
Comments