TABLE OF CONTENTS (HIDE)

JDK 8

New Features

JDK 8 is a major upgrade! It introduces new syntaxes (Lambda Expression) to support functional programming; retrofitted existing libraries (especially the Collection Framework); and added new libraries and features.

JDK 8 comes wiith three big features:

  1. Lambda Expressions
  2. Stream API
  3. Date/Time API (java.time)

Functional Programming

Object-Oriented Programming vs Functional Programming

References
  1. Object Oriented Programming vs. Functional Programming @ https://www.codenewbie.org/blogs/object-oriented-programming-vs-functional-programming.

Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects", which combine data (in the form of fields or attributes) and code (in the form of procedures or methods)."

"Functional programming (FP) is a programming paradigm, a style of building the structure and elements of computer programs, that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data."

In a program, there are two primary components: the data and the behavior (or code). OOP says that brings together data and its associated behevior into an "object" makes it easier to understand how a program works. FP says that data and behavior are distinctively different and should be kept separate for clarity.

Example

Let's illustrate the differences of the two approaches with an example: Suppose that you are running a company and wish to give all your employee a raise.

Example in OOP

In OOP, we will have a Employee class, which encapsulates the data (name, salary) and behavior (raiseSalary()), for example,

public class Employee {
   // Data
   private String name;
   private int salary;

   // Constructor
   public Empolyee(String name, int salary) {
      this.name = name;
      this.salary = salary;
   }

   // Behavior
   public void raiseSalary(int amount) {
      salary += amount;
   }
   public String toString() {
      return "Employee[name=" + name + ",salary=" + salary + "]";
   }
}

We need to construct the a List of employees:

List<Empolyee> empolyees = = List.of(new Employee("Peter", 1000), new Employee("Paul", 2000));

and invoke the raiseSalary() method inside a for-each loop:

for (Employee employee : employees) {
   employee.raiseSalary(100);
   System.out.println(employee);  // debugging
}

In OOP:

  1. We first define class(es).
  2. We then create instances of classes.
  3. We invoke methods on the instances.

Data is supplied to an object during construction (via the new operator). We invoke methods on objects to interact with the data stored.

Example in FP

In FP, the data and behavior are separated. Data are often kept in a simple structure, such as array. Data are inmutable. Behavior is implementation in tiny, standalone and specialized functions. For example,

// Data
Map<String, Integer> workers = Map.of("Peter", 1000, "Paul", 2000);

// Behavior
[TODO] 
raiseSalary(): one
raiseSalaries(): delegate to raiseSalary()

In FP, data are kept in simple arrays or hashes, instead of high-level objects. Data are not mix with behavior. Data are immutable and shared state is avoided. FP relies heavily on tiny and specialized methods that do one small part of a larger job, and delegate the details to other tiny methods.

FP views computation as data transformation: you start with the origianl dataset, apply transformations to generate a new dataset. The contents (or state) of the original variable does not change (i.e., immutable). A new state after the transformation is created.

FP focuses computation on pure functions. A pure function is a function where:

  1. The return value depends only on the input, i.e., the same input always produces the same return value.
  2. There is no side effect.
  3. It does not alter the data that was passed into it.

In FP:

  • There is a complete separation of data and behavior (functions).
  • The data (objects) are immutable.
  • Shared state is avoid (no inheritance).

There are typically fewer lines of codes, as we do not need to define a complex class.

Comparison
  • FP is good when you have a fixed set of data; and as your application evolves, you add new functions to operate on existing data. The existing functions are left alone. For example, [TODO]
  • FP is good in concurrent environments. The immutable state enables easy concurrency.
  • OOP is good when you have a fixed set classes (data and behavior); and as your application evolves, you add new classes (with composition or inheritance). The existing classes are left alone. For example, [TODO]

However, when the evolution goes the wrong way, you have problems:

  • Adding new data to a FP program may require modifying many existing functions. For example, [TODO]
  • Adding a new method to an OO program may require modifying many existing classes. For example, [TODO]

However, you don't necessarily have to choose between OOP and FP, you can write applications with an OO architecture using many functional concepts. Developers can create tiny, standalone and specialized functions in an OO environment.

JDK 8's Support for FP

In order to support functional programming, JDK 8 re-designs the interface, introduces lambda expression, retrofits the Collection framework and introduce Stream API.

We shall describe these changes before presenting the Java Functional Programming in details.

Interface's default and static Methods

Recall that the main goal of interface is to let you "program on the interface instead of the actual implementation".

Prior to JDK 8, a Java's interface can contain only 2 entities:

  1. public abstract methods: Methods without implementation or body. The implementation subclasses must override these abstract methods by providing implementation body.
  2. public static final fields or constants.

As a consequence, designing and maintaining interfaces becomes difficult because if we were to add an additional method to the interface, all the implementation subclasses must be retrofitted to implement the additional method.

To resolve this constraint, starting from JDK 8, an interface can include public static methods and public default methods. (JDK 9 further includes private method and private static methods into the interfaces.)

  • Both public default method and public static method are non-abstract, with an implementation (method body).
  • Adding a public default method or a public static method to an existing interface does not require retrofitting the existing implementation subclasses.
  • The interface's public default method are inherited by its sub-types (subclasses or sub-interfaces). The implementation subclassess or sub-interfaces CAN override a public default method inherited; but NOT necessarily.
  • The interface's public static method are NOT inherited by its sub-types. It can only be invoke via the super-type; and CANNOT invoke via its sub-types or sub-type's instances. (Unlike superclass' static methods, which are inherited by its subclasses, but CANNOT be overridden, but CAN be hidden by its subclasses.)
  • (JDK 9) The private method and private static method are meant for helper methods within the interface. They are NOT inherited by its subtypes.

In summary, JDK 8/9's interface may include:

  1. public static (class) final fields or constants.
  2. public abstract (instance) methods WITHOUT implementation - MUST be overridden by the implementation subclasses.
  3. public default (instance) method with implementation - inherited by the implementation subclasses; MAY be overridden but NOT necessarily.
  4. public static (class) method with implementation - NOT inherited by its sub-types (unlike superclass' static methods).
  5. (JDK 9) private (instance) method with implementation - NOT inherited by its sub-types; CANNOT be invoked by other static (class) methods within the interface.
  6. (JDK 9) private static (class) method with implementation - NOT inherited by its sub-types; CAN be invoked by other static (class) methods within the interface.

Interface default (instance) Methods

JDK 8's interface supports default (instance) methods via a new keyword "default". Default methods are non-abstract. You need to provide the implementation method body. The implementation subclasses are NOT require to override the default methods, but could do so if needed. The default (instance) methods are always public.

public interface MyJ8InterfaceWithDefault {
   void foo();   // abstract public (instance) (pre-JDK 8)

   // Default methods are marked by keyword "default"
   default void bar() {    // public (instance) (post-JDK 8)
      System.out.println("MyJ8InterfaceWithDefault runs default bar()");
   }
   
   //default void bar1();
   // error: missing method body, or declare abstract
}

If you did not provide the method body to default method, you will receive compilation "error: missing method body, or declare abstract".

public class MyImplClass1 implements MyJ8InterfaceWithDefault {
   // Need to override all the abstract methods,
   //   but not necessarily for the default methods.
   @Override
   public void foo() {
      System.out.println("MyImplClass1 runs foo()");
   }

   // Test Driver
   public static void main(String[] args) {
      MyImplClass1 c = new MyImplClass1();
      c.foo();  // MyImplClass1 runs foo()
      c.bar();  // MyJ8InterfaceWithDefault runs default bar()
   }
}
Implementing Multiple Interfaces

Java classes can implement multiple interfaces (but extend only one superclass). In the above example, if another interface (say MyJ8Interface1) also provides a default method bar(), and a class (say MyClass1) implements both MyJ8Interface and MyJ8Interface1, a problem arises. To resolve this problem, JDK 8 requires the implementation classes to override the default methods if more than one versions are inherited. For example,

public interface MyJ8InterfaceWithDefault1 {
   // Same signature (but different implementation) as the default method in MyJ8InterfaceWithDefault
   default void bar() {   // public (instance) (post-JDK 8)
      System.out.println("MyJ8InterfaceWithDefault1 runs default bar() too!");
   }
}
public class MyImplClass2 implements MyJ8InterfaceWithDefault, MyJ8InterfaceWithDefault1 {
   @Override
   public void foo() {
      System.out.println("MyImplClass2 runs foo()");
   }

   @Override
   public void bar() {
      System.out.println("MyImplClass2 runs overridden bar()");
   }
   // bar() exists in both interfaces.
   // MUST override, or
   //    error: class MyImplClass2 inherits unrelated defaults for bar()
   //    from types MyJ8InterfaceWithDefault and MyJ8InterfaceWithDefault1

   public static void main(String[] args) {
      MyImplClass2 c = new MyImplClass2();
      c.foo();   // MyImplClass2 runs foo()
      c.bar();   // MyImplClass2 runs overridden bar()
   }
}

If you fail to override the default method, you will get an "error: class MyImplClass2 inherits unrelated defaults for bar() from types MyJ8InterfaceWithDefault and MyJ8InterfaceWithDefault1".

Interface static (class) Methods

The static (class) method is similar to the default method. However, it is NOT inherited by its sub-types (unlike superclass' static method). By default, interface's static methods are public (JDK 9 supports private static methods).

public interface MyJ8InterfaceWithStatic {
   void foo();   // abstract public (instance) (pre-JDK 8)

   static void bar() {  // public (class) (post-JDK 8)
      System.out.println("MyJ8InterfaceWithStatic runs static bar()");
   }

   //static void bar1();
   // error: missing method body, or declare abstract   
}

Like default methods, static methods are non-abstract and you need to provide the implementation method body. Otherwise, it triggers compilation error "missing method body, or declare abstract".

public class MyImplClass3 implements MyJ8InterfaceWithStatic {
   @Override
   public void foo() {
      System.out.println("MyImplClass3 run foo()");
   }

   // Test Driver
   public static void main(String[] args) {
      MyImplClass3 c = new MyImplClass3();
      c.foo();  // MyImplClass3 run foo()
      MyJ8InterfaceWithStatic.bar();  // MyJ8InterfaceWithStatic runs static bar()

      // Interface's static methods are NOT inherited (Unlike Superclass)!!!
      // MyImplClass3.bar();
      // c.bar();
            // error: cannot find symbol bar()
      // MyJ8InterfaceWithStatic c1 = new MyImplClass3();
      // c1.bar();
            // error: illegal static interface method call
   }
}

Take note that interface's static methods are NOT inherited by its sub-types!!! Unlike superclass's static methods, which are inherited by its subclasses. This is probably because we can extend only one superclass, but can implement multiple interfaces.

public class MyImplClass4 implements MyJ8InterfaceWithStatic {
   @Override
   public void foo() {
      System.out.println("MyImplClass4 run foo()");
   }

   // @Override  // error: static methods cannot be annotated with @Override
   public static void bar() {
      System.out.println("MyImplClass4 run bar()");
   }

   // Test Driver
   public static void main(String[] args) {
      MyImplClass4 c = new MyImplClass4();
      c.foo();  // MyImplClass3 run foo()

      MyJ8InterfaceWithStatic.bar();  // MyJ8InterfaceWithStatic runs static bar()
      MyImplClass4.bar(); // MyImplClass4 run bar()
      c.bar();            // MyImplClass4 run bar()
   }
}

Since the interface's static methods are NOT inherited by its sub-types, you CAN provide your own definition of the same static methods in the sub-types.

Are Java supclass's static methods inherited by its subclasses?

YES. Read HERE.

Are Java interface's static methods inherited by its sub-types (subclasses or sub-interfaces)?

NO. Probably because a sub-type can implement multiple interfaces, but can extend only one superclass.

Interface "Instance" Methods

There are 3 kinds of method in JDK 8 interface: abstract, default and static. All methods are public.

JDK 8 documentation for interfaces lists "Instance methods", which includes all non-static methods (abstract and default methods).

It lists "All methods", as well as "Static methods", "Instance methods", "Abstract methods" and "Default methods". For example, checkout interface java.util.stream.Stream.

Interface vs. Abstract Superclass

  1. Variables: Interface can contain only constants (public static final variables).
  2. Method Access Control: All methods (abstract, static and default) in interface are public. JDK 9 supports private and private static methods.

Lambda Expressions, Functional Interfaces and Collections

Reference:
  1. Java Tutorial "Lambda Expression" @ http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html.
  2. "Java SE 8: Lambda Quick Start" @ http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html.
  3. Java Tutorial "Collections" @ http://docs.oracle.com/javase/tutorial/collections/.
  4. Java Tutorial "Aggregate Operations" @ http://docs.oracle.com/javase/tutorial/collections/streams/index.html.

 

The most notable new feature in JDK 8 is the Lambda Expression, which provides a concise notation to construct an instance implementing a "Single-Abstract-Method Interface". In JDK 8, a "Single-Abstract-Method Interface" (i.e., an interface containing only one abstract method) is known as a "Functional Interface".

JDK 8 also retrofitted the Collection framework, with Lambda expression, functional interfaces and the introduction of streams API, to support chaining and aggregate operations (or filter-map-reduce) in functional programming.

Functional Interfaces

JDK has many "Single-Abstract-Method Interfaces" (called "Functional Interface" in JDK 8). The most commonly-used are: java.awt.event.ActionListener (used as ActionEvent handler), java.lang.Runnable (for starting a thread) and java.util.Comparator (used in Collections.sort() or Arrays.sort()).

These interfaces are commonly used in anonymous inner classes to construct anonymous instances.

@FunctionInterface Annotation

The @FunctionInterface annotation can be used to mark that an interface contains only one abstract method. This is useful to prevent accidental addition of extra abstract methods into a functional interface.

Example 1: Swing Listener Lambda

In Swing program, we use an ActionListener to handle the ActionEvent, triggered by pushing a button. ActionListener is a Functional interface containing a single abstract method, defined as follows:

package java.awt.event;

@FunctionalInterface
public interface ActionListener extends java.util.EventListener {
   void actionPerformed(ActionEvent e);  // public abstract
}

The following Swing Counter program contains a textfield to display the count, and 2 buttons for counting up and counting down.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CounterLambda extends JFrame {
   JTextField tfCount;
   int count = 0;

   public CounterLambda() {
      Container cp = getContentPane();
      cp.setLayout(new FlowLayout());

      cp.add(new JLabel("Counter"));
      tfCount = new JTextField(count + "", 8);
      tfCount.setEditable(false);
      cp.add(tfCount);

      // Using an anonymous inner class as ActionEvent handler
      JButton btnUp = new JButton("Count Up");
      cp.add(btnUp);
      btnUp.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            tfCount.setText(++count + "");
         }
      });

      // Using a Lambda Expression to return an instance of ActionListener
      JButton btnDown = new JButton("Count Down");
      cp.add(btnDown);
      btnDown.addActionListener(e -> tfCount.setText(--count + ""));

      setSize(400, 100);
      setVisible(true);
   }
   public static void main(String[] args) {
      // Using Lambda Expression to return a Runnable instance
      SwingUtilities.invokeLater(() -> new CounterLambda());
   }
}
How It Works
  1. Using an anonymous inner class (Line 21 to 25) requires at least 5 lines of codes, which can be replaced by a one-liner Lambda Expression (Line 30). You can treat Lambda Expression as a shorthand. I will explain the syntax in the following section.
  2. Similar, the Runnable can be coded in a one-liner using Lambda Expression (Line 37).
JavaFX

For JavaFX, you can also replace an anonymous inner class EventHandler with a one-line lambda expression. For example,

// Anonymous inner class
btn.setOnAction(new EventHandler<ActionEvent>() {
   @Override
   public void handle(ActionEvent e) {
      System.out.println("hello, world");
   }
});

// Lambda Expression
btn.setOnAction(e -> System.out.println("hello, world"));

Syntax and Usage of Lambda Expressions

Prior to JDK 8, to construct an instance that implements a Functional Interface requires many lines of codes. Lambda Expression provides a shorthand notation. Moreover, you can pass a lambda expression as a method argument (treating code as data), as shown in the above example.

Lambda Expression defines the "sole" method of a Functional Interface. It consists of 2 parts: parameters and method body, separated by ->. The parameters are separated by commas and enclosed by parentheses. The parentheses can be omitted if there is only one parameter. The method body could be a statement or a block. The method name is omitted, as it is the sole abstract method of the Functional Interface. The parameters' type and the return type are also optional, as they can be inferred from the method signature.

The syntax is:

(arguments) -> (body)

For examples:

() -> statement    // No argument and one-statement method body

arg -> statement   // One argument (parentheses can be omitted) and method body 

(arg1, arg2, ...) -> { 
   body-block 
}   // Arguments separated by commas and block body

(Type1 arg1, Type2 arg2, ...) -> { 
   method-body-block;
   return return-value; 
}   // With arguments and block body

In other languages that support function variables or function objects (such as C++ and Python), Lambda is used to define an anonymous function. However, in JDK 8, Lambda expression is used to define the method implementation of an instance of a Single-Abstract-Method Interface?!

In fact, if you try to write:

int i = () -> 5;
// error: incompatible types: int is not a functional interface

But,

// Using Lambda expression to construct a Runnable instance.
// In other words, Lambda expression returns an instance of Function Interface
Runnable r1 = () -> System.out.println("run run()");
   // Runnable is a functional interface
   // Lambda expression is used to define the implementation of the abstract method run()

// Using Lambda expression to construct an ActionListener instance
ActionListener lis = e -> System.out.println("run actionPerformed()");

Java is an Object-oriented Programming language. Everything in Java are objects (except primitives for simplicity). Functions are not objects in Java (but part of an object), and hence, they cannot exist by themselves. Unlike other languages (such as C++, Python and JavaScript) functions can exist by themselves, and you can pass a function as a funtion's argument, and return a function from a funtion.

JDK 8's Functional Interface and Lambda Expression allow us to construct a "Function Object" in a one-liner (or a fewer lines of codes). However, it can be used only for objects with one method.

Example 2: Runnable Lambda

The Runnable interface contains a single abstract method, defined as follows:

@FunctionalInterface
public interface Runnable {
   void run();  // public abstract
}

We can create a Runnable object via anonymous inner class (Pre-JDK 8) or Lambda Expression (JDK 8), as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestRunnableLambda {
   public static void main(String[] args) {
      // Using an anonymous inner class
      Runnable r1 = new Runnable() {
         public void run() {
            System.out.println("Runnable 1");
         }
      };
      // Using a one-liner Lambda Expression for One-Method Interface
      Runnable r2 = () -> System.out.println("Runnable 2");

      r1.run();
      r2.run();
   }
}

[TODO] Explanation

Example 3: Functional Interface for Binary Operators

Let's define a Functional Interface to denote a binary operator (such as add or subtract), as follows:

@FunctionalInterface
public interface MyIntBinaryOperator {
   int applyAsInt(int left, int right);
}
In the following class, the method operate() takes 2 int's and an object of the above Functional Interface as parameters, and carries out the binary operation.
public class MyMathBinaryOperation {
   // Define instances of IntBinaryOperator for add, subtract, multiply and divide
   public MyIntBinaryOperator add = (a, b) -> a + b;
   public MyIntBinaryOperator sub = (a, b) -> a - b;
   public MyIntBinaryOperator mul = (a, b) -> a * b;
   public MyIntBinaryOperator div = (a, b) -> a / b;

   // Carry out the binary operation
   public int operate(int left, int right, MyIntBinaryOperator op) {
      return op.applyAsInt(left, right);
   }

   // Test Driver
   public static void main(String args[]){
      MyMathBinaryOperation op = new MyMathBinaryOperation();
      // Use pre-defined IntBinaryOperator
      System.out.println("8 + 9 = " + op.operate(8, 9, op.add));
      System.out.println("8 - 9 = " + op.operate(8, 9, op.sub));
      System.out.println("8 x 9 = " + op.operate(8, 9, op.mul));
      System.out.println("8 / 9 = " + op.operate(8, 9, op.div));
      // Use a custom IntBInaryOperator
      System.out.println("2 ^ 5 = " + op.operate(2, 5, (a, b) -> (int)Math.pow(a, b)));
   }
}

[TODO] Explanation

Package java.util.function

The JDK 8 new package java.util.function provides similar functional interfaces, such as type-specific IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator, and a generic BinaryOperator.

We will discuss java.util.function in details later.

Example 4: Comparator Lambda

We can use the static method Collections.sort() to custom sort a Collection object, which has the following signature:

public static <T> void sort(List<T> list, Comparator<? super T> c)

The second argument of sort() is a Functional Interface Comparator, which contains an abstract method to compare two objects of the given Collection, defined as follows:

package java.util;

@FunctionalInterface
public interface Comparator<T> {
   // Compares its two arguments for order.
   // Returns a negative integer, zero, or a positive integer
   //   as the first argument is less than, equal to, or greater than the second.
   int compare(T o1, T o2);  // public abstract
}

Suppose that we have a List of Person objects, and want to perform a custom sort. Again, we could use an anonymous inner class (Pre-JDK 8), or a Lambda Expression (JDK 8) to construct a Comparator instance.

Person.java
public class Person {
   private String name;
   private int age;

   public Person(String name, int age) {  // Constructor
      this.name = name; this.age = age;
   }
   public String getName() {
      return name;
   }
   public int getAge() {
      return age;
   }
   public String toString() {
      return name + "(" + age + ")";
   }
   // To be used in testing Consumer
   public void sayHello() {
      System.out.println(name + " says hello");
   }
}
import java.util.*;
public class TestComparatorLambda {
   public static void main(String[] args) {
      List<Person> pList = new ArrayList<>();
      pList.add(new Person("Peter", 21));
      pList.add(new Person("Paul", 18));
      pList.add(new Person("Patrick", 22));
      System.out.println(pList);
         // Unsorted: [Peter(21), Paul(18), Patrick(22)]

      // In JDK 9, you can simply write:
      List<Person> pList9 = List.of(new Person("Peter", 21), new Person("Paul", 18));
      System.out.println(pList9);
      
      // Using an anonymous inner class to create a Comparator instance
      Collections.sort(pList, new Comparator<Person>() {
         @Override
         public int compare(Person p1, Person p2){
            return p1.getName().compareTo(p2.getName());  // String's compareTo()
         }
      });
      System.out.println(pList);
         // Sort by name: [Patrick(22), Paul(18), Peter(21)]

      // Using a Lambda Expression to create a Comparator instance
      Collections.sort(pList, (p1, p2) -> p1.getAge() - p2.getAge());
      System.out.println(pList);
         // Sort by age: [Paul(18), Peter(21), Patrick(22)]
   }
}
How It Works
  1. For the first sort(), we use an anonymous inner class to construct an instance of Comparator; whereas for the second sort(), we replaced by a one-liner lambda expression to construct an instance of Comparator.

Example 5: Collection

JDK 8 did a major revamp to the Collection framework, by integrating with Lambda expression and introducing Stream API to support Functional Programming.

This example is modified from Java Tutorial.

Use Case - Filter and Reduce

Suppose that we have a List (a subclass of Collection) of Person objects (defined in Person.java in the above example) and we want to:

  1. Loop through the entire list,
  2. Filter with a certain criteria (e.g., age >= 21),
  3. Run certain operations to the filtered list (e.g., invoke sayHello(), toString(), or find the average age). This is known as a reduction operation which allows you to compute a result.

The codes should be general to handle any filtering criteria and run any reduction operations.

Approach 1: Roll Your Own

Person.java: As in the above example.

PersonPredicate.java: Define a Functional Interface called PersonPredicate to perform filtering, based on a boolean function test(), as follows. (A predicate is a boolean function or relation that returns true to indicate such a relation, i.e., P: X-> {true, false}.)

@FunctionalInterface
public interface PersonPredicate {
   boolean test(Person p);  // Perform this boolean test on the given Person
}

PersonConsumer.java: Define a Functional Interface called PersonConsumer to run some operations on a Person object, as follows:

@FunctionalInterface
public interface PersonConsumer {
   void accept(Person p);  // Run these operations on the given Person
}

PersonsFilterReduce.java:

import java.util.List;

public class PersonsFilterReduce {
}

ProcessPersons.java: we shall define a static method process() to carry out the filter-reduce operation, by looping through the List<Person>. We can test with various filters and reduction operations as in test driver:

import java.util.*;

public class ProcessPersons {
   // Given a List<Person>, filter with predicate, and consume.
   public static void process(List<Person> pList, PersonPredicate predicate, PersonConsumer consumer) {
      for (Person p : pList) {
         if (predicate.test(p)) {  // Filter
            consumer.accept(p);    // Reduce
         }
      }
   }

   public static void main(String[] args) {
      // Create a List of Person objects
      List<Person> pList = new ArrayList<>();
      pList.add(new Person("Peter", 21));
      pList.add(new Person("Paul", 60));
      pList.add(new Person("Patrick", 15));
      System.out.println(pList);  // [Peter(21), Paul(60), Patrick(15)]

      // Pre-JDK 8: Using anonymous inner classes
      ProcessPersons.process(
         pList,
         new PersonPredicate() {
            @Override
            public boolean test(Person p) {
               return p.getAge() >= 21;  // Filtering criteria
            }
         },
         new PersonConsumer() {
            @Override
            public void accept(Person p) {
               p.sayHello();   // Apply this operation
            }
         }
      );

      // JDK 8: Using Lambda Expressions
      ProcessPersons.process(pList, p -> p.getAge() >= 21, p -> p.sayHello());
   }
}

[TODO] Explanation

Approach 2: Using JDK 8 Pre-defined Functional Interfaces

JDK 8 added a new package java.util.function, which contains many standard Functional Interfaces, including Predicate and Consumer, defined with generic, as follows:

java.util.function.Predicate:

package java.util.function;

@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);  // Evaluates this predicate on the given object.
   ......
}

java.util.function.Consumer:

package java.util.function;

@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);  // Run this operation on the given object.
   ......
}

Instead of rolliing our own functional interfaces (the non-generic PersonPredicate and PersonConsumer) in the above, we shall use the generic ones.

PersonsFilterReduce.java:

import java.util.List;
import java.util.function.Predicate;
import java.util.function.Consumer;
public class PersonsFilterReduce {
   ......

}

ProcessPersons.java: We add a method process1(), which uses the standard functional interfaces Predicate<Person> and Consumer<Person>. We can test this new method as follows:

......
import java.util.function.*;
public class ProcessPersons { ...... public static void process1(List<Person> pList, Predicate<Person> predicate, Consumer<Person> consumer) { for (Person p : pList) { if (predicate.test(p)) { consumer.accept(p); } } } public static void main(String[] args) { ...... // Using JDK 8 standard functional interfaces Predicate<T> and Consumer<T> ProcessPersons.process1(pList, p -> p.getAge() >= 21, p -> p.sayHello()); } }
Approach 3: Filter-Map-Reduce

Suppose that instead of operating on the filtered List (of Person objects), we want to operate on a certain property of Person (e.g., name), then we need to add a mapper or transformer (i.e., Filter-Map-Reduce pattern). We shall use the standard Functional Interface java.util.function.Function as our mapper, defined as follows:

java.util.function.Function:

package java.util.function;

@FunctionalInterface
public Function<T, R> {
   R apply(T t);  // Apply this mapping to the given object.
   ......
}

ProcessPersonList.java:

ProcessPersons.java: We add a new method process2(). We can test this new method as follows:

import java.util.*;
import java.util.function.*;

public class ProcessPersons {
   ......
   // Given a List, filter with predicate, apply mapper, and reduce (filter-map-reduce)
   public static void process2(List<Person> pList, 
                               Predicate<Person> predicate, 
                               Function<Person, String> mapper, 
                               Consumer<String> consumer) {
      for (Person p:pList) {
         if (predicate.test(p)) {
            String s = mapper.apply(p);  // Apply mapper to transfom Person to String
            consumer.accept(s);
         }
      }
   }

   public static void main(String[] args) {
      ......

      // Using Lambda Expression
      ProcessPersons.process2(
         pList, 
         p -> p.getAge() >= 21, 
         p -> p.getName(), 
         name -> System.out.println(name)
      );

      // Using method references
      ProcessPersons.process2(pList, p -> p.getAge() >= 21, Person::getName, System.out::println);
   }
}

[TODO] Explanation

Method References

JDK 8 introduced a new operator :: to reference a method without invoking it - called Method Reference. For example,

// Method References
System.out::println
Person::getName
Person::sayHello
"xyz"::length

// Constructor References
Integer::new
int[]::new

We can replace Lambda Expression p -> p.method() with a method reference ClassName::method, as in the above example.

Approach 4: Using JDK 8's Stream API and Pipeline

JDK 8 added a new Stream API to the Collection framework to support aggregate operations (of Functional Programming). This can simplify the above filter-map-reduce to a one-liner. Furthermore, there is no need for an explicit for-loop.

For example,

import java.util.*;
import java.util.function.*;

public class ProcessPersons {
   public static void main(String[] args) {
      ......
      
      // Using JDK 8 Stream for filter-reduce
      pList.stream().filter(p -> p.getAge() >= 21).forEach(p -> p.sayHello());
      pList.stream().filter(p -> p.getAge() >= 21).forEach(Person::sayHello);  // Using method reference

      // Using map() to extract a specific property from the object
      Predicate<Person> adult = p -> p.getAge() >= 21;
      pList.stream().filter(adult).map(p -> p.getName()).forEach(name -> System.out.println(name));
      pList.stream().filter(adult).map(Person::getName).forEach(System.out::println);

      // Apply aggregate operation average(), sum() to an int property extracted via mapToInt()
      System.out.println(pList.stream().filter(adult).mapToInt(p -> p.getAge()).average().getAsDouble());
      System.out.println(pList.stream().filter(adult).mapToInt(Person::getAge).average().getAsDouble());
      System.out.println(pList.stream().filter(adult).mapToInt(Person::getAge).sum());
   }
}
Pipeline

A pipeline is a sequence of operations on a Collection (or array). The sequence composes:

  1. A source: a collection or an array, e.g., pList (which is a List of Person objects) in the above examples.
  2. stream(): produce a Stream, which is a sequence of elements to be carried from the source into the pipeline.
  3. Some intermediate operations: for example, filter(Predicate), which creates a new Stream consisting of elements that matches the Predicate.
  4. A terminal operation (the reduction operation): such as forEach(), which produces the desired result.
The java.util.stream.Stream Interface

A Stream is a sequence of elements supporting sequential and parallel aggregate operations in a stream pipeline. We can create a sequential Stream via the new default method in the Collection interface stream(); or create a parallel Stream via method parallelStream(), defined as follows:

interface Collection<E> {
   default Stream<E> stream()          // Returns a sequential Stream with this Collection as its source
   default Stream<E> parallelStream()  // Returns a possibly parallel Stream with this Collection as its source
   ......
}

There are 3 primitive type specializations for Stream: IntStream, LongStream and DoubleStream.

[TODO]

The methods in the pipeline chain
  • aCollection.stream(): returns a sequential Stream with this Collection as its source.
  • aStream.filter(aPredicate): Filter this Steam with the given java.util.function.Predicate object.
    Stream<T> filter(Predicate<? super T> predicate)
  • aStream.forEach(aConsumer): Run the operation in the java.util.function.Consumer object.
    void forEach(Consumer<? super T> action)
  • aStream.map(aFunction): Apply the mapping function (transformation) in the given java.util.function.Function object to map from T to R.
    <R> Stream<R> map(Function<? super T, ? extends R> mapper)
  • aStream.mapToInt(anToIntFunction): A type specialization of map() that returns an IntStream (a primitive type specialization of Stream).
    IntStream mapToInt(ToIntFunction<? super T> mapper)
    
  • anIntStream.average().getAsDouble(): Compute the average for this IntStream.
    OptionalDouble average()  // Returns an OptionalDouble for the average of this stream,
                              // or an empty OptionalDouble if this stream is empty.
    The result is a java.util.OptionDouble object, which is a container may or may not contain a double value. If a value is present, isPresent() will return true and getAsDouble() will return the value.
  • anIntStream.sum(): Compute the sum of this IntStream and return the int sum.
    int sum()  // Returns the sum of elements in this stream as int

JDK 8 java.util.function package

JDK 8 provides a new package java.util.function, which provides a number of standard Functional Interfaces. These interfaces heavily use default and static methods (with implementation) introduced in JDK 8 to enhance their functionality. (JDK 8 also retrofitted some existing interfaces with default and static methods for backward compatibility).

 

Predicate: A predicate is a boolean function in the form of P: T -> {true, false}.

The Predicate interface contains:

  1. abstract boolean method test() to implement predicate test.
  2. default methods and(), or() and negate() for logical AND, OR and negate with test().
  3. static method isEqual() for equality test.

Study the code, which is extracted from the JDK source.

package java.util.function;
import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);  // Evaluates this predicate on the given argument.

   // Returns a composed predicate that represents a short-circuiting logical AND
   //   of this predicate and another.
   default Predicate<T> and(Predicate<? super T> other) {
      Objects.requireNonNull(other);  // ensure "other" is NOT null
      return (t) -> this.test(t) && other.test(t);  // return an instance of Predicate<T>
   }
   // Returns a composed predicate that represents a short-circuiting logical OR
   //   of this predicate and another.
   default Predicate<T> or(Predicate<? super T> other) {
      Objects.requireNonNull(other);
      return (t) -> test(t) || other.test(t);
   }

   // Returns a predicate that represents the logical negation of this predicate.
   default Predicate<T> negate() {
      return (t) -> !test(t);
   }

   // Returns a Predicate that tests if two arguments are equal
   //   according to Objects.equals(Object, Object)
   static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
               ? Objects::isNull
               : object -> targetRef.equals(object);
   }
}

For example,

import java.util.*;
import java.util.function.*;

public class TestPredicate {
   public static void main(String[] args) {
      // Create a List of Person objects (JDK 9)
      List pList = List.of(new Person("Peter", 21), new Person("Paul", 60), new Person("Patrick", 15));
      System.out.println(pList);

      Predicate adult = p -> p.getAge() >= 21;
      Predicate senior = p -> p.getAge() >= 55;
      Predicate junior = p -> p.getAge() <= 15;
      pList.stream().filter(adult).map(Person::getName).forEach(System.out::println);
      pList.stream().filter(senior.or(junior)).map(Person::getName).forEach(System.out::println);
      pList.stream().filter(senior.and(junior)).map(Person::getName).forEach(System.out::println);
      pList.stream().filter(junior.or(p -> p.getAge >= 55)).map(Person::getName).forEach(System.out::println);
   }
}

BiPredicate: Binary predicate P: T, U -> {true, false}.

Function: Transform F: T -> R

package java.util.function;
import java.util.Objects;
@FunctionalInterface public Function<T, R> { // Applies this function to the given argument. R apply(T t); // Returns a composed function that first applies the before function to its input, // and then applies this function to the result. default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); // ensure before is NOT null return (V v) -> this.apply(before.apply(v)); // before.apply then this.apply } // Returns a composed function that first applies this function to its input, // and then applies the after function to the result. default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(this.apply(t)); } // Returns a function that always returns its input argument. static <T> Function<T, T> identity() { return t -> t; } }

BiFunction: Binary function F: T, U -> R

package java.util.function;
import java.util.Objects;
@FunctionalInterface public BiFunction<T, U, R> { // Applies this function to the given arguments. R apply(T t, U u); // Returns a composed function that first applies this function to its inputs, // and then applies the after function to the result. default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t, U u) -> after.apply(this.apply(t, u)); } }

Consumer: Accept a single input argument and return no result (C: T -> void). Operate via side-effects.

package java.util.function;
import java.util.Objects;
@FunctionalInterface public interface Consumer<T> { // Performs this operation on the given argument void accept(T t); // Returns a composed Consumer that performs this operation followed by after operation. default Consumer<T> andThen(Function<? super T> after) { Objects.requireNonNull(after); return (T t) -> this.accept(t); after.accept(t); } }

BiConsumer: Binary consumer C: T, U -> void. Again operate via side-effects.

Supplier: Provide an instance of a T

package java.util.function;

@FunctionalInterface
public Supplier<T> {
   T get();  // Gets a result.
}

UnaryOperator: A unary operator, O: T -> T. A Special case of Function, where the type of argument is the same as the return type.

package java.util.function;

@FunctionalInterface
public UnaryOperator<T> extends Function<T, T> {
   // Inherit apply(), default compose() and default andThen() from supertype Function

   // Provide its own identity(), as static method are NOT inherited.
   static <T> UnaryOperator<T> identity() { ...... }
}

BinaryOperator: A binary operator, O: T, T -> T. A special case of BiFunction, where inputs and return value have the same type.

package java.util.function;

@FunctionalInterface
public BinartOperator<T> extends BiFunction<T, T, T> {
   // Inherit apply() and default andThen() from supertype BiFunction
   static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator);
   static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator);
}
Primitive Type Specializations

Many of the above Functional Interfaces have type specialization for primitives int, long and double. For examples, the int specializations are:

  • IntFunction:
    package java.util.function;
    
    @FunctionalInterface
    public IntFunction<R> {
       R apply(int value);
    }
  • IntPredicate:
    package java.util.function;
    
    @FunctionalInterface
    public IntPredicate {
       boolean test(int value);
       default IntPredicate and(IntPredicate other);
       default IntPredicate or(IntPredicate other);
       default IntPredicare negate()
    }
  • IntConsumer:
    package java.util.function;
    
    @FunctionalInterface
    public IntConsumer {
       void apply(int value);
       default IntConsumer andThen(IntConsumer after);
    }
  • IntSupplier:
    package java.util.function;
    
    @FunctionalInterface
    public IntSupplier {
       int getAsInt();
    }
  • IntBinaryOperator:
    package java.util.function;
    
    @FunctionalInterface
    public IntBinartOperator<T> {
       int applyAsInt(int left, int right);
    }
  • IntUnaryOperator:
    package java.util.function;
    
    @FunctionalInterface
    public IntUnaryOperator<T> {
       int applyAsInt(int operand);
       default IntUnaryOperator compose(IntUnaryOperator before);
       default IntUnaryOperator andThen(IntUnaryOperator after);
       static IntUnaryOperator identity();
    }
  • IntToDoubleFunction:
    package java.util.function;
    
    @FunctionalInterface
    public IntToDoubleFunction {
       double applyAsDouble(int value);
    }
  • IntToLongFunction:
    package java.util.function;
    
    @FunctionalInterface
    public IntToLongFunction {
       long applyAsLong(int value);
    }

JDK 8 java.util.stream package

JDK 8 added a new java.util.stream package to support Stream API. Stream API allows sequential as well as parallel execution. The new Collection's Stream API is good for Big Data analysis such as performing filter/map/reduce like operations with the collection.

JDK 8 added two default method stream() and parallelStram() into the java.util.Collection<E> interface for producing sequential and parallel streams with this Collection as its source, as follows:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
} default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true);
}

For example,

import java.util.List;
import java.util.ArrayList;
import java.util.stream.Stream;

public class TestSequentialParallelStreams {
   public static void main(String[] args) {
      List<Integer> lst = new ArrayList<>();
      for (int i = 1; i <= 100; ++i) {
         lst.add(i);
      }
      System.out.println(lst);

      Stream<Integer> sequentialStream = lst.stream();
      Stream<Integer> parallelStream   = lst.parallelStream();

      sequentialStream
         .filter(i -> (i % 10) == 0)
         .forEach(i -> System.out.println("sequential: " + i)); // outputs sequentail

      parallelStream
         .filter(i -> (i % 10) == 0)
         .forEach(i -> System.out.println("parallel: " + i)); // outputs non-sequentail
   }
}

[TODO]

More on Lambda Expression and Functional Programming

Java is an OOP language where functions are defined inside objects. Java does not support passing of function as a function's argument and returning a function. You need to pass objects arguments into function.

On the other hand, functional programming are based on functions and passing of functions. Lambda Expression lets you visualize functional programming in the Java object-oriented world, by wrapping a function in a Functional Interface (Object).

The benefits of Lambda expression are:

  1. Reduced lines of codes.
  2. Support parallel execution.
  3. Passing of behavior (in Functional Interface instance) as function's argument.

JDK 8 Date/Time API

References: The Java Tutorial: Date Time @ https://docs.oracle.com/javase/tutorial/datetime/.

Prior to JDK 8, It has always been hard to work with date, time and time zones, because there is no standard approach or API for date and time. For example, we have Date class in both java.util (with both date and time) and java.sql (with date only) packages. Formatting and parsing classes are defined in java.text package. All the date classes are mutable, hence, they are NOT thread safe in multithreading. There is no internationalization support (such as time zone) in java.util.date. So, java.util.Calendar and java.util.TimeZone classes were introduced.

JDK 8 revamped the date-time support by introducing a new package java.time to provide a comprehensive support of dates, times, instants, durations, timezones, and periods. All the classes are immutable and thread safe for multithreading.

JDK 8 Date/Time API is comprehensive! It consists of main package java.time and four sub-packages:

  • java.time: Core API classes.
    • LocalDate, LocalTime, LocalDateTime: no time zone
    • ZonedDateTime: with time zone
    • Instant: machine readable time representation
    • Period, Duration:
    • Clock:
    These classes are based on ISO-8601 (Gregorian calendar system).
  • java.time.format: Classes for formatting and parsing dates and times.
  • java.time.zone: Classes for time zones, time-zone offsets, and time zone rules (ZonedDateTime, and ZoneId or ZoneOffset).
  • java.time.temporal: Extended API for interoperations between the date and time classes, querying, and adjustment.
  • java.time.chrono: for calendar systems other than the default ISO-8601 (e.g., Thai Buddhist and Japanese), not commonly-used.

There are two ways to represent date-time, which are clearly separated in the new API:

  1. Human Time: in terms of year, month, day, hour, minute and second.
  2. Machine Time: in nanoseconds or days from an origin called epoch (1970-01-01T00:00:00Z)).

java.time by Examples

Example on Classes LocalDate, LocalTime, LocalDateTime

LocalXxx classes (such as LocalDate, LocalTime, LocalDateTime) represents a human readable date/time without timezone.

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Month;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.ZoneId;
public class TestLocalDateTime { public static void main(String[] args) { // Test LocalDate LocalDate d1 = LocalDate.now(); System.out.println(d1); // 2018-04-30 LocalDate d2 = LocalDate.of(2017, Month.JANUARY, 8); // year, month, day System.out.println(d2); // 2017-01-08 // Test LocalTime LocalTime t1 = LocalTime.now(); System.out.println(t1); // 21:24:24.699162200 LocalTime t2 = LocalTime.of(12, 34, 56, 123456); // hour, minute, second, nanosecond System.out.println(t2); // 12:34:56.000123456 // Test LocalDateTime LocalDateTime dt1 = LocalDateTime.now(); System.out.println(dt1); // 2018-04-30T21:25:38.615116500 (default foramt in ISO_LOCAL_DATE_TIME) // Test "Getters" LocalDateTime dt2 = LocalDateTime.of(2017, 2, 18, 23, 56, 45, 123456789); System.out.println(dt2); // 2017-02-18T23:56:45.123456789 System.out.println(dt2.getYear()); // 2017 System.out.println(dt2.getMonth()); // FEBRUARY (using enum java.time.Month) System.out.println(dt2.getDayOfMonth()); // 18 System.out.println(dt2.getHour()); // 23 System.out.println(dt2.getMinute()); // 56 System.out.println(dt2.getSecond()); // 45 System.out.println(dt2.getNano()); // 123456789 (nanosecond) System.out.println(dt2.getDayOfWeek()); // SATURDAY (using enum java.time.DayOfWeek) System.out.println(dt2.getDayOfYear()); // 49 // Test "Setters": DateTime classes are immutable. Return a new instance LocalDateTime dt2a = dt2.withYear(2018); System.out.println(dt2a); // 2018-02-18T23:56:45.123456789 LocalDateTime dt2b = dt2.withDayOfYear(365); System.out.println(dt2b); // 2017-12-31T23:56:45.123456789 // Test Output Formatter LocalDateTime dt3 = LocalDateTime.of(2017, 2, 18, 23, 56, 45); // Using formatter with pre-defined constant System.out.println(dt3.format(DateTimeFormatter.ISO_LOCAL_DATE)); // 2017-02-18 // Using formatter with pattern DateTimeFormatter f1 = DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm:ss"); // HH for 24-hour clock, kk for 12-hour clock System.out.println(dt3.format(f1)); // 18-Feb-2017 11:56:45 // Test Input Parser LocalDateTime dt4 = LocalDateTime.parse("2017-05-02T23:15:43.967"); System.out.println(dt4); // 2017-05-02T23:15:43.967 LocalDateTime dt5 = LocalDateTime.parse("18-Feb-2017 11:56:45", f1); System.out.println(dt5); // 2017-02-18T11:56:45 // You can also create the current date/time specifying a timezone. LocalDateTime dt6 = LocalDateTime.now(ZoneId.of("America/Los_Angeles")); System.out.println(dt6); // 2018-04-30T06:50:00.419534 } }

The API's methods have standardized perfixes as follows:

  • now: static factory method for creating an instance of current date, time.
  • of: static factory methods for creating an instance.
  • getXxx: get an attribute.
  • withXxx: returns a copy of the target object with one element changed. Similar to set, but all date-time classes are immutable.
  • parse: static factory methods for parsing the input string to produce an instance.
  • format: using the formatter to produce a string.
  • is: query the state.
  • plus, minus: returns a copy with the amount of time added or subtracted.
  • to: convert to another type.
  • at: combine with another.
Example on Date/Time Utilities

The LcoalDate/LocalTime/LocalDateTime classes provide various utility methods such as plusXxx(), minusXxx(). There are some other utility methods for adjusting the date using TemporalAdjuster and to calculate the Period between two dates. For example,

import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class TestDateTimeCalculation {
   public static void main(String[] args) {
      LocalDate d1 = LocalDate.of(2000, 1, 23);
      System.out.println(d1);  // 2000-01-23

      System.out.println(d1.isLeapYear());   // true
      System.out.println(d1.isBefore(LocalDate.of(2000, 12, 30))); // true
      System.out.println(d1.isAfter(LocalDate.of(2000, 12, 30)));  // false
      System.out.println(d1.plusDays(2));    // 2000-01-25
      System.out.println(d1.plusWeeks(4));   // 2000-02-20
      System.out.println(d1.plusMonths(6));  // 2000-07-23
      System.out.println(d1.plusYears(8));   // 2008-01-23
      System.out.println(d1.minusDays(2));   // 2000-01-21
      System.out.println(d1.minusWeeks(4));  // 1999-12-26
      System.out.println(d1.minusMonths(6)); // 1999-07-23
      System.out.println(d1.minusYears(8));  // 1992-01-23

      LocalDate d2 = d1.with(TemporalAdjusters.firstDayOfMonth());
      System.out.println(d2);  // 2000-01-01
      LocalDate d3 = d1.with(TemporalAdjusters.lastDayOfYear());
      System.out.println(d3);  // 2000-12-31

      Period p1 = d1.until(d2.plusYears(10));
      System.out.println(p1);  // P9Y11M9D (period of 9 Years 11 months 9 days)
   }
}
Example on Class Instant, Duration

The Instant class is used to work with machine readable time format, it stores date time in Unix timestamp. The Duration class models a quantity or amount of time in terms of seconds and nanoseconds (whereas Period in the previous example expressed in days, months and years).

import java.time.Instant;
import java.time.Duration;

public class TestInstant {
   public static void main(String[] args) {
      Instant t1 = Instant.now();
      System.out.println(t1);  // 2018-04-30T14:30:12.213617900Z
      long epochSecond = t1.getEpochSecond();
      System.out.println(epochSecond);  // 1525098612

      Instant t2 = Instant.ofEpochSecond(epochSecond + 86400);  // a day
      System.out.println(t2);  // 2018-05-01T14:30:12Z

      Duration d1 = Duration.ofDays(15);
      System.out.println(d1);  // PT360H
      System.out.println(d1.getSeconds());  // 1296000
   }
}
Example of ZonedDateTime

The ZonedDateTime represent a date/time with a timezone. (There are no ZonedDate or ZonedTime classes.)

import java.time.ZonedDateTime;
import java.time.ZoneId;

public class TestZonedDateTime {
   public static void main(String[] args) {
      ZonedDateTime dt1 = ZonedDateTime.now();  // in default timezone
      System.out.println(dt1);  // 2018-04-30T23:24:38.476241100+08:00[Asia/Singapore]

      ZonedDateTime dt2 = ZonedDateTime.of(2000, 1, 2, 3, 4, 5, 6, ZoneId.of("America/Los_Angeles"));
      System.out.println(dt2);  // 2000-01-02T03:04:05.000000006-08:00[America/Los_Angeles]
   }
}

Compared with LocalDateTime, ZonedDateTime clearly maintains the time zone.

Enum java.time.DayOfWeek and java.time.Month

Enum DayOfWeek

For representing day of the week, with 7 constants MONDAY (1) through SUNDAY (7) and various operations.

Enum Month

For representing month names, with 12 constants JANUARY (1) through DECEMBER (12) and various operations.

Collection API Improvements

Iterable's forEach()

JDK 8 added a new method forEach() to the java.lang.Iterable interface. Since java.util.Collection is a subtype of Iterable, foreach() is available to all Collection objects.

This forEach() is a default method in the Iterable interface, which takes a Comsumer as its argument, defined as follows:

default void forEach(java.util.function.Consumer<? super T> action) {
   Objects.requireNonNull(action);  // Ensure that action is NOT null
   for (T t : this) {
      action.accept(t);
   }
}
Example

Person.java: as above.

TestIterableForEach.java:

import java.util.*;
import java.util.function.*;

public class TestIterableForEach {
   public static void main(String[] args) {
      List<Person> pList = new ArrayList<>();
      pList.add(new Person("Peter", 21));
      pList.add(new Person("Paul", 60));
      pList.add(new Person("Patrick", 15));
      System.out.println(pList);

      // Pre-JDK 8: Using for-each loop
      for (Person p: pList) {
         System.out.println(p);
         System.out.println(p.getName());
      }

      // Pre-JDK 8: Using Iterator
      Iterator<Person> iter = pList.iterator();
	     while (iter.hasNext()) {
			Person p = iter.next();
			System.out.println(p.getName());
		 }

      // JDK 8 Iterable's forEach(): with Consumer anonymous inner class
      pList.forEach(new Consumer<Person>() {
         @Override
         public void accept(Person p) {
            p.sayHello();
         }
      });
      
      // JDK 8 Iterable's forEach(): with Lambda Expression
      pList.forEach(p -> System.out.println(p));
      pList.forEach(p -> System.out.println(p.getName()));
      pList.forEach(p -> p.sayHello());

      // JDK Iterable's forEach(): with Method Reference
      pList.forEach(System.out::println);
      pList.forEach(Person::sayHello);
} }

[TODO] Explanation

Notes: The Iterable.forEach() is different from Stream.forEach().

Others

  • Interface java.lang.Iterable (supertype of java.util.Collection)'s default method forEach(), as discussed before.
  • Interface java.util.Iterator's default method forEachRemaining(Consumer<? super E> action) to perform the action for each of the remaining elements.
  • Interface java.util.Collection's default method removeIf(Predicate<? super E> filter) to remove all the elements of this collection that satisfy the given predicate. This complements the existing abstract methods remove(Object o) and removeAll(Collection<?> c).
  • Interface java.util.Collection's default method stream() and parallelStream() to create a sequential and parallel stream; and spliterator() to create a Spliterator which supports parallel as well as sequential operations.
  • Interface java.util.Map's default methods: compute(), merge(), remove(), replace(), and etc.
  • Interface java.uitl.Comparator's default methods: ...
  • more.

IO Improvements

[TODO]

Concurrency API Improvements

[TODO]

Miscellaneous Core API Changes

min(), max(), sum() in Integer, Long and Double Wrapper Classes

[TODO]

Unsigned int and long Support

JDK 8 does not introduce a C-like new type "unsignedInt". But it added new methods in Integer and Long classes to treat an int and long as unsigned integer.

For example,

public class TestUnsignedInteger {
   public static void main(String[] args) {
      // Pr-JDK 8
      // 32-bit signed int ranges from −2,147,483,648 −(2^31) to 2,147,483,647 (2^31 – 1)
      System.out.println(Integer.parseInt("2147483647"));  // max 32-bit unsigned integer
      System.out.println(Integer.parseInt("-2147483648")); // min 32-bit unsigned integer
      //System.out.println(Integer.parseInt("2147483648"));  // error: NumberFormatException

      // JDK 8
      // 32-bit unsigned int ranges from 0 to 4,294,967,295 (2^32 – 1)
      int i1 = Integer.parseUnsignedInt("4294967295");  // max 32-bit unsigned integer
      System.out.println(i1);   // -1 (treated as signed int)
      System.out.println(Integer.toUnsignedString(i1));  // 4294967295
      System.out.println(Integer.toUnsignedString(-1));  // 4294967295

      long l1 = Long.parseUnsignedLong("18446744073709551615");  // max 64-bit unsigned integer
      System.out.println(l1);  // -1 (treated as signed long)
      System.out.println(Long.toUnsignedString(l1));  // 18446744073709551615
      System.out.println(Long.toUnsignedString(-1));  // 18446744073709551615
   }
}

JDK 8 added these static methods in the java.lang.Integer class to deal with unsigned integers:

  • static int compareUnsigned(int x, int y): treat x and y as unsigned.
  • static int divideUnsigned(int dividend, int divisor):
  • static int remainderUnsigned(int dividend, int divisor):
  • static int parseUnsignedInt(...):
  • static String toUnsignedString(int i):

Similar static methods were also added in the java.land.Long class.

logicalAnd(), logicalOr(), logicalXor() in Boolean Wrapper Class

[TODO]

More utiltiy methods in java.lang.Math class

  • int addExact​(int x, int y): throwing an exception if the result overflows an int.
  • long addExact​(long x, long y): throwing an exception if the result overflows an long.
  • subtractExact​(), multiplyExact​(), incrementExact​(), decrementExact​(), negateExact​(): for int and long.
  • floorDiv​(), floorMod​(): for int and long.
  • int toIntExact​(long value):
  • nextDown​(double), nextDown​(float):

[TODO] Examples

Others

  • jjs command is added to invoke Nashorn JavaScript Engine.
  • jdeps command is added to analyze class files.
  • JDBC-ODBC Bridge has been removed.
REFERENCES & RESOURCES
  1. What's New in JDK 8 @ http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html.