Java 8 Features
Java 8 was a major release and brought important new features for the Java language. Here are the highlights:
1. Lambdas
Java 8 added support for functional programming through lambas. Higher order functions are now possible in Java.
public interface ScolarshipPolicy {
boolean isEligible(Student student);
}
private boolean isElgibleForScolarship(Student student, ScolarshipPolicy scolarshipPolicy) {
return scolarshipPolicy.isEligible(student);
}
// before lambdas
boolean isEligible = isElgibleForScolarship(chuck, new ScolarshipPolicy() {
@Override
public boolean isEligible(Student student) {
return student.getScore() > 10.0;
}
});
//now
isElgibleForScolarship(chuck, (student) -> student.getScore() > 10.0);
isElgibleForScolarship(chuck, (student) -> student.getScore() > 10.0 && !student.hasScolarship());
Functional Interfaces
- Specifies exactly one abstract method
- Describes the signature of the lambda expression
- Examples already in java 7: Comparable, Runnable and Callable
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t); //Predicate function descriptor
}
Method References
- Reuse existing method definitions and pass them as lambdas
- Increase readability
//before
(Student a) -> a.getScore()
sort((s1, s2) -> s1.getScore().compareTo(s2.getScore()))
//now
Student::getScore
sort(comparing(Student::getScore))
2. Streams
Java 8 added a really nice feature called Streams. This changed the way we program in a much more intuitively and declarative way.
Example:
// before streams
List<Student> filteredStudents = new ArrayList<Student>();
for (Student s : classRoom) {
if (s.getScore() > 10.0) {
filteredStudents.add(s);
}
}
Collections.sort(filteredStudents, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getScore(), o2.getScore());
}
});
List<String> namesList = new ArrayList<>();
for (Student s : filteredStudents) {
namesList.add(s.getName());
}
// now
List<String> namesList =
classRoom.stream()
.filter(s -> s.getScore() > 10.0)
.sorted(comparing(Student::getScore))
.map(Student::getName)
.collect(toList());
The code is much more concise, comparison is readable now and we are not using throw-away variables.
3. Default Methods
- Java 8 enables interface default methods
- Interfaces now have behaviour
- You can implement static methods in Interfaces
- You can add default methods to interfaces and implementing classes may choose to override.
default void sort(Comparator<? super E> c){
Collections.sort(this, c);
}
4. Optional
Problems with null
- It’s a source of error. NullPointerException is by far the most common exception in Java.
- It bloats your code. It worsens readability by making it necessary to fill your code with often deeply nested null checks.
- It’s meaningless. It doesn’t have any semantic meaning
- etc
How can we avoid returning null?
// before
public String getCarModel(String licencePlate) {
Car car = carDao.findCar(licencePlate);
if (car != null) {
return car.getModel();
}
return null;
}
// now
public Optional<String> getCarModelImproved(String licencePlate) {
Optional<Car> car = carDao.searchFor(licencePlate);
return car.map(Car::getModel);
}
5. Completable Futures
What are Futures?
Some computation to be done in the future, asynchronously, allowing the main thread of execution to continue.
ExecutorService executorService = Executors.newCachedThreadPool();
Future<Double> futureValue = executorService.submit(new Callable<Double>() {
@Override
public Double call() throws Exception {
Thread.sleep(1000);
return 10.0;
}
});
System.out.println("waiting");
// do something else
try {
Double result = futureValue.get(); // blocking operation
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Problems with Futures:
- Difficult to express dependencies between results of a Future
- Hard to work with. Use of timeouts and isDone methods are not helpful in building a working asynchronous/parallel pipeline.
- No declarative syntax to:
- Combine two asynchronous computations in one
- Waiting for the completion of all tasks performed by a set of Futures
- Waiting for the completion of the quickest task in a set of Futures
- Reacting to a future completion (being notified when the completion happens and do further processing with the result)
Asynchronous pipelines
List<CompletableFuture<String>> priceFutures = shops.stream()
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice("iphone6")))
.map(future -> future.thenApply(Quote::parse))
.map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() ->
Discount.applyDiscount(quote))))
.collect(toList());
List<String> prices = priceFutures.stream().map(CompletableFuture::join).collect(toList());
Combining Values
Combining Results from asynchronous computations:
Future<Integer> combine =
CompletableFuture.supplyAsync(() -> value(2).withDelay(1000))
.thenCombine(CompletableFuture.supplyAsync(() -> value(3).withDelay(1000)),
(a, b) -> a + b);
The solution before takes 1 second to complete instead of the 2 seconds if using serial processing.
Reacting
CompletableFuture[] futures = findPricesStream("iphone6")
.map(p -> p.thenAccept(System.out::println))
.toArray(size -> new CompletableFuture[size]);
CompletableFuture.allOf(futures).join();
6. Date and Time API
Before Java 8 there was no standard for Date and Time API. That led to error prone code:
- Date represents a point in time (Timestamp -> long)
- The years start from 1900 and the months start at index 0
- 19-04-2016 = new Date(116,3,19)
- Timezone problems
- Lisbon is WET not BST
- Date and Calendar confusion
- DateFormat only works with Date
- DateFormat not Thread-safe
- Mutability in Date and Calendar leads to maintenance overhead and error-prone code
For those reasons a new Date and Time API was created -> java.time package
Important Classes:
- LocalDate
- LocalTime
- LocalDateTime
- Instant
- Duration
- Period
LocalDate date = LocalDate.of(2016, 4, 20);
int year = date.getYear();
Month month = date.getMonth();
int day = date.getDayOfMonth();
DayOfWeek dayOfWeek = date.getDayOfWeek();
// [2016, APRIL, 20, WEDNESDAY, 30, true]
LocalDate today = LocalDate.now();
//2016-04-20
LocalTime time = LocalTime.of(14, 30, 01);
time.getHour();
time.getMinute();
time.getSecond();
// [14, 30, 01]
Parsing
LocalDate localDate = LocalDate.parse("2016-04-20");
LocalTime localTime = LocalTime.parse("16:30:01");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
LocalDate parsedDate = LocalDate.parse("20-04-2016", formatter);
Assert.assertEquals(localDate, parsedDate);
System.out.println(parsedDate.format(formatter));
//20-04-2016
Immutability
All classes in the new date time API are immutable:
LocalDate date = LocalDate.of(2016, 4, 20);
date = date.plusYears(2).minusDays(10);
date.withYear(2011);
LocalDate date2 = date.plusWeeks(3);
LocalDate date3 = date.withYear(2011);
// date = 2018-04-10, date2 = 2018-05-01, date3 = 2011-04-10