Java 8 Tutorial
"Java is still alive and people are starting to understand it."
Welcome to my introduction to Java 8. This article will take you step-by-step through all the new features from Java 7 to Java 8. With quick and simple code examples, we can learn how to use Default Interfaces,
Method references
, and
Repeatable annotations . At the end of the article we will get acquainted with the Stream API.
No unnecessary chatter - just code and comments! Forward!
Default Methods for Interfaces
Java 8 allows us to add non-abstract methods (that are implemented) to interfaces by adding the
default
. This feature is also known as
Extention Methods . Below is the first example:
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
In addition to the abstract method
calculate
, the interface
Formula
also defines a default method
sqrt
. Classes that implement this interface only need to implement the
calculate
. The default method
sqrt
can be used out of the box.
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100);
formula.sqrt(16);
The interface
Formula
is implemented as an anonymous class. The code is redundant: 6 lines for implementation
sqrt(a * 100)
. As we'll see in the next section, there is a prettier way to implement a single method in Java 8.
Lambda expressions
Let's start with a simple example of sorting a list of strings in previous versions of Java:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
The static method
Collections.sort
accepts a list and a comparator in the order in which the list is to be sorted. You can always create an anonymous comparator class and pass it along. Instead of creating an anonymous class, in Java 8 you can create a shorter notation, a lambda expression.
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
As you can see the code is much shorter and easier to read. But this can be made even shorter:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
For a body with one line, you can skip
{}
the word
return
. But you can make it even shorter:
Collections.sort(names, (a, b) -> b.compareTo(a));
The Java compiler knows about argument types, so you can skip them as well. Let's dig deeper into lambda expressions and understand how they can be used.
Functional Interfaces
How do lambda expressions fit into the Java type system? Each lambda corresponds to a type described in the interface. Therefore, a functional interface should contain only one abstract method. Each lambda expression of this type will correspond to this abstract method. Since default methods are not abstract, you are free to create default methods in functional interfaces as needed. We can also use arbitrary interfaces as lambda expressions if there is only one abstract method in this interface. To meet these requirements you need to add
@FucntionalInterface
an annotation. The compiler knows about it and will throw an exception if you want to provide more than one abstract method. Example:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);
Keep in mind that the code will also compile if
@FunctionalInterface
the annotation is omitted.
Method and Constructor References
The example above can also be made even smaller by using method references:
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);
Java 8 allows you to pass references to a method or constructor by adding
::
. The example above shows how we can reference a static method, although we can also reference non-static methods:
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);
Let's see how
::
it works with constructors. To begin, we will define an example class with different constructors:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Next, we will define an interface for creating new objects:
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
Instead of implementing a creation factory, we'll tie it all together with
::
constructor help:
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
We created a link to the constructor via
Person::new
. The Java compiler will automatically select the correct constructor that matches the method signature
PersonFactory.create
. ... To be continued. Unfortunately, I didn’t find a way to save a draft of the article, and this is really strange, and the time for translation is over - so I’ll finish it later. For everyone who knows and understands English -
Original Article . If you have suggestions for correcting the translation, write in any way available to you.
My Github account