Java 8 new features study notes
- Fundamentals
- Behavior parameterization
- Lambda expressions
- Streams
- Working with streams
- Collecting data with streams
- Parallel data processing and performance
- Refactoring, testing, and debugging
- Default methods
- Using Optional as a better alternative to null
- CompletableFuture: composable asynchronous programming
- New Date and Time API
Fundamentals
1. why should you care?
What made big change occur in java 8
- Simplifying - write programs more easily—instead of writing verbose code
- Parallelism - commodity CPUs have become multicore, fork/join framework in java 7 is too difficult.
Functions in Java
First-class values Vs second-class citizens - Only first-class values be passed around during program execution
- Passing code example - Find Hidden Files
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
Streams
- expose on collections
- Parallel computations on data
Default methods
Behavior parameterization
In this section, we use a example to explain what’s Passing code with behavior parameterization and how to make code evolvable.
Prior to Java 8
1. filtering apples by color
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
2. filtering apples by weight
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if(apple.getWeight() > weight){
result.add(apple);
}
}
return result;
}
3. filtering with every attribute you can think of
public static List<Apple> filterApples(List<Apple> inventory, String color, int weight, boolean flag){
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if((flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight)){
result.add(apple);
}
}
return result;
}
Behavior parameterization
First define an interface to model the selection criteria:
public interface ApplePredicate {
public boolean predicate(Apple apple);
}
Different strategies for selecting an Apple
static class AppleWeightPredicate implements ApplePredicate {
public boolean predicate(Apple apple) {
return apple.getWeight() > 150;
}
}
static class AppleColorPredicate implements ApplePredicate {
public boolean predicate(Apple apple) {
return "green".equals(apple.getColor());
}
}
4. filtering by abstract criteria
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if(p.predicate(apple)){
result.add(apple);
}
}
return result;
}
Anonymous classes
- don’t have a name
- let you declare and instantiate a class at the same time
- create ad hoc implementations
5. using an anonymous class
List<Apple> greenApples = filterApples(inventory, new ApplePredicate(){
public boolean predicate(Apple apple) {
return "green".equals(apple.getColor());
}});
6. using a lambda expression
List<Apple> greenApples = filterApples(inventory, (Apple apple) -> "green".equals(apple.getColor()));
7. abstracting over List type
public interface Predicate<T> {
boolean predicate(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for(T e : list){
if(p.predicate(e)){
result.add(e);
}
}
return result;
}
List<Apple> greenApples = filter(inventory, (Apple apple) -> "green".equals(apple.getColor()));
Real-world examples
Sorting with a Comparator
List<Apple> inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "green"), new Apple(120, "red"));
inventory.sort(new Comparator<Apple>(){
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}});
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
Executing a block of code with Runnable
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// do something
}));
GUI event handling
Button button = new Button("Send");
button.setOnAction((ActionEvent event) -> label.setText("Sent!!"));
Lambda expressions
What’s Lambda expressions
The Definition of Lambda:
- Anonymous - doesn’t have an explicit name
- Function - a lambda isn’t associated with a particular class like a method is, a lambda has a list of parameters, a body, a return type, and a possible list of exceptions that can be thrown
- Passed around - A lambda expression can be passed as argument to a method or stored in a variable.
- Concise - You don’t need to write a lot of boilerplate like you do for anonymous classes.
The basic syntax of Lambda
(parameters) -> expression
or
(parameters) -> { statements; }
Where and how to use lambdas
- use a lambda expression in the context of a functional interface.
Functional interface and Function descriptor
- A functional interface is an interface that specifies exactly one abstract method.
- @FunctionalInterface
Functional interfaces
- Predicate - defines an abstract method named test that accepts an object of generic type T and returns a boolean
- Consumer - defines an abstract method named accept that takes an object of generic type T and returns no result (void).
- Function - defines an abstract method named apply that takes an object of generic type T as input and returns an object of generic type R.
NOTE: boxing/unboxing/autoboxing - boxing means convert a primitive type into a corresponding reference type, the opposite approach called unboxing. Java also has an autoboxing mechanism that boxing and unboxing operations are done automatically. Boxed values use more memory and comes with a performance cost, Java 8 brings addtional functional interfaces(IntPredicate, DoublePredicate, ToIntFunction
, IntTo-DoubleFunction, etc) to avoid autoboxing.
Lambda Type
- Type checking - deduce the type of lambda from the context in which the lambda is used.
- Target typing
- Type inference
Method references
List<Apple> inventory = Arrays.asList(new Apple(80,"green"), new Apple(155, "green"), new Apple(120, "red"));
inventory.sort(comparing(Apple::getWeight));
System.out.println(inventory);
Constructor references:
BiFunction<Integer, String, Apple> function = Apple::new;
System.out.println(function.apply(100, "red"));
Streams
Stream is a sequence of elements from a source that supports data processing operations.
- Sequence of elements - a collection, a stream provides an interface to a sequenced set of values of a specific element type.
- Source - Streams consume from a data-providing source such as collections, arrays, or I/O resources.
- Data processing operations - filter, map, reduce, find, match, sort. Stream operations can be executed either sequentially or in parallel.
Streams API in Java 8 lets you write code:
- Declarative - More concise and readable
- omposable - Greater flexibility
- Parallelizable - Better performance
Comparison Example of java 7 and java 8
There is a menu contains a lot of dishes as below
public static final List<Dish> menu =
Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 400, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
Now we need the names of dishes that are low in calories and sorted by number of calories.
Java 7
List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish d : menu){
if(d.getCalories() < 400){
lowCaloricDishes.add(d);
}
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>(){
public int compare(Dish d1, Dish d2) {
return Integer.compare(d1.getCalories(), d2.getCalories());
}});
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish d : lowCaloricDishes){
lowCaloricDishesName.add(d.getName());
}
Java 8
List<String> lowCaloricDishesName = menu.stream().filter(d -> d.getCalories() < 400).sorted(comparing(Dish::getCalories)).map(Dish::getName).collect(toList());
Streams vs. collections
- Collections are about data; streams are about computations(filter, sorted, map).
- A stream can be traversed only once.
- Streams internal iteration Vs Collections external iteration
Stream operations
The Stream interface in java.util.stream.Stream defines many operations, it can be classified into two categories:
- intermediate operations - filter, map, and limit, etc, which can be connected together to form a pipeline.
- terminal operations - collect, count, forEach, etc, which causes the pipeline to be executed and closes it.
NOTE: The idea behind a stream pipeline is similar to the builder pattern(http://en.wikipedia.org/wiki/Builder_pattern).
Working with streams
Filtering and slicing
Filtering with a predicate
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
NOTE: The filter method takes as argument a predicate and returns a stream including all elements that match the predicate.
NOTE:
Dish::isVegetarian
is a method reference which equals(Dish d) -> d.isVegetarian()
ord -> d.isVegetarian()
Filtering unique elements in a stream
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
NOTE: The distinct method returns a stream consisting of the distinct elements.
Truncating a stream
List<Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).limit(3).collect(toList());
NOTE: The limit method used to truncate a stream.
Skipping elements
List<Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());
dishes.forEach(System.out::println);
NOTE: The skip method used to skip a stream.
Mapping
Applying a function to each element of a stream
menu.stream().map(Dish::getName).collect(toList()).forEach(System.out::println);
List<String> words = Arrays.asList("Java8", "Lambdas", "In", "Action");
words.stream().map(String::length).collect(toList()).forEach(System.out::println);
menu.stream().map(Dish::getName).map(String::length).collect(toList()).forEach(System.out::println);
NOTE:
getName
andlength
are applies method in above example.
Flattening streams
List<String> words = Arrays.asList("Hello", "World");
words.stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList()).forEach(System.out::println);
NOTE: The above code return a list of all the unique characters for a list of words.
Finding and matching
menu.stream().anyMatch(Dish::isVegetarian);
menu.stream().allMatch(d -> d.getCalories() < 1000);
menu.stream().noneMatch(d -> d.getCalories() >= 1000);
Finding an element
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
dish.ifPresent(d -> System.out.println(d.getName()));
Reducing
Summing the elements
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
int sum = numbers.stream().reduce(0, Integer::sum);
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
Optional<Integer> sum = numbers.stream().reduce(Integer::sum);
int product = numbers.stream().reduce(1, (a, b) -> a * b);
Maximum and minimum
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
Practice - traders executing transactions
In this section, we will try to solve 8 questons of traders executing transactions, before listing the questions, we first give the details of Trader
and Transaction
:
public class Trader {
private String name;
private String city;
public Trader(String name, String city) {
this.name = name;
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Trader: "+this.name + " in " + this.city;
}
}
public class Transaction {
private Trader trader;
private int year;
private int value;
public Transaction(Trader trader, int year, int value) {
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader() {
return trader;
}
public int getYear() {
return year;
}
public int getValue() {
return value;
}
public String toString(){
return "{" + this.trader + ", " + "year: "+this.year+", " + "value:" + this.value +"}";
}
}
There are 4 traders and a transacton list:
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario","Milan");
Trader alan = new Trader("Alan","Cambridge");
Trader brian = new Trader("Brian","Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
Questions
- Find all transactions in the year 2011 and sort them by value (small to high).
- What are all the unique cities where the traders work?
- Find all traders from Cambridge and sort them by name.
- Return a string of all traders’ names sorted alphabetically.
- Are any traders based in Milan?
- Print all transactions’ values from the traders living in Cambridge.
- What’s the highest value of all the transactions?
- Find the transaction with the smallest value.
Solutions
- Question 1
List<Transaction> tr2011 = transactions.stream().filter(t -> t.getYear() == 2011).sorted(comparing(Transaction::getValue)).collect(toList());
tr2011.forEach(System.out::println);
- Question 2
List<String> cities = transactions.stream().map(t -> t.getTrader().getCity()).distinct().collect(toList());
cities.forEach(System.out::println);
- Question 3
List<Trader> traders = transactions.stream().map(Transaction::getTrader).filter(t -> t.getCity().equals("Cambridge")).distinct().sorted(comparing(Trader::getName)).collect(toList());
traders.forEach(System.out::println);
- Question 4
String traderStr = transactions.stream().map(transaction -> transaction.getTrader().getName()).distinct().sorted().reduce("", (n1, n2) -> n1 + n2);
System.out.println(traderStr);
- Question 5
boolean milanBased = transactions.stream().anyMatch(t -> t.getTrader().getCity().equals("Milan"));
System.out.println(milanBased);
- Question 6
transactions.stream().map(Transaction::getTrader).filter(t -> t.getCity().equals("Milan")).forEach(t -> t.setCity("Cambridge"));
System.out.println(transactions);
- Question 7
int highestValue = transactions.stream().map(Transaction::getValue).reduce(0, Integer::max);
System.out.println(highestValue);
- Question 8
int smallestValue = transactions.stream().map(Transaction::getValue).reduce(0, Integer::min);
System.out.println(smallestValue);
Numeric streams
Primitive stream specializations
int calories = menu.stream().mapToInt(Dish::getCalories).sum();
Numeric ranges
IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0).forEach(System.out::println);
Building streams
Streams from values
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
Streams from arrays
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
System.out.println(sum);
Streams from files
long uniqueWords = Files.lines(Paths.get("data.txt"), Charset.defaultCharset()).flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();
System.out.println("There are " + uniqueWords + " unique words in data.txt");
Streams from functions
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
Stream.generate(Math::random).limit(5).forEach(System.out::println);
Collecting data with streams
We will start this section with a comparison example - Grouping transactions by currency.
Java 7 - imperative style
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
for (Transaction transaction : transactions){
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency, transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
}
System.out.println(transactionsByCurrencies);
Java 8 - functional style
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().collect(groupingBy(Transaction::getCurrency));
System.out.println(transactionsByCurrencies);
Reducing and summarizing
long howManyDishes = menu.stream().collect(counting());
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream().collect(maxBy(dishCaloriesComparator));
System.out.println(mostCalorieDish.get());
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
System.out.println(totalCalories);
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
System.out.println(avgCalories);
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
System.out.println(shortMenu);
Parallel data processing and performance
Parallel streams
Stream.iterate(1L, i -> i + 1).limit(n).reduce(Long::sum).get();
Stream.iterate(1L, i -> i + 1).limit(n).parallel().reduce(Long::sum).get();
LongStream.rangeClosed(1, n).reduce(Long::sum).getAsLong();
LongStream.rangeClosed(1, n).parallel().reduce(Long::sum).getAsLong();
Avoiding shared mutable state
public static class Accumulator {
public long total = 0;
public void add(long value) {
total += value;
}
}
Accumulator accumulator = new Accumulator();
LongStream.rangeClosed(1, n).parallel().forEach(accumulator::add);
long sum = accumulator.total
NOTE: above is a example of misuse of parallel stream, multiple threads are concurrently accessing the accumulator and in particular executing total += value, which, despite its appearance, isn’t an atomic operation.
The fork/join framework
More details refer to http://ksoong.org/fork-join.
Spliterator
Refactoring, testing, and debugging
Default methods
default methods and static methods
public interface Java8Interface {
static void A(){
System.out.println("A");
}
default void B() {
System.out.println("B");
}
}
Inheritance considered harmful
Inheritance shouldn’t be your answer to everything when it comes down to reusing code. For example, inheriting from a class that has 100 methods and fields just to reuse one method is a bad idea, because it adds unnecessary complexity.
Using Optional as a better alternative to null
CompletableFuture: composable asynchronous programming
New Date and Time API
LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration
LocalDate date = LocalDate.of(2016, 9, 18);
int year = date.getYear();
Month month = date.getMonth();
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek();
int len = date.lengthOfMonth();
boolean leap = date.isLeapYear();
LocalDate today = LocalDate.now();
int y = date.get(ChronoField.YEAR);
int m = date.get(ChronoField.MONTH_OF_YEAR);
int d = date.get(ChronoField.DAY_OF_MONTH);
LocalTime time = LocalTime.of(13, 25, 40);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
LocalDate date = LocalDate.parse("2016-09-18");
LocalTime time = LocalTime.parse("12:17:40");
LocalDateTime dt1 = LocalDateTime.of(2016, Month.SEPTEMBER, 18, 13, 25, 40);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 25, 40);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
Instant instant1 = Instant.ofEpochSecond(3);
Instant instant2 = Instant.ofEpochSecond(3, 0);
Instant instant3 = Instant.ofEpochSecond(2, 1000000000);
Instant instant4 = Instant.ofEpochSecond(4, -1000000000);
Duration threeMinutes1 = Duration.ofMinutes(3);
Duration threeMinutes2 = Duration.of(3, ChronoUnit.MINUTES);
Duration threeDays1 = Duration.ofDays(3);
Duration threeDays2 = Duration.of(3, ChronoUnit.DAYS);
Period tenDays = Period.ofDays(10);
Period fiveDays = Period.between(LocalDate.of(2016, 9, 13), LocalDate.of(2016, 9, 18));
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
Manipulating, parsing, and formatting dates
LocalDate date1 = LocalDate.of(2016, 9, 18);
LocalDate date2 = date1.withYear(2015);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 10);
LocalDate date5 = date4.plusWeeks(1);
LocalDate date6 = date5.minusYears(3);
LocalDate date7 = date6.plus(36, ChronoUnit.MONTHS);
LocalDate date = LocalDate.of(2016, 9, 18);
LocalDate date8 = date.with(nextOrSame(DayOfWeek.SUNDAY));
LocalDate date9 = date.with(lastDayOfMonth());
LocalDate date10 = date.with(new NextWorkingDay());
LocalDate date11 = date.with(nextOrSame(DayOfWeek.MONDAY));
LocalDate date12 = date.with(temporal -> {
DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;
if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate date1 = LocalDate.parse("20160918", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2016-09-18", DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formattedDate = date.format(formatter);
LocalDate date3 = LocalDate.parse(formattedDate, formatter);
DateTimeFormatter cnaFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.CHINA);
String formattedDate1 = date.format(cnaFormatter);
LocalDate date4 = LocalDate.parse(formattedDate1, cnaFormatter);
DateTimeFormatter cnaFormatter1 = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.CHINA);
String formattedDate2 = date.format(cnaFormatter1);
LocalDate date5 = LocalDate.parse(formattedDate2, cnaFormatter1);
time zones and calendars
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
ZoneId zoneId = TimeZone.getDefault().toZoneId();
LocalDate date = LocalDate.of(2016, 9, 18);
LocalDateTime dateTime = LocalDateTime.of(2016, 9, 18, 13, 45);
Instant instant = Instant.now();
ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone);
ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone);
ZonedDateTime zdt3 = instant.atZone(shanghaiZone);
ZoneOffset beijingOffset = ZoneOffset.of("+08:00");
OffsetDateTime dateTimeInBeiJing = OffsetDateTime.of(dateTime, beijingOffset);
Chronology cnaChronology = Chronology.ofLocale(Locale.CHINA);
ChronoLocalDate now = cnaChronology.dateNow();