Java has not declined. Everyone's understanding of it is just beginning a new start of Java 8

Keywords: Java Lambda Attribute JDK

Java has not declined. People's understanding of it is just beginning

I'm glad to share the new features of Java 8 here. This article will take you step by step through all the new features of Java 8. I will show you the default method, lambda expression, method reference and repeated annotation in the interface through simple example code. After reading this article, you will understand the new changes in flow, function, interface, map extension and date. No nonsense, put the code here!

Default methods for interfaces

Java 8 allows us to add a non abstract method modified by the default keyword in the interface. This feature is called the extension method. Here is an example:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);

In the interface Formula, in addition to the abstract method calculation, the non abstract method sqrt is also defined. The implementation class inherits the abstract method calculation. The default method can be called directly

Formula formula = new Formula() {
    public double calculate(int a) {
        return sqrt(a * 100);

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

Formula is an anonymous object inheriting the formula interface. The example code is very detailed: only 6 lines of code realize a simple sqrt(a * 100) calculation. In the next chapter, we will introduce a more perfect scheme to realize the use of single method object in Java 8

Lambda expressions

The following example shows you how to sort the strings in the List in the previous version:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    public int compare(String a, String b) {
        return b.compareTo(a);

Through a static Collections.sort You often find yourself creating an anonymous Comparator class and sorting through it

In order to get rid of the way of creating anonymous objects to achieve sorting, Java 8 brings a more concise way to achieve it. lambda expression:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);

You will find that the code is more concise and readable. Even it can be more concise:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

You can directly omit the {} and return keywords for a single line method body. You can even make it shorter:

Collections.sort(names, (a, b) -> b.compareTo(a));

The java compiler can automatically recognize parameter types, so you can omit them. Let's have a deeper understanding of the broader application of lambda expressions in Java

Functional interfaces

How do lambda expressions match Java types? Each lambda expression is equivalent to specifying the type of an interface. A function interface that must define a method to draw a line. Each lambda expression type will match this abstract method. Because the default method is not an abstract method, you can freely add the default method to your function interface

We can use any interface as a lambda expression, which only needs to contain an abstract method. In order to ensure that your interface meets the requirements, you need to add a @ functional interface annotation to your interface. When you use this annotation, this interface can only define an abstract method, or the compiler will report an error

interface Converter<F, T> {
    T convert(F from);
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

Note that @ functional interface is also valid code

Method and constructor references

The above code can be applied in a static way to make it more concise:

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

Java 8 can pass a method or constructor reference through:: keyword. The above example shows you how to reference a static method. We can also reference a common object method:

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

Let's see how:: refers to constructors. First, we define two constructors in a javabean:

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;

Next, we create a factory interface to create a Person object:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);

To implement the factory interface manually, we associate everything with the constructor:

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

We use Person::new to create a reference to the Person constructor. The Java compiler automatically creates an object through PersonFactory.create Parameters match the appropriate constructor

Lambda scopes

Accessing local variables from lambda expressions is similar to anonymous objects. You can access final decorated local variables and instantiated local properties and static variables

Accessing local variables

lambda expression can read a final decorated local variable from the local:

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

Unlike anonymous objects, the variable num does not need to be decorated with final in anonymous objects. It can also be called normally:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

However, num must implicitly use final decoration to compile. The following code will not compile:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;

Writing num into a lambda expression is also prohibited

Accessing fields and static variables

Compared with local variables, we can read and write a global attribute and static variable in lambda expression. This is the same as in anonymous object

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);

Accessing default interface methods

Remember the first instance of formula? Interface formula defines a default method, sqrt, which can be accessed by every formula instance including anonymous objects. However, it is not suitable for lambda expressions

lambda expressions cannot access the default method of the interface. The following code cannot be compiled:

Formula formula = (a) -> sqrt( a * 100);

Built in functional interfaces

The DK 1.8 API contains many built-in functions, many of which are familiar in the old versions, such as Comparator and Runnable. These interfaces support Lambda expressions by adding @ functional interface annotation

But Java 8 API also adds many new interfaces to make programming easier. Many of these new functions are based on the well-known Google Guava function library. Even if you are familiar with these function libraries, you need to pay attention to how these methods are extended in the interface and how they are used

Predictions (judgments)
Judgment is to pass a parameter and return a Boolean value. This interface contains various default methods to form a complex logical judgment unit (and, or, negate)

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();


The function takes a parameter and returns a result. The default method can be combined, and then

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"

Suppliers (producers)

Suppliers returns the result of a given generic type. Unlike functions, suppliers do not need to pass parameters

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

Consumers represents an operation performed on an input parameter

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Comparators (comparison)
Comparators are familiar in the old version. Java 8 adds several default methods for this interface

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

Optionals is not a function interface, but its function is to avoid null pointer exception. It is a very important concept for us to understand it quickly

Optional is a simple container that contains empty or non empty values. Imagine if a method can return an empty or non empty value. In Java 8, you can replace empty by returning an optional value

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

java.util.Stream Represents a sequence of elements on which one or more operations can be performed. Stream operations can include operations on the middle part or the terminal part. Terminal operations can return a certain type, and intermediate operations can return the flow itself. You can call a line by multiple methods. Streams can be created by a source, For example. Like java.util.Collection Either lists or sets (not supported by maps) in can create streams. Streams can operate in serial or parallel

First, let's see how to perform sequence operations. First, we create a source through a List containing strings:

List<String> stringCollection = new ArrayList<>();

In Java 8, Collections already inherit streams, so you can call it in Java 8 are extended so you can simply create and Collection.parallelStream() to create a stream. I'll show you the operation of most streams

Filter receives a judgment to filter all elements in the flow. This operation is an intermediate operation, which enables us to perform another flow operation (ForEach) on the result. ForEach accepts a consumer operation for each filtered flow element. ForEach is a terminal operation. It returns the value void, so we cannot call another function operation

    .filter((s) -> s.startsWith("a"))

// "aaa2", "aaa1"

Sorted is an intermediate operation that can return a sorted stream. This element is sorted by nature unless you pass a Comparator

    .filter((s) -> s.startsWith("a"))

// "aaa1", "aaa2"

Note that sorted is just to create a sorted view operation, and there is no operation to sort the returned collection. The sorted stringCollection is not affected:

// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map (map)
Intermediate operation map converts each element to another element through the given function. The following example is to convert each string to uppercase. You can also use map to convert the type of each object. Generic flow depends on the generic function you pass to map

    .sorted((a, b) -> b.compareTo(a))

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Match (match)
Various matching operations can be used to determine whether a flow is deterministic. All of these operations are terminal operations and return a Boolean value

boolean anyStartsWithA =
        .anyMatch((s) -> s.startsWith("a"));

System.out.println(anyStartsWithA);      // true

boolean allStartsWithA =
        .allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ =
        .noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ);      // true

Count (Statistics)
Count is the number of elements in a terminal operation return stream

long startsWithB =
        .filter((s) -> s.startsWith("b"))

System.out.println(startsWithB);    // 3

This terminal operation completes the element merging operation in a stream through the given function. The returned result saves the value through Optional

Optional<String> reduced =
        .reduce((s1, s2) -> s1 + "#" + s2);

// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Parallel Streams(Parallel flow)

As mentioned above, overflow can be serial or parallel. Serial sequential operation is single thread, while parallel flow operates on multiple threads

The following example shows you how to increase performance through parallel flow

First, create a large List with unique element Occurrences:

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();

Now test the time it takes to sort

Sequential Sort(Serial sort)
long t0 = System.nanoTime();

long count =;

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// sequential sort took: 899 ms
Parallel Sort(Parallel sorting)
long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// parallel sort took: 472 ms

You can see that the above code is basically the same, but the parallelism is about 50% faster. You can test to use the parallelStream() operation instead of stream()

Map (map)
As mentioned above, map does not support stream operations. However, the new map supports various new methods and common task operations

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);

map.forEach((id, val) -> System.out.println(val));

The above code is simple and easy to understand: putIfAbsent method tests the traditional non empty; forEach method traverses every value in the map for consumer operation

This example shows how to use functions to operate on a map:

map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false

map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true

map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33

Next, we learn how to give a key and how to remove the object. Now we add a key value pair:

map.remove(3, "val3");
map.get(3);             // val33

map.remove(3, "val33");
map.get(3);             // null

Another useful approach:

map.getOrDefault(42, "not found");  // not found

Merging objects in a map is easy:

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat

If the key value pair does not exist, the merge operation can be used to change the existing value

Date API

Java 8 includes a new date and time operation API java.time In the package. The new Date API is comparable to joda time package, but they are not the same. Here is an example of the main new API in the new date


The clock provides a way to access the current date and time. The clock means that the time zone will probably be replaced System.currentTimeMillis() to get the current millisecond. Such a instantaneous point is represented by the class Instant on the time line. Instead of the one that can be used java.util.Date Traditional objects

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date

Timezones (time zone)
Time zone is replaced by ZoneId. You can easily access it through a static factory method. Time zone definition makes up for an important transition between instantaneous and local time and date

// prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime(Local time)

The local time represents a time that does not contain a time zone, for example. 10pm or 17:30:15. The following two examples create two local time zones that contain the above defined time zones. Then we compare the differences between the two time and calendar in hours and minutes

LocalTime now1 =;
LocalTime now2 =;

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

LocalTime can easily create new instances from various factory methods, including formatted strings

LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37

Localdate (local date)
The local date represents a clear date, for example. 2014-03-11. It is an unalterable full simulation of LocalTime. This simple example shows how to calculate a new date by adding or subtracting days, months, or years. Note that each operation returns a new instance

LocalDate today =;
LocalDate tomorrow =, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);    // FRIDAY

Parsing LocalDate from a string is as simple as parsing LocalTime:

DateTimeFormatter germanFormatter =

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24

Localdatetime (local datetime)
LocalDateTime represents a date time. It is a combination of time and date in the above example. LocalDateTime is also immutable. Its working principle is the same as that of LocalTime and LocalDate. We can obtain an attribute from date time by using the method

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439

The traditional timezone can be converted into an instant. The instant can also be easily converted into a traditional java.util.Date .

Instant instant = sylvester

Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

Formatting date times is just like formatting dates and times. We can also use predefined formats to create customized formats

DateTimeFormatter formatter =
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13

Not like java.text.NumberFormat The new DateTimeFormatter is immutable and thread safe


Annotations can be reused in Java 8. Let's learn more about them through examples

First, we define a wrapper annotation that contains an actual annotation array:

@interface Hints {
    Hint[] value();

@interface Hint {
    String value();

Java 8 enables us to use the same annotations defined by @ Repeatable annotation

Variant 1: Using container annotations(old school)
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
Variant 2: Use duplicate annotations(new school)
class Person {}

Using the variant 2java compiler will implicitly set an @ Hints annotation. This is very important to read annotation information through reflection

Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);                   // null

Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length);  // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);          // 2

Although we have never defined @ hints annotation on the Person class, it can always get the annotation through getannotation( Hints.class )However, there is a more convenient method getannotations bytype, which can directly access all @ Hint annotations

In addition, using Java8 annotations are two new goals of the extension:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

My Java 8 new feature case ends here. If you want to learn all the new features and classes of JDK 8 API, you can pay attention to the official account: program zero world, which helps you understand all the new classes and some hidden new features in JDK 8. Arrays.parallelSort, StampedLock, completable future, etc

I hope this article can help you, and I hope you can gain some experience after reading. You can also feedback to me through official account: Zero world.

Posted by FFEMTcJ on Fri, 29 May 2020 02:46:52 -0700