A little dry goods | Jdk1.8 new features combat articles (41 cases)

Posted May 26, 202021 min read

Author:little brother Fu
Blog: https://bugstack.cn - Original series of high-quality feature articles`

Precipitation, sharing, growth, let yourself and others gain something!

Foreword

I have always wanted to sort out the new features of jdk1.8, and I just saw the foreigner's git(there is a link after the text). On this structure, I continued to improve the description and functions. I made 41 unit test cases to facilitate newcomers to learn. The following content is very dry, for a cute new white, learn the new features of jdk1.8, basically read it once to know a 7788, read it twice and finally write it again, then it can be used in actual projects . but! New features, although good. But if you want to use it, then you must look at the corresponding source code and practice more, otherwise it is really easy to confuse yourself, and it is difficult to read.

Zero, review an abstract class

Before jdk1.8, because the interface can only do method definitions and no method implementation, we usually implement the default method in the abstract class. In general, this default method is a public method after abstraction, and does not need every inherit All of them are implemented, just call it. Like below

At the time of definition;

public abstract class AFormula {

    abstract double calculate(int a);

    //square
    double sqrt(int a) {
        return Math.sqrt(a);
    }

}

When in use;

@Test
public void test_00() {
    AFormula aFormula = new AFormula() {
        @Override
        double calculate(int a) {
            return a * a;
        }
    };
    System.out.println(aFormula.calculate(2)); //Squaring:4
    System.out.println(aFormula.sqrt(2)); //Find the prescription:1.4142135623730951
}

First, provide the default method implementation in the interface(a bit like an abstract class)

In jdk1.8, not only can the interface be defined, but also a default implementation can be provided in the interface. This small change changed the entire abstract design!

At the time of definition; {default keyword must}

public interface IFormula {

    double calculate(int a);

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

}

When in use(1);

@Test
public void test_01() {
    IFormula formula = new IFormula() {
        @Override
        public double calculate(int a) {
            return a * a;
        }
    };
    System.out.println(formula.calculate(2));
    System.out.println(formula.sqrt(2));
}

When it is used(2); if it is only used in one way, then it is not very interesting. I've said it all the time; good code is ridiculous!

  1. a; a is a name of the input parameter, any other name

  2. -> a \ * a; the arrow points to the specific implementation

  3. However, this is actually not suitable for adding logs

    @Test
    public void test_02() {

     //Enter parameter a and implement
     IFormula formula = a-> a * a;
     System.out.println(formula.calculate(2));
     System.out.println(formula.sqrt(2));

    }

Two, Lambda expression

Because there can be added default method implementation in the interface, then Java must be because of such a design to simplify development. So you will see the default method implementation in each of our previous List, Set, etc. all interfaces.

Start with a familiar sorting sequence

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

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

The Collections tool class provides a static method, the sort method. The input parameter is a List collection, and a Comparator to sort the given List collection. The above sample code creates an anonymous inner class as an input parameter. This kind of similar operation can be seen everywhere in our daily work.

This writing is no longer recommended in Java 8, but Lambda expression is recommended:

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

The code block of the same function above is shorter and much cleaner. Like mother-in-law, she may not be used to it at first, but she likes it. Because it can be more short and excellent;

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

In order to pursue the ultimate, we can also make it shorter:{Of course, your implementation is not a line of code, then you ca n t do this}

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

The java.util.List collection has now added the sort method. And the Java compiler can determine the parameter type according to the type inference mechanism, so that you can omit the type of the parameter.

java.util.List.sort

default void sort(Comparator <? super E> c) {
    Object []a = this.toArray();
    Arrays.sort(a,(Comparator) c);
    ListIterator <E> i = this.listIterator();
    for(Object e:a) {
        i.next();
        i.set((E) e);
    }
}

All right! Do you think this is over, no! It can also be shorter!(Thanks to the stack default method provided in the Comparator interface, that is to say, not only the default default implementation, but also static methods in the interface)

names.sort(Comparator.reverseOrder());
  1. Functional Interfaces

How does lambda expressions fit into Java's type system? Each lambda corresponds to a given type, specified by an interface. A so called functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method . Since default methods are not abstract you're free to add default methods to your functional interface.

Through the above example, we can see that the logic of the same function can be developed through Lambda, but the code is very simple. So how does Jvm perform type inference and find the corresponding method?

Through the introduction of the official text and our discovery, not every interface can be abbreviated as the development method of Lambda expressions. In fact, only those functional interfaces can be abbreviated as Lambda expressions.

The so-called functional interface(Functional Interface) is a statement that contains only one abstract method. All Lambda expressions for this interface type will match this abstract method. {In addition, just adding default to the interface is not an abstract method}

Summary:In order to ensure that an interface is clearly defined as a functional interface(Functional Interface), we need to add annotations to the interface:@FunctionalInterface. In this way, once you add the second abstract method, the compiler will immediately throw an error message. {Do not fill in, but just write a default}

Define the interface with annotation @FunctionalInterface

@FunctionalInterface
public interface IConverter <F, T> {

    T convert(F from);

}
  1. Let's start with a traditional way & easy to understand because I am used to it

    IConverter <String, Integer> converter01 = new IConverter <String, Integer>() {
    @Override
    public Integer convert(String from) {

     return Integer.valueOf(from);

    }

  2. Slightly simplified, make up &(form), only one parameter bracket can be

    IConverter <String, Integer> converter02 =(from)-> {

     return Integer.valueOf(from);

    };

  3. Continue to simplify, because his implementation has only one line of code, which can be shorter

    IConverter <String, Integer> converter03 = from-> Integer.valueOf(from);

  4. Can be shorter, in fact, this alternative belongs to the next paragraph, let me have an impression first

    IConverter <Integer, String> converter04 = String ::valueOf;

  5. Convenient references to methods and constructors


In the above, we first added the impression fragment XX ::xx, which is also a convenient reference of the new features of Java8. These four points may be seen in other languages.

IConverter <Integer, String> converter04 = String ::valueOf;
String converted04 = converter04.convert(11);
System.out.println(converted04);

These four points:The keywords, not only can refer to methods and constructors, but also can refer to ordinary methods.

public class Something {
    public String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}

IConverter <String, String> converter01 = s-> String.valueOf(s.charAt(0)); //[reference]put the logic directly here
IConverter <String, String> converter02 = something ::startsWith; //The logic of the referenced method body can be more, otherwise just a sentence of code is not suitable for all situations
System.out.println(converter01.convert("Java"));
System.out.println(converter02.convert("Java"));

Next, we use these four points to see how to refer to the class constructor. First we create such a class;

public class Person {
    String firstName;
    String lastName;

    Person() {}

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

Then I also need to top a factory class for generating Person objects;

@FunctionalInterface
public interface IPersonFactory <P extends Person> {

    P create(String firstName, String lastName);

}

Now it's time to use four loaves ::

IPersonFactory <Person> personFactory = Person ::new; //[reference object]:(firstName, lastName)-> new Person(firstName, lastName);
Person person = personFactory.create("Peter", "Parker");

Reminder; there can only be one function in the factory function, otherwise an error will be reported

Four cakes :::Let us directly refer to the constructor of the Person class, and then the Java compiler can select the correct constructor according to the class signature to implement the PersonFactory.create method.

  1. The scope of Lambda

Accessing outer scope variables from lambda expressions is very similar to anonymous objects. You can access final variables from the local outer scope as well as instance fields and static variables.

Lambda expressions access external variables(local variables, member variables, static variables, the default method of the interface), which is very similar to anonymous internal classes accessing external variables.

1 . Access to local variables

We can read the final local variable num from the outer scope of the lambda expression;

int num = 1;
IConverter <Integer, String> stringConverter = from-> String.valueOf(from + num);
String convert = stringConverter.convert(2);
System.out.println(convert); //3

But this num is an immutable value, so changing the value will report an error;

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

Variable used in lambda expression should be final or effectively final

In addition, modification within the lambda expression is also not allowed;

int num = 1;
IConverter <Integer, String> converter =(from)-> {
    String value = String.valueOf(from + num);
    num = 3;
    return value;
};

Variable used in lambda expression should be final or effectively final

2 . Access to member variables and static variables

Access local variables in Lambda expressions. Compared with local variables, member variables and static variables have read and write permissions in Lambda expressions:

public class Lambda4 {

    //static variable
    static int outerStaticNum;
    //Member variables
    int outerNum;

    void testScopes() {
        IConverter <Integer, String> stringConverter1 =(from)-> {
            //Assign values   to member variables
            outerNum = 23;
            return String.valueOf(from);
        };

        IConverter <Integer, String> stringConverter2 =(from)-> {
            //Assign values   to static variables
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }

}

3 . Access to the default interface method

Remember the IFormula example from the first section?

public interface IFormula {

    double calculate(int a);

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

}

At that time, we defined a sqrt square root method with a default implementation in the interface. We can easily access this method in the anonymous inner class:

IFormula formula = new IFormula() {
    @Override
    public double calculate(int a) {
        return a * a;
    }
};

But the default method cannot be accessed through lambda expressions, such code cannot be compiled;

IFormula formula =(a)-> sqrt(a * a);

Interface methods with default implementations cannot be accessed in lambda expressions. The above code will not be compiled.

Five, built-in functional interface

The JDK 1.8 API includes many built-in functional interfaces. This includes the Comparator and Runnable that we often see in old versions. Java 8 adds @FunctionalInterface annotation to them to support Lambda expressions.

For example, in addition to the Comparator and Runnable that are commonly used in our old version of Jdk, there are some new functional interfaces that can support Lamdba support through function annotations. Many of them are borrowed from the well-known Google Guava library.

Even if you are already familiar with this class library, you should pay close attention to how those interfaces are extended by some useful methods:

1 . Predicate

Predicate is a functional interface that can specify input types and return boolean values. It provides some methods with a default implementation internally, which can be used to combine a complex logical judgment(and, or, negate):

@Test
public void test11() {
    Predicate <String> predicate =(s)-> s.length()> 0;

    boolean foo0 = predicate.test("foo"); //true
    boolean foo1 = predicate.negate(). test("foo"); //negate negation is equivalent to true!

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

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

2 . Functions

Function The function of the functional interface is that we can provide a raw material for him, and he will produce a final product. Through the default method provided by it, combination, line processing(compose, andThen):

@Test
public void test12() {
    Function <String, Integer> toInteger = Integer ::valueOf; //Turn Integer
    Function <String, String> backToString = toInteger.andThen(String ::valueOf); //Turn String
    Function <String, String> afterToStartsWith = backToString.andThen(new Something() ::startsWith); //Intercept first
    String apply = afterToStartsWith.apply("123"); //"123"
    System.out.println(apply);
}

3 . Suppliers

Supplier is different from Function in that it does not accept input parameters and directly produces a specified result for us, a bit like the producer mode:

@Test
public void test13() {
    Supplier <Person> personSupplier0 = Person ::new;
    personSupplier0.get(); //new Person
    Supplier <String> personSupplier1 = Something ::test01; //This test method is static and has no parameters
    personSupplier1.get(); //hi

    Supplier <String> personSupplier2 = new Something() ::test02;
}

4 . Consumers

For Consumer, we need to provide input parameters for consumption, as shown in the following sample code:

@Test
public void test14() {
    //Reference object, easy to know the following Lamdba expression writing
    Consumer <Person> greeter01 = new Consumer <Person>() {
        @Override
        public void accept(Person p) {
            System.out.println("Hello," + p.firstName);
        }
    };
    Consumer <Person> greeter02 =(p)-> System.out.println("Hello," + p.firstName);
    greeter02.accept(new Person("Luke", "Skywalker")); //Hello, Luke
    Consumer <Person> greeter03 = new MyConsumer <Person>() ::accept; //Can also be called by defining classes and methods, so that is the actual development gesture
    greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}

5 . Comparators

Comparator was widely used before Java 8. In addition to upgrading it to a functional interface in Java 8, it also extends some default methods for it:

@Test
public void test15() {
    Comparator <Person> comparator01 =(p1, p2)-> p1.firstName.compareTo(p2.firstName);
    Comparator <Person> comparator02 = Comparator.comparing(p-> p.firstName); //Same as above
    Person p1 = new Person("John", "Doe");
    Person p2 = new Person("Alice", "Wonderland");
    comparator01.compare(p1, p2); //> 0
    comparator02.reversed(). compare(p1, p2); //<0
}
  1. Optionals

First of all, Optional is not a functional interface. It is designed to prevent NullPointerException. In Java programming, NullPointerException is notorious.

Let's take a quick look at how Optional is used! You can think of Optional as a container for packaging objects(which may or may not be null). When you define

A method, the object returned by this method may be empty, or it may be non-null, you can consider using Optional to wrap it, which is also recommended in Java 8.

@Test
public void test16() {
    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"
    Optional <Person> optionalPerson = Optional.of(new Person());
    optionalPerson.ifPresent(s-> System.out.println(s.firstName));
}

Seven, Stream

What is a stream?

Simply put, we can use java.util.Stream to perform various operations on a collection that contains one or more elements. These operations may be intermediate operations or terminal operations.
Terminal operations will return a result, while intermediate operations will return a Stream.

It should be noted that you can only do streaming operations on classes that implement the java.util.Collection interface.

Stream supports simultaneous execution and concurrent execution.

Note:Map does not support Stream, but his key and value are supported!

Let us first see how the Stream stream works. First, we create an example in the form of a list of strings;

List <String> stringCollection = new ArrayList <>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

1 . Filter

The input parameter of Filter is a Predicate. As mentioned above, Predicate is an intermediate operation of assertion, which can help us filter out the collection elements we need. Its return parameter is also a Stream, we can operate through the foreach terminal to print the filtered elements:

@Test
public void test17() {
    stringCollection
            .stream()
            .filter((s)-> s.startsWith("a"))
            .forEach(System.out ::println);
}

2 . Sorted

Sorted is also an intermediate operation, and its return parameter is a Stream. In addition, we can pass in a Comparator to customize sorting, if not, the default sorting rules will be used.

@Test
public void test18() {
    stringCollection
            .stream()
            .sorted()
            .filter((s)-> s.startsWith("a"))
            .forEach(System.out ::println);
}

Note; this sorted just makes a sorted view for output, and actually does not sort the data in the List

System.out.println(stringCollection);
//ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

3 . Map conversion

The intermediate operation map transforms each element into another object through the given function. For example, in the following example, we use map to convert each string to uppercase:

@Test
public void test19() {
    stringCollection
            .stream()
            .map(String ::toUpperCase)
            .sorted(Comparator.reverseOrder()) //Equivalent to(a, b)-> b.compareTo(a)
            .forEach(System.out ::println);
}

This can be used for DTO data object conversion. In the field-driven design and development, DTO is converted to DO and transmitted to the background.

4 . Match

As the name suggests, match is used for matching operations, and its return value is a boolean type. Through match, we can easily verify whether a certain type of element exists in a list.

@Test
public void test20() {
    //anyMatch:verify whether the string in the list starts with a, matches the first one, that is, returns true
    boolean anyStartsWithA =
            stringCollection
                    .stream()
                    .anyMatch((s)-> s.startsWith("a"));
    System.out.println(anyStartsWithA); //true
    //allMatch:verify that all strings in list start with a
    boolean allStartsWithA =
            stringCollection
                    .stream()
                    .allMatch((s)-> s.startsWith("a"));
    System.out.println(allStartsWithA); //false
    //noneMatch:verify that none of the strings in list start with z
    boolean noneStartsWithZ =
            stringCollection
                    .stream()
                    .noneMatch((s)-> s.startsWith("z"));
    System.out.println(noneStartsWithZ); //true
}

5 . Count

count is a terminal operation, it can count the total number of elements in the stream, and the return value is of type long.

@Test
public void test21() {
    //count:first filter the string beginning with b in the list, and then count the number
    long startsWithB =
            stringCollection
                    .stream()
                    .filter((s)-> s.startsWith("b"))
                    .count();
    System.out.println(startsWithB); //3
}

6 . Reduce

Reduce Chinese translation:reduce, shrink. By entering the function, we can reduce the list to a value. Its return type is Optional.

@Test
public void test22() {
    Optional <String> reduced =
            stringCollection
                    .stream()
                    .sorted()
                    .reduce((s1, s2)-> s1 + "#" + s2);
    reduced.ifPresent(System.out ::println);
    //aaa1 # aaa2 # bbb1 # bbb2 # bbb3 # ccc # ddd1 # ddd2
}

Eight, Parallel-Streams parallel stream

As mentioned above, the streams can be sequential or parallel. Operations on sequential streams are executed on a single thread, while operations on parallel streams are executed concurrently on multiple threads.

The following example demonstrates how easy it is to use parallel streams to improve performance. Pro-test improves performance by 1 times!

First, we create a larger List:

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

1 . Sequential Sort

@Test
public void test23() {
    int max = 1000000;
    List <String> values   = new ArrayList <>(max);
    for(int i = 0; i <max; i ++) {
        UUID uuid = UUID.randomUUID();
        values.add(uuid.toString());
    }
    //nanoseconds
    long t0 = System.nanoTime();
    long count = values.stream(). sorted(). count();
    System.out.println(count);
    long t1 = System.nanoTime();
    //Nanosecond to microsecond
    long millis = TimeUnit.NANOSECONDS.toMillis(t1-t0);
    System.out.println(String.format("Sequence stream sorting time:%d ms", millis));
    //Sequence flow sorting time:712 ms
}

2 . Parallel Sort Parallel Stream Sort

@Test
public void test24() {
    int max = 1000000;
    List <String> values   = new ArrayList <>(max);
    for(int i = 0; i <max; i ++) {
        UUID uuid = UUID.randomUUID();
        values.add(uuid.toString());
    }
    long t0 = System.nanoTime();
    long count = values.parallelStream(). sorted(). count();
    System.out.println(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:385 ms
}

As you can see, the two code fragments are almost the same, but parallel sorting is about 50%faster. You just need to change stream() to parallelStream().

Nine, Map collection

As mentioned earlier, Map does not support Stream, because the Map interface does not define the stream() method like the Collection interface. However, we can use stream operations on its key, values, entry, such as map.keySet(). Stream(), map.values (). Stream() and map.entrySet(). Stream().

In addition, JDK 8 provides some other new features for map:

@Test
public void test25() {
    Map <Integer, String> map = new HashMap <>();
    for(int i = 0; i <10; i ++) {
        //Unlike the old version, putIfAbent() method does not need to write if null continue before put
        //Will judge whether the key already exists, return value directly if it exists, otherwise put, then return value
        map.putIfAbsent(i, "val" + i);
    }
    //forEach can easily traverse the map
    map.forEach((key, value)-> System.out.println(value));
}

After that we do a conversion output of a Map object;(define two classes BeanA, BeanB)

@Test
public void test26() {
    Map <Integer, BeanA> map = new HashMap <>();
    for(int i = 0; i <10; i ++) {
        //Unlike the old version, putIfAbent() method does not need to write if null continue before put
        //Will judge whether the key already exists, return value directly if it exists, otherwise put, then return value
        map.putIfAbsent(i, new BeanA(i, "  " + i, i + 20, "89021839021830912809" + i));
    }
    Stream <BeanB> beanBStream00 = map.values  (). Stream(). Map(new Function <BeanA, BeanB>() {
        @Override
        public BeanB apply(BeanA beanA) {
            return new BeanB(beanA.getName(), beanA.getAge());
        }
    });
    Stream <BeanB> beanBStream01 = map.values  (). Stream(). Map(beanA-> new BeanB(beanA.getName(), beanA.getAge()));
    beanBStream01.forEach(System.out ::println);
}

In addition to putIfAbsent() and forEach() above, we can also easily perform operations on the value of a key:

@Test
public void test27() {
    //As follows:For the value of key 3, the internal will first determine whether the value exists, and then do the stitching operation of value + key
    map.computeIfPresent(3,(num, val)-> val + num);
    map.get(3); //val33

    //First determine whether the element with key 9 exists, and if so, delete it
    map.computeIfPresent(9,(num, val)-> null);
    map.containsKey(9); //false

    //computeIfAbsent(), when the key does not exist, the relevant processing will be done
    //as follows:first determine whether the element with key 23 exists or not, then add
    map.computeIfAbsent(23, num-> "val" + num);
    map.containsKey(23); //true

    //First determine whether the element with key 3 exists, if it exists, then do nothing
    map.computeIfAbsent(3, num-> "bam");
    map.get(3); //val33
}

Regarding the delete operation, JDK 8 provides a new remove() API:

@Test
public void test28() {
    map.remove(3, "val3");
    map.get(3); //val33

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

As shown in the above code, the delete operation will only be performed when the given key and value match exactly.

Regarding adding methods, the getOrDefault() method with default values is provided in JDK 8:

@Test
public void test29() {
    //If key 42 does not exist, it returns not found
    map.getOrDefault(42, "not found"); //not found
}

The merge operation for value also becomes simpler:

@Test
public void test30() {
    //The merge method will first determine whether the key to be merged exists or not, and then add the element
    map.merge(9, "val9",(value, newValue)-> value.concat(newValue));
    map.get(9); //val9
    //If the element of key exists, perform the splicing operation on value
    map.merge(9, "concat",(value, newValue)-> value.concat(newValue));
    map.get(9); //val9concat
}

Ten, date Date API

Java 8 added a new date API under the package java.time. It is similar to the Joda-Time library, but it is not exactly the same. Next, I will introduce the most critical features of the new API through some sample code:

1 . Clock

Clock provides access to the current date and time. We can use it to replace the System.currentTimeMillis() method. In addition, you can get an instant instance through clock.instant(),
This instance can be easily converted into the java.util.Date object in the old version.

@Test
public void test31() {
    Clock clock = Clock.systemDefaultZone();
    long millis = clock.millis();
    Instant instant = clock.instant();
    Date legacyDate = Date.from(instant); //old version java.util.Date
}

2 . Timezones

ZoneId represents the time zone class. It can be easily obtained through the static factory method, and we can pass in a time zone code for the input parameter. In addition, the time zone class also defines an offset to convert between the current time or a certain time and the target time zone time.

@Test
public void test32() {
    System.out.println(ZoneId.getAvailableZoneIds());
    //prints all available timezone ids

    ZoneId zone1 = ZoneId.of("Europe/Berlin");
    ZoneId zone2 = ZoneId.of("Brazil/East");
    System.out.println(zone1.getRules());
    System.out.println(zone2.getRules());

    //[Asia/Aden, America/Cuiaba, Etc/GMT + 9, Etc/Gada/Atlantic, Atlantic/St_Helena, Australia/Tasmania, Libya, Europe/Guernsey, America/Grand_Turk, US/Pacific-New, Asia/Samarkand , America/Argentina/Cordoba, Asia/Phnom_Penh, Africa/Kigali, Asia/Almaty, US/Alaska, Asi ...
    //ZoneRules [currentStandardOffset = + 01:00]
    //ZoneRules [currentStandardOffset = -03:00]
}

3 . LocalTime

LocalTime represents a time class without a specified time zone, for example, 10 p.m. or 17:30:15. In the following sample code, two LocalTimes will be created using the time zone object created above. Then we will compare the two times and calculate the difference in hours and minutes between them.

@Test
public void test33() {
    ZoneId zone1 = ZoneId.of("Europe/Berlin");
    ZoneId zone2 = ZoneId.of("Brazil/East");
    LocalTime now1 = LocalTime.now(zone1);
    LocalTime now2 = LocalTime.now(zone2);
    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 provides multiple static factory methods, the purpose is to simplify the creation and operation of time object instances, including the operation of parsing time strings.

@Test
public void test34() {
    LocalTime late = LocalTime.of(23, 59, 59);
    System.out.println(late); //23:59:59
    DateTimeFormatter germanFormatter =
            DateTimeFormatter
                    .ofLocalizedTime(FormatStyle.SHORT)
                    .withLocale(Locale.GERMAN);
    LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
    System.out.println(leetTime); //13:37
}

4 . LocalDate

LocalDate is a date object, for example:2014-03-11. It is a final type object like LocalTime. The following example demonstrates how to calculate a new date by adding and subtracting days, months, and years.

@Test
public void test35() {
    LocalDate today = LocalDate.now();
    //Add one day today
    LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
    //Two days tomorrow
    LocalDate yesterday = tomorrow.minusDays(2);
    //The fourth day of July 2014
    LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
    DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
    System.out.println(dayOfWeek); //Friday
}

You can also parse the date string directly to generate a LocalDate instance.(As simple as LocalTime operation)

@Test
public void test36() {
    DateTimeFormatter germanFormatter =
            DateTimeFormatter
                    .ofLocalizedDate(FormatStyle.MEDIUM)
                    .withLocale(Locale.GERMAN);
    LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
    System.out.println(xmas); //2014-12-24
}

5 . LocalDateTime

LocalDateTime is a date-time object. You can also think of it as a combination of LocalDate and LocalTime. In operation, it is also roughly the same.

@Test
public void test37() {
    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
    //Get the change time is the first few minutes of the day
    long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
    System.out.println(minuteOfDay); //1439
}

If you add the time zone information, LocalDateTime can also be converted into an Instance instance. Instance can be converted into java.util.Date object in the old version.

@Test
public void test38() {
    LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
    Instant instant = sylvester
            .atZone(ZoneId.systemDefault())
            .toInstant();
    Date legacyDate = Date.from(instant);
    System.out.println(legacyDate); //Wed Dec 31 23:59:59 CET 2014
}

Formatting LocalDateTime objects is the same as formatting LocalDate or LocalTime. In addition to using a predefined format, you can also customize the formatted output.

@Test
public void test39() {
    DateTimeFormatter formatter =
            DateTimeFormatter
                    .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
}

Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.

For details on the pattern syntax read here .

  1. Annotations

The comments in Java 8 are repeatable. Let's dive directly into an example to solve this problem. {You can see this type of annotation in the SpringBoot startup class}

First, we define a wrapper comment, which contains an array of actual comments:

@Repeatable(Hints.class)
public @interface Hint {
    String value();
}

public @interface Hints {
    Hint []value();
}

Java 8 enables us to use multiple annotations of the same type by declaring the annotation @Repeatable.

The first form:using annotation containers(old method)

 @Test
 public void test40() {
     @Hints({@ Hint("hint1"), @Hint("hint2")})
     class Person {
     }
 }

The second form:using repeatable annotations(new method)

@Test
public void test41() {
    @Hint("hint1")
    @Hint("hint2")
    class Person {
    }
}

The java compiler uses variable 2 to implicitly set the @Hints annotation under the hood. This is important for reading annotation information through reflection.

@Test
public void test41() {
    @Hint("hint1")
    @Hint("hint2")
    class Person {
    }
    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
}

      Person      @Hints                  getAnnotation(Hints.class)        

getAnnotationsByType @Hints

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

Related Posts