Java8 - Quick Start Manual (Study Notes)

github blog portal

Java8 feature study notes

  Many new features have been added to Java8. Here I have studied and learned some of the more commonly used features, and I will share them with you here. (A deep understanding of Java 8 is recommended for understanding the basics) This article is divided into the following chapters:

  • Lambda expressions
  • method reference
  • default method
  • functional interface
  • Function
  • Stream
  • Optional API
  • Date Time API

Lambda expressions

Lambda expressions, also known as closures. Lambdas allow functions to be passed as parameters to a method (functions are passed into methods as parameters). Using lambda expressions can make the code more concise and compact. Lambda expressions can replace the previously widely used internal anonymous classes, various callbacks, such as event responders, Runnable passed into the Thread class, etc.

Lambda syntax

The syntax of a lambda expression is as follows:

(parameters) -> expression  
(parameters) ->{ statements; }

Characteristics of Lambda Expressions

  • Type declaration (optional): You don't need to declare the parameter type, the compiler will recognize the parameter value.
  • Parameter parentheses (optional): Parentheses can be used without a single parameter, but must be used with multiple parameters.
  • Braces and return keyword (optional): If there is only one expression, you can omit the braces and return keyword, and the compiler will automatically return the value; on the contrary, in the case of using braces, you must specify the return value value.

Lambda Expression Example

Here is an example of the commonly used list sorting function:

private static List<People> peopleList = new ArrayList<People>();
{
    peopleList.add(new People("a",17));
    peopleList.add(new People("b",16));
    peopleList.add(new People("c",19));
    peopleList.add(new People("d",15));
}

@Test
public void testLambda(){
    System.out.println("排序前:"+peopleList);

    //第一种,传统匿名Compartor接口排序
    Collections.sort(peopleList, new Comparator<People>() {
        @Override
        public int compare(People o1, People o2) {
            return o1.getAge().compareTo(o2.getAge());
        }
    });
    System.out.println("匿名接口方法——排序后:"+peopleList);

    //第二种,使用Lambda表达式来代替匿名接口方法
    //1.声明式,不使用大括号,只可以写单条语句
    Collections.sort(peopleList,(People a,People b)->a.getAge().compareTo(b.getAge()));
    System.out.println("Lambda表达式1、排序:"+peopleList);;
    //2.不声明式,使用大括号,可以写多条语句
    Collections.sort(peopleList,(a,b)->{
        System.out.print("——————————————");
        return a.getAge().compareTo(b.getAge());
    });
    System.out.println();
    System.out.println("Lambda表达式2、排序:"+peopleList);

    //第三种,使用Lambda表达式调用类的静态方法
    Collections.sort(peopleList,(a,b)->People.sortByName(a,b));
    System.out.println("Lambda表达式调用静态方法:"+peopleList);

    //第四种,使用Lambda表达式调用类的实例方法
    Collections.sort(peopleList,(a,b)->new People().sortByAge(a,b));
    System.out.println("Lambda表达式调用实例方法:"+peopleList);
}

Corresponding running result:

( Note : Only final objects can be operated in Lambda expressions, and declared objects are also final)

Some friends should have observed that Lambda expressions are somewhat similar to function pointers in C and anonymous functions in JavaScript. In fact, a lambda expression is essentially an anonymous method, but its target type must be a "functional interface", which is a new concept introduced in Java 8 and will be introduced in more detail in the following.

method reference

In some Lambdas, methods may be simply invoked, such as three and four in the previous example. In this case, method references can be used to improve readability.

Types of method references

  • class static method reference
    Class::staticMethodName
  • method reference to an object
    instance::instanceMethodName
  • A method reference for an arbitrary object of a specific class:
    Class::method
  • Constructor reference:
    Class::new

Example of a method reference

@Test
public void testMethodReference() {
//第一种,引用类的静态方法
Collections.sort(peopleList, People::sortByName);
System.out.println("引用类的静态方法:" + peopleList);

//第二种,引用类的实例方法
Collections.sort(peopleList, new People()::sortByAge);
System.out.println("引用类的实例方法:" + peopleList);

//第三种,特定类的方法调用()
Integer[] a = new Integer[]{3, 1, 2, 4, 6, 5};
Arrays.sort(a, Integer::compare);
System.out.println("特定类的方法引用:" + Arrays.toString(a));

//第四种,引用类的构造器
Car car = Car.create(Car::new);
System.out.println("引用类的构造器:" + car);
}
public static Car create(Supplier<Car> supplier){
    return supplier.get();
}

default method

In the era before Java 8, it was very difficult to add a general implementation to an existing interface. Once the interface was released, it was equal to finalization. If a method was added to the interface at this time, it would destroy all objects that implement the interface.
The goal of default methods (previously known as virtual extension methods or guard methods) is to solve this problem so that interfaces can still be evolved incrementally after they are published.

Default method (defalut)

public interface vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}

static method

public interface vehicle {
   static void blowHorn() {
      System.out.println("按喇叭!!!");
   }
}

Note: There can be multiple static methods and default methods, and default methods can be overridden.

functional interface

"Functional interface (functional interface)" is an interface that removes default methods and inherited abstract methods, and only explicitly declares an abstract method. It is annotated on the class with the @FunctionalInterface annotation , or it can be omitted, and Java will automatically recognize it. Next, some common functional interfaces are introduced:

java.util.function.Predicate

This interface contains the method boolean test(T t), which is generally used for condition detection. It contains three default methods: and, or, negate, that is, and or not, which are used for various condition judgments. example:

Predicate<Integer> predicate = x -> x > 3;

predicate.test(10);//true
predicate.negate().test(10);//false
predicate.or(x -> x < 1).and(x -> x > -1).negate().test(-1);//true

Note : The order of AND or NOT judgment here is from left to right, and the order of calls will affect the result.

java.util.Comparator

Comparator is a classic interface in Java and is more commonly used in sorting. Java8 adds some new default methods on top of this to enrich the functionality of this interface. example:

Integer[] a = new Integer[]{3, 1, 2, 4, 6, 5};
Comparator<Integer> comparator = Integer::compare;

Arrays.sort(a, comparator);
System.out.println("升序:" + Arrays.toString(a));

Arrays.sort(a,comparator.reversed());
System.out.println("降序:"+Arrays.toString(a));

result

升序:[1, 2, 3, 4, 5, 6]
降序:[6, 5, 4, 3, 2, 1]

java.util.function.Supplier

The class contains only methods: The
T get();
Supplier interface is a new functional interface in 1.8 to support functional programming. It is used to return an arbitrary generic instance object, similar to the function of a factory.

java.util.function.Consumer

This interface represents an operation that accepts a single input parameter and returns no value. Unlike other functional interfaces, the Consumer interface expects to perform operations that modify the content. For example, we need a method to modify People in batches. Using Predicate and Consumer, we can write it like this
. Add the updateMany method in People:

public static List updateMany(List<People> peopleList, Predicate<People> predicate, Consumer<People> consumer) {
    for (int i = 0; i < peopleList.size(); i++) {
        if (predicate.test(peopleList.get(i))) {
            consumer.accept(peopleList.get(i));
        }
    }
    return peopleList;
}

transfer:

//批量修改,将age<18的对象的age改为18
People.updateMany(peopleList,
        p -> p.getAge() < 18,
        p -> p.setAge(18));
System.out.println("修改后的结果:" + peopleList);

In this way, the internal judgment logic and modification code can be called externally, and the for, if and other statements can be encapsulated inside to improve the readability of the code.

There are other functional interfaces, such as Runnable, InvocationHandler, etc., which will not be described here. Those who are interested can inquire about the information by themselves. Stream, Function, and Optional are also function interfaces, which will be described in detail below.

Function

illustrate

There are four core function interfaces of the java.util.function package provided by Java8.

  • The function type T -> R, completes the conversion of the parameter type T to the result type R and data processing. Core function interface Function
  • Judgment type T -> boolean, core function interface Predicate
  • Consumption type T -> void, core function interface Consumer
  • Supply type void->T, core function interface Supplier

The Function interface provides the basis of functional programming for Java8. The apply method is similar to the accept method of Consumer, but provides the possibility of return and type conversion, and the function is more powerful; then the andThen and compose methods can make the Function form a Function function chain , for multi-level data processing and transformation.

main method

  • R apply(T t) – Applies a Function object to the input parameters and returns the result of the calculation.

  • default Function andThen(Function<? super R,? extends V> after) returns a function object that first executes the apply method of the current function object and then executes the apply method of the after function object.

  • default Function compose(Function<? super V,? extends T> before) returns a function object that executes the apply method of the before function object and then executes the apply method of the current function object.

  • static Function identity() returns a function object that returns only the input parameters after executing the apply() method.

Detailed method

apply:
R apply(T t); 

Receive type: T
Return type: R
Type conversion: T→R

The core method of the Function interface, which can perform arbitrary operations and has a return value. Receives an object of type T, and returns an object of type R after processing. The main functions are type conversion and data processing.

compose:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
}

Receive type: Function<? super V, ? extends T>
Return type: Function
Type conversion: (V+)→(T-)→T→R
apply execution order: before→this

Here "V+" refers to "? super V", which means any parent class of V including V; "T-" refers to "? extends T", which means any subclass of T including T. The compose method returns a Function , this Function first executes the apply method of before, converts the data of type V+ to type T-, and then passes T- as a parameter to the apply method of this to convert type T to type R.
Through the compose method, a Function execution can be inserted before a Function execution. Since the return type is still Function, the compose method can be called repeatedly to form a method chain.

andThen:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

Receive type: Function<? super R, ? extends V>
Return type: Function
Type conversion: T→R→(R+)→(V-)
apply execution order: this→after

Here "R+" refers to "? super R", which means any parent class of R including R; "V-" refers to "? extends V", which means any subclass of V including V. andThen method returns a Function , this Function first executes the apply method of this, converts the data of type T to type R, and then passes R as a parameter to the apply method of after to convert the R+ type to the V- type.
Through the andThen method, you can insert a Function execution after a Function execution. Since the return type is still Function, the andThen method can be called repeatedly to form a method chain.

identity:
static <T> Function<T, T> identity() {
    return t -> t;
}

Receive Type: None
Return Type: Function
Type conversion: T→T

The description of the method is: Returns a function that always returns the input parameters. Calling this method can get a Function that returns the input parameters, and this Function can simply be used for data processing without type conversion.

Stream

The Stream API is provided in Java 8, that is, stream processing. You can operate by converting objects such as List, Set, Array, etc. into streams. Stream operations in Stream are divided into two types: intermediate operations and final operations. The intermediate operation will return a brand new Stream object , which means that your operation will not affect the original stream; the final operation will convert or operate the stream and return a Object of Stream. Stream can replace traditional loop operations. It is distinguished from threads. Stream is divided into serial (Stream) and parallel (parallelStream). For the performance analysis of Stream, please refer to this article "Stream Performance Analysis" . Let's take a look at some of the methods in Strea:

Intermediate operation

  • distinct

    Stream<T> distinct();

    Removes duplicate objects from Stream and returns a stream. (using the equals method of the object)

  • skip

    Stream<T> skip(long n);

    Skip the first n objects in the Stream and return other objects to a Stream. If n exceeds the number of objects in the Stream, an empty Stream is returned.

  • limit

    Stream<T> limit(long maxSize);

    Intercept the first maxSize objects of the Stream and form a new Stream.

  • filter

        Stream<T> filter(Predicate<? super T> predicate);

    Filter objects according to the given predicate and return a Stream of objects that satisfy the conditions.

  • map

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    Converts a stream of type T to a Stream of type R with the given mapper.

  • flatMap

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    flatMap also converts Stream. The difference between flatMap and map is that flatMap converts each value in a Stream into a Stream, and then flattens these streams into a Stream.
    Example (from: Streaming data processing of new features of Java8 ):
    Suppose we have a string array String[] strs = {"java8", "is", "easy", "to", "use"};, we Want to output all the non-repeating characters that make up this array, then we may first think of the following implementation:

    List<String[]> distinctStrs = Arrays.stream(strs)
                        .map(str -> str.split(""))  // 映射成为Stream<String[]>
                        .distinct()
                        .collect(Collectors.toList());

    After performing the map operation, we get a stream containing multiple strings (character arrays that form a string). At this time, the distinct operation is performed based on the comparison between these string arrays, so it is not up to our expectations. , the output at this time is:

    [j, a, v, a, 8]
    [i, s]
    [e, a, s, y]
    [t, o]
    [u, s, e]

    distinct can only achieve our purpose by operating on a stream containing multiple characters, that is, on Stream to operate. At this point flatMap can achieve our purpose:

    List<String> distinctStrs = Arrays.stream(strs)
                                    .map(str -> str.split(""))  // 映射成为Stream<String[]>
                                    .flatMap(Arrays::stream)  // 扁平化为Stream<String>
                                    .distinct()
                                    .collect(Collectors.toList());

    flatMap will map the Stream obtained by map , converted into a stream mapped by each string array , and then flatten these small streams into one big stream Steam composed of all strings , so as to achieve our purpose.

  • sorted

    Stream<T> sorted();
    Stream<T> sorted(Comparator<? super T> comparator);

    The sorted method can sort the Stream. The sorted object must implement Comparable. If it is not implemented, a ClassCastException will be thrown; if no comparator is provided, the compareTo method will be called.

  • peek

    Stream<T> peek(Consumer<? super T> action);

    Executes the provided action on each object in the stream.
    In Stack, peek is used to view an object. The same is true in the flow, which is used to view the object according to the given action when the flow loops. While element modification operations are possible, they are not recommended.

  • Comprehensive example:

    Integer[] a = new Integer[]{3, 1, 2, 5, 11, 4, 6, 5, 3, 1};
        List<Integer> aList = Arrays.stream(a)
        .distinct()
        .skip(1)
        .filter((e) -> e < 6)
        .peek(e -> System.out.println("循环1次"))
        .limit(4)
        .sorted()
        .collect(Collectors.toList());
    System.out.println(aList);

    output:

    循环1次
    循环1次
    循环1次
    循环1次
    [1, 2, 4, 5]

    final operation

  • polymerization
    • max & min

      Optional<T> min(Comparator<? super T> comparator);
      Optional<T> max(Comparator<? super T> comparator);

      Returns the max or min in the Stream based on the given comparator.

    • count

      long count();

      Returns the number of objects in the Stream.

  • match
    • anyMatch & allMatch & noneMatch

      boolean anyMatch(Predicate<? super T> predicate);
      boolean allMatch(Predicate<? super T> predicate);
      boolean noneMatch(Predicate<? super T> predicate);

      Determines whether the Stream matches the condition based on the given predicate.

    • collect

      <R, A> R collect(Collector<? super T, A, R> collector);

      Operates on the elements in the Stream according to the given collector, returning an object of complex data structure. It is used to convert the objects in Stream into the structure we want, such as list, map, set, etc.
      In the previous example, collect(Collectors.toList()) is used to convert the objects in Stream to List.

    • reduce

      Optional<T> reduce(BinaryOperator<T> accumulator);
      T reduce(T identity, BinaryOperator<T> accumulator);

      If we don't want to simply return a type such as List, but want to reduce the entire Stream to an object after some operations, we can use the reduction operation. The reduce method has two parameters, among which accumulator represents the operation of the stipulation, that is, how to parameterize it; identity is the identification value of the accumulator (the specific use is not yet known).
      Example: Summation

      Integer[] a = new Integer[]{3, 1, 2, 5, 11, 4, 6, 5, 3, 1};
      int sum = Arrays.stream(a)
          .distinct()
          .filter((e) -> e < 6)
          .reduce(0, (x, y) -> x + y);//或.reduce(0, Integer::sum);
      System.out.println(sum);//15
    • toArray

      Object[] toArray();

      Returns the objects in the Stream as an array of Objects.

    • forEach

      void forEach(Consumer<? super T> action);

      As the name implies, the action operation is performed on each element in the Stream, which is similar to peek, but forEach is a final operation, which is generally used to view the object at the end.

    • findFirst & findAny

      Optional<T> findFirst();
      Optional<T> findAny();

      findFirst can return the first object in the Stream and encapsulate it in Optional.
      findAny does not return the first object, but any object. The results of findFirst and findAny are the same in sequential streams, but in parallel streams, findFirst has limitations, so you need to use findAny in parallel streams (what is some element written in the source comments of findAny?). Also encapsulate the object in Optional.

Optional API

In programming before java8, we always need to perform if(obj=null) to prevent NullPointException, and after java8, the Optional class is provided, which is used to prevent NullPotinException judgment on the one hand, and flow programming on the other hand. Better support is provided with functional variables; Optional is a container that contains objects, which can contain null values. Many methods are encapsulated in the Optional class to allow us to better handle our code. Next, let's take a look at several commonly used methods in Optional:

  • empty & of & ofNullable

    public static <T> Optional<T> empty(){...}
    public static <T> Optional<T> of(T value) {return new Optional<T>(value);}
    public static <T> Optional<T> ofNullable(T value){return value == null ? empty() : of(value);}

    First of all, the constructor of Optioanl is private, and an instance of Optional can only be obtained through the above three static methods. The empty method will return the constant EMPTY object in the Optional, which is generally used when comparing. Note that the EMPTY here is a singleton and a constant; generally we need to construct an Optional, use the of or ofNullable method, the of method will pass our value Constructs a new Optional to return, while ofNullable returns an EMPTY instance when it receives null.

  • isPresent

    public boolean isPresent() {return value != null;}
    public void ifPresent(Consumer<? super T> consumer) {if (value != null)consumer.accept(value);}

    The isPresent method is used to determine whether the value contained in the Optional is null. The first method returns a boolean; the second method is based on the judgment. If it is null, nothing will be executed, and if it is not null, a consumer operation will be executed.

  • map & flatMap

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper){...}
    public<U> Optional<U> flatMap(Function<? super T, Optional<U> mapper){...}

    The usage and functions of map, flatMap and Stream are roughly the same. They are all transformations and merge transformations, so I won't repeat them.

  • get

    public T get() {...}

    The get method is used to get the value. Note that if value is null, a NoSuchElementException will be thrown.

  • filter

    public Optional<T> filter(Predicate<? super T> predicate) {...}

    The filter method also gets the value, and it can pass in a predicate to determine whether the value satisfies the condition. If value is null, this will be returned; if predicate.test is true, this will be returned, otherwise EMPTY will be returned.

  • orElse & orElseGet & orElseGet

    public T orElse(T other) {return value != null ? value : other;}
    public T orElseGet(Supplier<? extends T> other) {return value != null ? value : other.get();}
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X{...} 

    These three methods are all used to get the value, and can do different operations in the case of value==null. orElse can pass in an other, and returns null when value==null; orElseGet uses Supplier, and calls the get method when it is null; orElseThrow receives an exceptionSupplier whose Supplier contains some kind of exception, and calls get when it is null method throws an exception.

Date Time API

Java 8 overwrites the old datetime API with the new datetime API, addressing the following shortcomings.

  • Thread safety - java.util.Date is not thread safe, so developers have to deal with concurrency issues when using dates. The new datetime API is immutable and has no setter methods.
  • Design issue - default start date is 1900, month start month is 0 instead of 1, no uniformity. Do not use methods to manipulate dates directly. The new API provides utility methods for doing this.
  • Difficulty handling time zones - Developers have to write a lot of code to deal with time zones. New API design developments help in these specific areas.

JAVA8 introduces the java.time package, a new datetime API. Due to limited space and energy, I will not introduce too much java.time here. Here are a few blog posts that I personally think are good for research:
Date/Time API,
Java 8 , New Features of Java Class Library .time package

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324929605&siteId=291194637