部分转载,部分总结
https://blog.csdn.net/hulinku/article/details/79065023
https://www.ntu.edu.sg/home/ehchua/programming/java/JDK7_NewFeatures.html
https://www.ntu.edu.sg/home/ehchua/programming/java/JDK8_NewFeatures.html
https://www.ntu.edu.sg/home/ehchua/programming/java/JDK9_NewFeatures.html
https://www.ntu.edu.sg/home/ehchua/programming/java/JDK10_NewFeatures.html
Java 5
1、泛型 Generics:
引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处。
A、类型安全
抛弃List、Map,使用List<T>、Map<K,V>给它们添加元素或者使用Iterator<T>遍历时,编译期就可以给你检查出类型错误
B、方法参数和返回值加上了Type
抛弃List、Map,使用List<T>、Map<K,V>
C、不需要类型转换
List<String> list=new ArrayList<String>();
String str=list.get(i);
如果通配符?让我们的参数类型过于广泛,我们可以把List<?>、Iterator<?> 修改为
List<? Extends Number>、Iterator<? Extends Number>限制一下它。
2、枚举类型 Enumeration:
3、自动装箱拆箱(自动类型包装和解包)autoboxing & unboxing:
简单的说是类型自动转换。
自动装包:基本类型自动转为包装类(int ——Integer)
自动拆包:包装类自动转为基本类型(Integer——int)
4、可变参数varargs(varargs number of arguments)
参数类型相同时,把重载函数合并到一起了。
如:
public void test(object… objs){
for(Object obj:objs){
System.out.println(obj);
}
}
5、Annotations(注解) 它是java中的metadata(注释)
6、新的迭代语句(for(int n:numbers))
7、静态导入(import static )
8、新的格式化方法(java.util.Formatter)
formatter.format(“Remaining account balance: $%.2f”, balance);
9、新的线程模型和并发库Thread Framework
HashMap的替代者ConcurrentHashMap和ArrayList的替代者CopyOnWriteArrayList
在大并发量读取时采用java.util.concurrent包里的一些类会让大家满意BlockingQueue、Callable、Executor、Semaphore…
Java 6
1、引入了一个支持脚本引擎的新框架
2、UI的增强
3、对WebService支持的增强(JAX-WS2.0和JAXB2.0)
4、一系列新的安全相关的增强
5、JDBC4.0
6、Compiler API
7、通用的Annotations支持
Java 7
1. switch on String
Before JDK 7, only integral types can be used as selector for switch-case statement. In JDK 7, you can use a String
object as the selector. For example,
String day = "SAT"; switch (day) { case "MON": System.out.println("Monday"); break; case "TUE": System.out.println("Tuesday"); break; case "WED": System.out.println("Wednesday"); break; case "THU": System.out.println("Thursday"); break; case "FRI": System.out.println("Friday"); break; case "SAT": System.out.println("Saturday"); break; case "SUN": System.out.println("Sunday"); break; default: System.out.println("Invalid"); }
String.equals()
method is used in comparison, which is case-sensitive. Java compiler can generate more efficient code than using nested if-then-else statement.
This feature is handy in handling options specified in command-line arguments, which are String
s. For example (slightly neater code than using nested if-then-else statement),
// This program accepts three command-line options // -c : create // -v : verbose // -d : debug // More than one options can be specified in any order. public class SwitchOnString { public static void main(String[] args) { boolean create = false; boolean verbose = false; boolean debug = false; for (String arg: args) { switch (arg) { case "-c": create = true; break; case "-v": verbose = true; break; case "-d": debug = true; break; default: System.out.println("invalid option"); System.exit(1); } } System.out.println("create: " + create); System.out.println("verbose: " + verbose); System.out.println("debug: " + debug); } }
2. Binary Literals with prefix "0b
"
In JDK 7, you can express literal values in binary with prefix '0b
' (or '0B
') for integral types (byte
, short
, int
and long
), similar to C/C++ language. Before JDK 7, you can only use octal values (with prefix '0
') or hexadecimal values (with prefix '0x
' or '0X
').
You are also permitted to use underscore (_
) to break the digits to improve the readability but you must start and end with a digit, e.g.,
int number1 = 0b01010000101000101101000010100010;
int number2 = 0b0101_0000_1010_0010_1101_0000_1010_0010;
int number3 = 2_123_456; // break the digits with underscore
For example,
public class BinaryLiteralTest { public static void main(String[] args) { // Some 32-bit 'int' literal values int anInt1 = 0b0101_0000_1010_0010_1101_0000_1010_0010; int anInt2 = 0b0011_1000; // An 8-bit 'byte' literal value. By default, literal values are 'int'. // Need to cast to 'byte' byte aByte = (byte)0b0110_1101; // A 16-bit 'short' literal value short aShort = (short)0b0111_0101_0000_0101; // A 64-bit 'long' literal value. Long literals requires suffix "L". long aLong = 0b1000_0101_0001_0110_1000_0101_0000_1010_0010_1101_0100_0101_1010_0001_0100_0101L; // Formatted output: "%d" for integer in decimal, "%x" in hexadecimal, "%o" in octal. // Take note that "%b" prints true or false (for null), NOT binary. System.out.printf("%d(%x)(%o)(%b)\n", anInt1, anInt1, anInt1, anInt1); System.out.printf("%d(%x)(%o)(%b)\n", aByte, aByte, aByte, aByte); } }
1352847522(50a2d0a2)(12050550242)(true) 109(6d)(155)(true)
3. Underscore for Numeric Literals
In JDK 7, you could insert underscore(s) '_' in between the digits in an numeric literals (integral and floating-point literals) to improve readability. For example,
int anInt = 0b10101000_01010001_01101000_01010001; double aDouble = 3.1415_9265; float aFloat = 3.14_15_92_65f;
4. Catching Multiple Exception Types
In JDK 7, a single catch
block can handle more than one exception types.
For example, before JDK 7, you need two catch
blocks to catch two exception types although both perform identical task:
try { ...... } catch(ClassNotFoundException ex) { ex.printStackTrace(); } catch(SQLException ex) { ex.printStackTrace(); }
In JDK 7, you could use one single catch
block, with exception types separated by '|
'.
try { ...... } catch(ClassNotFoundException|SQLException ex) { ex.printStackTrace(); }
5. The try-with-resources construct
Before JDK 7, we need to use a try-catch-finally
construct to manage resources. We need a finally
block, to ensure that a resource is properly closed regardless of whether the try
statement completes normally or abruptly. The code is messy! For example,
import java.io.FileReader; import java.io.FileWriter; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; // Copy from one file to another file line by line. // Pre-JDK 7 requires you to close the resources using a finally block. public class FileCopyPreJava7 { public static void main(String[] args) { BufferedReader src = null; BufferedWriter dest = null; try { src = new BufferedReader(new FileReader("in.txt")); dest = new BufferedWriter(new FileWriter("out.txt")); String line; while ((line = src.readLine()) != null) { System.out.println(line); dest.write(line); dest.newLine(); // write a newline } } catch (IOException ex) { ex.printStackTrace(); } finally { // always close the streams try { if (src != null) src.close(); // close() throw IOException if (dest != null) dest.close(); } catch (IOException ex) { ex.printStackTrace(); } } try { src.read(); // Trigger IOException: Stream closed } catch (IOException ex) { ex.printStackTrace(); } } }
JDK 7 introduces a try
-with-resources statement, which ensures that each of the resources in try(resources)
is properly closed at the end of the statement if the resource implements interface java.lang.AutoCloseable. This results in cleaner codes.
import java.io.FileReader; import java.io.FileWriter; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; // Copy from one file to another file line by line. // JDK 7 has a try-with-resources statement, which ensures that // each resource opened in try(resources) is closed at the end of the statement. public class FileCopyJava7 { public static void main(String[] args) { try (BufferedReader src = new BufferedReader(new FileReader("in.txt")); BufferedWriter dest = new BufferedWriter(new FileWriter("out.txt"))) { String line; while ((line = src.readLine()) != null) { System.out.println(line); dest.write(line); dest.newLine(); } } catch (IOException ex) { ex.printStackTrace(); } // src and dest automatically close. // No need for finally to explicitly close the resources. } }
6. Type Inference for Generic Instance Creation
import java.util.*; public class Java7GenericTest { public static void main(String[] args) { // Pre-JDK 7 List<String> lst1 = new ArrayList<String>(); // JDK 7 supports limited type inference for generic instance creation List<String> lst2 = new ArrayList<>(); lst1.add("Mon"); lst1.add("Tue"); lst2.add("Wed"); lst2.add("Thu"); for (String item: lst1) { System.out.println(item); } for (String item: lst2) { System.out.println(item); } } }
Java 8
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:
- Lambda Expressions
- Stream API
- Date/Time API (
java.time
)
1. Functional Programming
1.1 Object-Oriented Programming vs Functional Programming
References
- 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:
- We first define class(es).
- We then create instances of classes.
- 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:
- The return value depends only on the input, i.e., the same input always produces the same return value.
- There is no side effect.
- 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.
1.2 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.
2. 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:
public
abstract
methods: Methods without implementation or body. The implementation subclasses must override theseabstract
methods by providing implementation body.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 andpublic
static
method are non-abstract
, with an implementation (method body). - Adding a
public
default
method or apublic
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 apublic
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 andprivate
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:
public
static
(class)final
fields or constants.public
abstract
(instance) methods WITHOUT implementation - MUST be overridden by the implementation subclasses.public
default
(instance) method with implementation - inherited by the implementation subclasses; MAY be overridden but NOT necessarily.public
static
(class) method with implementation - NOT inherited by its sub-types (unlike superclass'static
methods).- (JDK 9)
private
(instance) method with implementation - NOT inherited by its sub-types; CANNOT be invoked by otherstatic
(class) methods within the interface. - (JDK 9)
private
static
(class) method with implementation - NOT inherited by its sub-types; CAN be invoked by otherstatic
(class) methods within the interface.
2.1 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
".
2.2 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.
2.3 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
.
2.4 Interface vs. Abstract Superclass
- Variables: Interface can contain only constants (
public static final
variables). - Method Access Control: All methods (
abstract
,static
anddefault
) in interface arepublic
. JDK 9 supportsprivate
andprivate
static
methods.
3. Lambda Expressions, Functional Interfaces and Collections
Reference:
- Java Tutorial "Lambda Expression" @ http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html.
- "Java SE 8: Lambda Quick Start" @ http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html.
- Java Tutorial "Collections" @ http://docs.oracle.com/javase/tutorial/collections/.
- 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.
3.1 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.
3.2 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
- 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.
- 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"));
3.3 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.
3.4 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
3.5 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.
3.6 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
- For the first
sort()
, we use an anonymous inner class to construct an instance ofComparator
; whereas for the secondsort()
, we replaced by a one-liner lambda expression to construct an instance ofComparator
.
3.7 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:
- Loop through the entire list,
- Filter with a certain criteria (e.g.,
age >= 21
), - 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:
- A source: a collection or an array, e.g.,
pList
(which is aList
ofPerson
objects) in the above examples. stream()
: produce aStream
, which is a sequence of elements to be carried from the source into the pipeline.- Some intermediate operations: for example,
filter(Predicate)
, which creates a newStream
consisting of elements that matches thePredicate
. - 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 asequential
Stream
with thisCollection
as its source.aStream
.filter(aPredicate)
: Filter thisSteam
with the givenjava.util.function.Predicate
object.Stream<T> filter(Predicate<? super T> predicate)
aStream.forEach(aConsumer)
: Run the operation in thejava.util.function.Consumer
object.void forEach(Consumer<? super T> action)
aStream.map(aFunction)
: Apply the mapping function (transformation) in the givenjava.util.function.Function
object to map fromT
toR
.<R> Stream<R> map(Function<? super T, ? extends R> mapper)
aStream.mapToInt(anToIntFunction)
: A type specialization ofmap()
that returns anIntStream
(a primitive type specialization ofStream
).IntStream mapToInt(ToIntFunction<? super T> mapper)
anIntStream
.average().getAsDouble()
: Compute the average for thisIntStream
.OptionalDouble average() // Returns an OptionalDouble for the average of this stream, // or an empty OptionalDouble if this stream is empty.
The result is ajava.util.OptionDouble
object, which is a container may or may not contain adouble
value. If a value is present,isPresent()
will returntrue
andgetAsDouble()
will return the value.anIntStream
.sum()
: Compute the sum of thisIntStream
and return theint
sum.int sum() // Returns the sum of elements in this stream as int
3.8 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:
abstract
boolean
methodtest()
to implement predicate test.default
methodsand()
,or()
andnegate()
for logical AND, OR and negate withtest()
.static
methodisEqual()
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); }
3.9 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]
3.10 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:
- Reduced lines of codes.
- Support parallel execution.
- Passing of behavior (in Functional Interface instance) as function's argument.
4. 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 zoneZonedDateTime
: with time zoneInstant
: machine readable time representationPeriod
,Duration
:- Clock:
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
, andZoneId
orZoneOffset
).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:
- Human Time: in terms of year, month, day, hour, minute and second.
- Machine Time: in nanoseconds or days from an origin called epoch (1970-01-01T00:00:00Z)).
4.1 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 toset
, 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.
4.2 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.
5. Collection API Improvements
5.1 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()
.
5.2 Others
- Interface
java.lang.Iterable
(supertype ofjava.util.Collection
)'sdefault
methodforEach()
, as discussed before. - Interface
java.util.Iterator
'sdefault
methodforEachRemaining(Consumer<? super E> action)
to perform theaction
for each of the remaining elements. - Interface
java.util.Collection
'sdefault
methodremoveIf(Predicate<? super E> filter)
to remove all the elements of this collection that satisfy the given predicate. This complements the existingabstract
methodsremove(Object o)
andremoveAll(Collection<?> c)
. - Interface
java.util.Collection
'sdefault
methodstream()
andparallelStream()
to create a sequential and parallel stream; andspliterator()
to create aSpliterator
which supports parallel as well as sequential operations. - Interface
java.util.Map
'sdefault
methods:compute()
,merge()
,remove()
,replace()
, and etc. - Interface
java.uitl.Comparator
'sdefault
methods: ... - more.
6. IO Improvements
[TODO]
7. Concurrency API Improvements
[TODO]
8. Miscellaneous Core API Changes
8.1 min(), max(), sum() in Integer, Long and Double Wrapper Classes
[TODO]
8.2 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.
8.3 logicalAnd(), logicalOr(), logicalXor() in Boolean Wrapper Class
[TODO]
8.4 More utiltiy methods in java.lang.Math class
int addExact(int x, int y)
: throwing an exception if the result overflows anint
.long addExact(long x, long y)
: throwing an exception if the result overflows anlong
.subtractExact()
,multiplyExact()
,incrementExact()
,decrementExact()
,negateExact()
: forint
andlong
.floorDiv()
,floorMod()
: forint
andlong
.int toIntExact(long value)
:nextDown(double)
,nextDown(float)
:
[TODO] Examples
8.5 Others
jjs
command is added to invoke Nashorn JavaScript Engine.jdeps
command is added to analyze class files.- JDBC-ODBC Bridge has been removed.