From the school to A factory all the way sunshine vicissitudes of life

Please go to www.codercc.com

The release of Java8 is clearly a milestone release for Java developers, with a number of exciting new features that, if taken advantage of, can greatly improve our development efficiency. The functional programming of Java8 can greatly reduce the amount of code and is easy to maintain, and there are some concurrent related features. New features commonly used in development are as follows:

  1. The default and static methods of the interface
  2. Functional interface with lambda expressions
  3. Method references
  4. Stream
  5. Optional
  6. Date/ Time API improvements
  7. Other improvements

1. Default and static methods of the interface

Prior to Java8, interfaces could only contain abstract methods. So what’s the downside? For example, adding a spliterator abstract method to the Collection interface means that all previous implementation classes that implement the Collection interface will have to re-implement the spliterator method. The default method of interface is to solve the problem of interface modification and interface implementation class incompatibility, as a method of code forward compatibility.

So how do you define a default method in an interface? The spliterator method is defined by Collection in the JDK.

default Spliterator<E> spliterator() {
    return Spliterators.spliterator(this, 0);
}
Copy the code

You can see that the default way to define an interface is through the default keyword. Thus, in Java8 interfaces can contain several default methods (that is, instance methods with full logic) in addition to abstract methods.

public interface IAnimal { default void breath(){ System.out.println("breath!" ); }; } public class DefaultMethodTest implements IAnimal { public static void main(String[] args) { DefaultMethodTest defaultMethod = new DefaultMethodTest(); defaultMethod.breath(); }} The following output is displayed: breath!Copy the code

If IAnimal has a default method defined by default, its implementation class DefaultMethodTest can also have breath. However, if a class inherits multiple interfaces, there will be conflicts with the same methods in multiple interfaces. How to resolve the conflicts? In fact, improvements to the default methods have enabled Java classes to have the ability to have something like multiple inheritance, where an object instance will have instance methods of multiple interfaces, with the natural problem of duplicate method collisions.

Here’s an example:

public interface IDonkey{ default void run() { System.out.println("IDonkey run"); } } public interface IHorse { default void run(){ System.out.println("Horse run"); } } public class DefaultMethodTest implements IDonkey,IHorse { public static void main(String[] args) { DefaultMethodTest defaultMethod = new DefaultMethodTest(); defaultMethod.breath(); }}Copy the code

Define two interfaces: IDonkey and IHorse, both of which have the same run method. DefaultMethodTest implemented both interfaces, and because the two interfaces have the same method, there’s a conflict where you can’t decide which of the Run methods in the interface is recommended and the compilation can fail: Inherits MethodTests for Run…..

The solution

To solve the problem of method conflict caused by default method, only by rewriting the conflicting method and method binding, specify which interface default method prevails.

public class DefaultMethodTest implements IAnimal,IDonkey,IHorse { public static void main(String[] args) { DefaultMethodTest defaultMethod = new DefaultMethodTest(); defaultMethod.run(); } @Override public void run() { IHorse.super.run(); }}Copy the code

DefaultMethodTest overrides the run method and passes ihorse.super.run (); The run method in IHorse is specified.

A static method

Another feature in Java8 is the ability to declare static methods in interfaces, as shown in the following example:

public interface IAnimal { default void breath(){ System.out.println("breath!" ); } static void run(){} }Copy the code

2. Functional interfaces and lambda expressions

Functional interface

The biggest change in Java8 is the introduction of the functional idea, which means that functions can be arguments to another function. A functional interface requires only one abstract method in the interface, so the Runnable and Callable interfaces are typical functional interfaces. You can use the @functionalInterface annotation to declare an interface to be functional. If an interface meets the definition of a functional interface, it is converted to a functional interface by default. However, it is best to explicitly declare using the @FunctionalInterface annotation. This is because functional interfaces are fragile, and if a developer inadvertently adds another method, it breaks the requirements of a FunctionalInterface. Using the @functionalinterface annotation, the developer knows that the current interface is a FunctionalInterface and doesn’t inadvertently break it. Here’s an example:

@java.lang.FunctionalInterface
public interface FunctionalInterface {
    void handle();
}
Copy the code

The interface has only one abstract method, which is explicitly declared using annotations. However, functional interfaces require an abstract method that can have several default methods (instance methods), as in the following example:

@java.lang.FunctionalInterface public interface FunctionalInterface { void handle(); default void run() { System.out.println("run"); }}Copy the code

In this interface, in addition to the abstract method Handle, there is a default method (instance method) run. In addition, any method implemented by Object should not be considered an abstract method.

Lambda expressions

Lambda expression is the core of functional programming. A lambda expression is an anonymous function. It is a function body without a function name that can be passed directly to the relevant caller as an argument. Lambda expressions greatly increase the expressiveness of the Java language. The syntax of lambda is:

(parameters) -> expression
或
(parameters) ->{ statements; }
Copy the code
  • Optional type declaration: There is no need to declare parameter types, and the compiler can uniformly identify parameter values.

  • Optional parameter parentheses: You do not need to define parentheses for one parameter, but you need to define parentheses for multiple parameters.

  • Optional braces: If the body contains a statement, braces are not required.

  • Optional return keyword: The compiler automatically returns the value if the body has only one expression return value. Curly braces are required to specify that the expression returns a value.

The complete example is (from the rookie tutorial)

public class Java8Tester { public static void main(String args[]){ Java8Tester tester = new Java8Tester(); MathOperation addition = (int a, int b) -> a + b; MathOperation subtraction = (a, b) -> a - b; MathOperation x x = (int a, int b) -> {return a * b; }; MathOperation division = (int a, int b) -> a/b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); GreetingService greetService1 = message -> System.out.println("Hello "+ message); // GreetingService greetService2 = (message) -> system.out.println ("Hello "+ message); greetService1.sayMessage("Runoob"); greetService2.sayMessage("Google"); } interface MathOperation { int operation(int a, int b); } interface GreetingService { void sayMessage(String message); } private int operate(int a, int b, MathOperation mathOperation){ return mathOperation.operation(a, b); }}Copy the code

In addition, lambda can also access external local variables, as shown in the following example:

int adder = 5;
Arrays.asList(1, 2, 3, 4, 5).forEach(e -> System.out.println(e + adder));
Copy the code

In fact, when accessing a member variable or local variable of a class ina lambda, it is implicitly converted to a final variable, so the above example is actually equivalent to:

final int adder = 5;
Arrays.asList(1, 2, 3, 4, 5).forEach(e -> System.out.println(e + adder));
Copy the code

3. Method references

Method references are used to further simplify lambda expressions by directly accessing existing methods or constructors of a class or instance through the class name or a combination of instance and method names. Method references are defined using **::, the first part of which represents the class name or instance name, the second part represents the method name, and NEW if it is a constructor.

Method references are used in quite flexible ways in Java8. In general, they come in the following forms:

  • Static method references: ClassName::methodName;
  • Instance method references on instances: instanceName::methodName;
  • Instance method references on the superclass: supper::methodName;
  • Class instance method references: ClassName:methodName;
  • The constructor refers to Class:new;
  • Array constructor reference ::TypeName[]::new

Here’s an example:

public class MethodReferenceTest { public static void main(String[] args) { ArrayList<Car> cars = new ArrayList<>(); for (int i = 0; i < 5; i++) { Car car = Car.create(Car::new); cars.add(car); } cars.forEach(Car::showCar); } @FunctionalInterface interface Factory<T> { T create(); } static class Car { public void showCar() { System.out.println(this.toString()); } public static Car create(Factory<Car> factory) { return factory.create(); }}} Output:  learn.MethodReferenceTest$Car@769c9116 learn.MethodReferenceTest$Car@6aceb1a5 learn.MethodReferenceTest$Car@2d6d8735 learn.MethodReferenceTest$Car@ba4d54 learn.MethodReferenceTest$Car@12bc6874Copy the code

In the example above, Car::new is used to further simplify lambda expressions by constructing method references to Car::showCar, which represents instance method references.

4. Stream

In Java8, there is a new way of processing data, called Stream, which can be combined with lambda expressions to process data more succinctly and efficiently. Stream performs operations such as filtering, sorting, and aggregating data in an intuitive way similar to SQL statements that query data from a database.

4.1 What is a Stream

A Stream is a queue of elements from a data source that supports aggregation operations. It is more like a higher version of Iterator, where the original Iterator can only iterate through elements and perform operations. With a Stream, you just specify something, like “filter strings greater than 10,” and the Stream iterates internally and does the specified operation.

The elements in Stream are processed by intermediate operation in the pipe, and the final result is obtained by terminal operation.

  • Data source: the source of a Stream, which can be a Stream converted from a collection, array, I/O channel, etc.
  • Basic operation: the same operation is similar to SQL statements, such as filter, map, reduce, the find, match, the sort of operation.

When we operate on a stream, it actually involves execution like this:

Get the data source –> convert to Stream–> perform the operation, return a new Stream–> continue the operation with the new Stream–> until the final operation outputs the final result.

4.2 Way to generate Stream

There are several ways to generate a Stream:

  1. From Collection and Arrays:

    • Collection.stream();
    • Collection.parallelStream(); // Parallel streams can greatly improve execution efficiency compared to serial streams
    • Arrays.stream(T array);
  2. Static methods in Stream:

    • The Stream of ();
    • generate(Supplier s);
    • iterate(T seed, UnaryOperator f);
    • empty();
  3. Other methods

    • Random.ints()
    • BitSet.stream()
    • Pattern.splitAsStream(java.lang.CharSequence)
    • JarFile.stream()
    • BufferedReader.lines()

Here are some examples of the two common approaches mentioned above:

public class StreamTest { public static void main(String[] args) { //1. Array String[] strArr = new String[]{" A ", "B ", "c"}; List<String> list = Arrays.asList(strArr); Stream<String> stream = list.stream(); Stream<String> stream1 = Arrays.stream(strArr); Stream<String> stream2 = stream.of (strArr); Stream<Double> stream3 = Stream.generate(Math::random); Stream<Object> stream4 = Stream.empty(); Stream.iterate(1, i -> i++); }}Copy the code

4.3 Operation of Stream

There are several common Stream operations:

  1. Intermediate: An Intermediate operation is a data element that is converted or manipulated in a Stream and returned as a Stream that is still available for the next Stream operation. The common ones are map (mapToInt, flatMap, etc.), filter, DISTINCT, sorted, peek, limit, and Skip.
  2. Termial (end operation) : Refers to the final aggregation operation on the Stream to output the result.

In the middle of operation

Filter: Filters the elements in Stream

Filter strings with empty elements:

long count = stream.filter(str -> str.isEmpty()).count();
Copy the code

Map: Maps elements in Stream to another element according to the specified rules

Add the string “_map” to each element

stream.map(str -> str + "_map").forEach(System.out::println);
Copy the code

The map method is one-to-one, where each element in the stream is mapped to another element according to the mapping rules, whereas the flatMap method is used for one-to-many relationships.

Concat: merges streams

The concat method concatenates two streams together to compose a single Stream. If both input streams are sorted, the new Stream is sorted. If either of the input streams is parallel, the new Stream is also parallel; When a new Stream is closed, both of the original input streams will be closed.

Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5, 6)).
	forEach(System.out::println);
Copy the code

Distinct: Indicates deduplication for convection

Removes duplicate elements from the stream

Stream<String> stream = Stream.of("a", "a", "b", "c"); stream.distinct().forEach(System.out::println); Output: A, B, cCopy the code

Limit: limits the number of elements in a stream

Intercept the first two elements of the stream:

Stream<String> stream = Stream.of("a", "a", "b", "c"); stream.limit(2).forEach(System.out::println); The output is as follows: a aCopy the code

Skip: Skip the first few elements in the stream

Discard the first two elements of the stream:

Stream<String> stream = Stream.of("a", "a", "b", "c"); stream.skip(2).forEach(System.out::println); Output: B CCopy the code

Peek: Operation on each element in the flow in turn, similar to forEach operation

Examples in the JDK:

Stream.of("one", "two", "three", "four") .filter(e -> e.length() > 3) .peek(e -> System.out.println("Filtered value: " + e)) .map(String::toUpperCase) .peek(e -> System.out.println("Mapped value: " + e)) .collect(Collectors.toList()); Filtered value: three Mapped value: three Filtered value: four Mapped Value: fourCopy the code

Sorted: To sort elements in a stream, use sorted(Comparator<? Super T> comparator) custom comparison rules

Stream<Integer> stream = Stream.of(3, 2, 1); stream.sorted(Integer::compareTo).forEach(System.out::println); Output: 1, 2, 3Copy the code

Match: Checks whether elements in the stream match the specified matching rule

Stream has three match methods, semantically speaking:

  • AllMatch: All elements of the Stream match the predicate passed in, which returns true;
  • AnyMatch: Stream returns true as long as one element of the Stream matches the predicate passed in;
  • NoneMatch: None of the elements in the Stream match the passed predicate, which returns true.

Check if every element in Stream is greater than 5:

Stream<Integer> stream = Stream.of(3, 2, 1); boolean match = stream.allMatch(integer -> integer > 5); System.out.println(match); Output: falseCopy the code

End of operation

Collectors common reduction methods are summarized

Refactor and customize the collector Collectors

Count: Counts the number of elements in Stream

long count = stream.filter(str -> str.isEmpty()).count();
Copy the code

Max /min: Find the largest or smallest element in the stream

Stream<Integer> stream = Stream.of(3, 2, 1); System.out.println(stream.max(Integer::compareTo).get()); Output: 3Copy the code

forEach

The forEach method has been used several times before to iterate through the elements in the Stream, eliminating the need for a for loop and making the code cleaner and the logic cleaner.

Example:

Stream.of(5, 4, 3, 2, 1) .sorted() .forEach(System.out::println); // print results // 1,2,3,4,5Copy the code

reduce

Reduce in Stream

The Stream reduction method is summarized as follows:

5. Optional

To resolve null-pointer exceptions, prior to Java8 you needed to use if-else statements to prevent null-pointer exceptions. In Java8 you can use Optional to resolve null-pointer exceptions. Optional can be understood as a data container, and can even encapsulate NULL, and returns true if the value exists by calling isPresent(). So just to understand Optional. Let’s start with an example:

public class OptionalTest { private String getUserName(User user) { return user.getUserName(); } class User { private String userName; public User(String userName) { this.userName = userName; } public String getUserName() { return userName; }}}Copy the code

In fact, the getUserName method does not determine whether the input parameter is null, so it is not secure. If, prior to Java8, we had to use if-else logic to avoid possible null pointer exceptions, getUserName would change as follows:

private String getUserName(User user) { if (user ! = null) { return user.getUserName(); } return null; }Copy the code

This is a very tedious piece of code. Using Optional is a lot leaner:

private String getUserName(User user) {
    Optional<User> userOptional = Optional.ofNullable(user);
    return userOptional.map(User::getUserName).orElse(null);
}
Copy the code

Before Java8, if-else logic was imperative, while Optional was more like functional programming, focusing on the end result and leaving the processing to internal JDK implementations.

By now, it’s clear that Optional is useful for avoiding null pointer exceptions.

Create the Optional

  1. Optional.empty(): Create an empty Optional object using the static factory method option.empty;
  2. Optional of(T value): Throws a NullPointerException if value is null.
  3. Optional ofNullable(T value) : Using the static factory method option. ofNullable, you can create an Optional object that allows null values.

Example code:

// create Optional Optional<Object> Optional = option.empty (); Optional<Object> optional1 = Optional.ofNullable(null); Optional<String> optional2 = Optional.of(null);Copy the code

Commonly used method

1. Boolean equals(Object obj) : Check whether other objects are equal to Optional; 2. Optional<T> filter(Predicate<? Super <T> predicate) : Returns an Optional description of the value if it exists and matches the given predicate, otherwise returns an empty Optional; 3. <U> Optional<U> flatMap(Function<? Super T,Optional<U>> mapper) : Returns the value of the mapping method based on the Optional inclusion if the value exists, otherwise returns an empty Optional; 4. T get() : Returns the value if it is included in this Optional, otherwise throws an exception: NoSuchElementException; 5. Int hashCode() : returns a hashCode for an existing value, or 0 if no value exists; 6. void ifPresent(Consumer<? Super T> consumer) : call consumer with the value if it exists, otherwise nothing is done; 7. Boolean isPresent() : Method returns true if the value exists, false otherwise; 8. <U>Optional<U> map(Function<? super T,? Extends U> mapper) : provides a mapping method if it exists, and returns an Optional description if non-NULL is returned; 9. T orElse(T other) : Returns the value if the value exists, otherwise returns other; 10. T orElseGet(Supplier<? Extends T> other) : returns the value if it exists, otherwise fires other and returns the result of the other call; 11. <X extends Throwable> T orElseThrow(Supplier<? Extends X> exceptionSupplier) : returns the included value if it exists, otherwise throws an exception inherited by the Supplier; 12. String toString() : Returns an Optional non-empty String for debuggingCopy the code

Optional common methods:

6. Date/ Time API improvements

Prior to Java8, the datetime API had a number of issues, such as:

  • Date is not thread-safe and all Date classes are mutable;
  • The design is poor: there are date classes in both the java.util and java.sql packages, and classes for formatting and parsing are defined in the java.text package. It is also unreasonable for each package to combine them together;
  • Calendar and java.util. TimeZone classes are introduced in Java because date classes do not provide internationalization and no TimeZone support.

To address these issues, Java8 has redesigned the date-time API, and Java8 has released a new date-time API (JSR 310) to further enhance Date and Time handling. Several classes commonly used in the java.util.time package are:

  • It gets the current time, date and time by specifying a time zone. Clock can replace system.currentTimemillis () with timezone.getDefault ().
  • Instant: An Instant object represents a point in time on the timeline. The instant.now () method returns the current Instant point (Greenwich Mean Time);
  • Duration: the amount of time between two instantaneous points;
  • LocalDate: a date with a year, month, and day, which can be created using the static now or of methods;
  • LocalTime: indicates a time in a day. You can also use now and of.
  • LocalDateTime: both date and time;
  • ZonedDateTime: create a time zone by setting the id of the time;
  • DateTimeFormatter: A date formatting class that provides a variety of predefined standard formats;

Example code is as follows:

public class TimeTest { public static void main(String[] args) { Clock clock = Clock.systemUTC(); Instant instant = clock.instant(); System.out.println(instant.toString()); LocalDate localDate = LocalDate.now(); System.out.println(localDate.toString()); LocalTime localTime = LocalTime.now(); System.out.println(localTime.toString()); LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime.toString()); ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); System.out.println(zonedDateTime.toString()); }} The output is: 2018-04-14T12:50:27.437z 2018-04-14 20:50:27.646 2018-04-14T20:50:27.646 2018-04-14T20:50:27.647+08:00[Asia/Shanghai]Copy the code

7. Other improvements

Java8 has made other changes as well, which can be summarized as follows:

  1. In previous versions, annotations could only be declared once in the same place. In Java8, the @repeatable annotation was provided to implement Repeatable annotations.
  2. The String class provides a join method to concatenate strings.
  3. ParallelSort provides a way to parallelize Arrays. For example, parallelSort can be used to sort Arrays in parallel.
  4. In Java8, a lot of work has been done on the concurrent application layer: (1) provide a more powerful Future: CompletableFuture; StampedLock can be used as an alternative to ReadWriteLock; (3) the better performance of atomic classes: : LongAdder, LongAccumulator DoubleAdder and DoubleAccumulator;
  5. The compiler adds some features and provides some new Java tools

The resources

References to Stream:

Stream API is on

Stream explains a series of articles

Stream is explained in detail

The Stream API is new to Java8

Optional references:

The Optional API is very detailed

The Optional section is well explained and worth referring to

Introduction to new Java8 features:

Java8 new feature guide, very good information

Keep up with Java 8 – new features you overlooked

Java8 new features learning series tutorials