Lambda expression and Stream in JDK1.8

1. lambda expressions

The most worthwhile features of Java8 are Lambda expressions and Stream API. If you have the language foundation of python or javascript, it is very helpful to understand Lambda expressions, because Java is changing itself to a higher (Sha) level (Gua),

more humane. -------- It can be said that lambda expressions are actually syntactic sugar for implementing the SAM interface.

Well-written lambdas can greatly reduce code redundancy, and at the same time, readability is better than verbose inner classes and anonymous classes.

Let's list two common simplifications (simple code is also easy to understand)

  • create thread

  • sort

Lambda expressions combined with the new Java 8 feature Stream API can implement business functions concisely through functional programming. (to pave the way for later examples)

E.g:

This code is a list of strings, converting each string contained in it to an all-lowercase string. Note the call to the map method on the fourth line of the code, where the map method accepts a lambda expression.

1.1 lambda expression syntax

1.1.1 General syntax of lambda expressions

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

This is the full syntax of a lambda expression, and the next few syntaxes are simplifications of it.

1.1.2 Single parameter syntax

param1 -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

When the lambda expression has only one parameter, the parentheses can be omitted

For example: convert a string in a list to all lowercase

List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> lowercaseNames1 = proNames.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());

1.1.3 Single-statement writing

param1 -> statment

When the lambda expression contains only one statement, the curly braces, return, and semicolon at the end of the statement can be omitted

For example: convert a string in a list to all lowercase

List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});

List<String> lowercaseNames2 = proNames.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());

1.1.4 Method reference writing

(Method references, like lambdas, are new language features of Java 8, which will be discussed later)

Class or instance :: method

For example: convert a string in a list to all lowercase

List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});

List<String> lowercaseNames3 = proNames.stream().map(String::toLowerCase).collect(Collectors.toList());

1.2 Variables that can be used in lambda expressions

Example first:

//will prefix the strings in the list
String waibu = "lambda :";
List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String>execStrs = proStrs.stream().map(chuandi -> {
Long zidingyi = System.currentTimeMillis();
return waibu + chuandi + " -----:" + zidingyi;
}).collect(Collectors. toList());
execStrs.forEach(System.out::println);

output:

lambda :Ni -----:1474622341604
lambda :Hao -----:1474622341604
lambda :Lambda -----:1474622341604

 

variable waibu: external variable

variable chuandi: pass variable

variable zidingyi: Internal custom variable

 

A lambda expression can access the variables passed to it, the variables defined within itself, and the variables outside it.

However, lambda expressions have a very important restriction on accessing external variables: variables are immutable (just reference immutable, not really immutable).

When modifying waibu = waibu + " "; inside an expression, the IDE will prompt you:

Local variable waibu defined in an enclosing scope must be final or effectively final

An error will be reported when compiling. Because the variable waibu is referenced by the lambda expression, the compiler will implicitly treat it as final.

In the past, when an anonymous inner class in Java accesses external variables, the external variables must be modified with final. Now java8 has optimized this restriction, and you can not use final modification explicitly, but the compiler implicitly treats it as final.

1.3 the concept of this in lambda expressions

In a lambda, this does not refer to the SAM object produced by the lambda expression, but the external object that declares it.

E.g:

public class WhatThis {

     public void whatThis(){
           //转全小写
           List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
           List<String> execStrs = proStrs.stream().map(str -> {
                 System.out.println(this.getClass().getName());
                 return str.toLowerCase();
           }).collect(Collectors.toList());
           execStrs.forEach(System.out::println);
     }

     public static void main(String[] args) {
           WhatThis wt = new WhatThis();
           wt.whatThis();
     }
}

output:

com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
lambda

2. Method references and constructor references

I think it is a syntactic sugar to further simplify the declaration of lambda expressions.

It has been used in the previous example: execStrs.forEach(System.out::println);

2.1 Method reference

objectName::instanceMethod

ClassName::staticMethod

ClassName::instanceMethod

The first two methods are similar, which is equivalent to calling the parameter of the lambda expression directly as the parameter of instanceMethod|staticMethod. For example, System.out::println is equivalent to x->System.out.println(x); Math::max is equivalent to (x, y)->Math.max(x,y).

The last method is equivalent to using the first parameter of the lambda expression as the target object of instanceMethod, and the other remaining parameters as the parameters of the method. For example String::toLowerCase is equivalent to x->x.toLowerCase().

It can be understood that the first two are to execute the method with the incoming object as a parameter, and the latter is to call the method of the incoming object.

2.2 Constructor reference

The constructor reference syntax is as follows: ClassName::new, which treats the parameters of the lambda expression as the parameters of the ClassName constructor. For example BigDecimal::new is equivalent to x->new BigDecimal(x).

3.Stream syntax

Two sentences to understand Stream:

1. Stream is a collection of elements, which makes Stream look similar to Iterator;
2. It can support sequential and parallel aggregation operations on the original Stream;

You can think of Stream as a decorated Iterator. In the original version of Iterator, the user can only traverse the elements one by one and perform certain operations on them; for the wrapped Stream, the user only needs to provide what operations need to be performed on the elements it contains, such as "filter out

Strings with a length greater than 10", "Get the first letter of each string", etc. How to apply these operations to each element, just give it to Stream! Originally, people told the computer how to do it step by step, now it is Tell the computer what to do, the computer decides for itself

How to do it. Of course, this "how to do it" is still relatively weak.

example:

//Lists is a tool class in Guava
List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
nums.stream().filter(num -> num != null). count();

The above code is to get the number of elements in a List that are not null. Although this code is short, it is a good entry-level example of how to use Stream, as the saying goes, "a sparrow is small and complete." Let's dive into it now

After planning this example, you may be able to basically master the usage of Stream!

The picture is an analysis of the Stream example, which can be clearly seen: the original sentence is divided into three parts by three-color boxes. The statement in the red box is where the life of a Stream begins and is responsible for creating a Stream instance; the green box

The statement in the box is where the soul is given to the Stream. It converts a Stream into another Stream. The statement in the red box generates a Stream containing all nums variables. After entering the filter method in the green box, a new one is generated that filters out the original one. list of nums

All Streams after null; the statement in the blue box is the place of harvest, and the content contained in the Stream is aggregated into a value according to a certain algorithm. In the example, the number of elements contained in the Stream is obtained. If you still don't understand after analyzing it in this way, then only

Ability to use "nuclear weapons" - graphical, a picture is worth a thousand words!

Basic steps to use Stream:

1. Create a Stream;
2. Convert a Stream, the original Stream object will not change each time it is converted, and a new Stream object will be returned (** can have multiple conversions**);
3. Perform an aggregation (Reduce) operation on the Stream to obtain desired result;

3.1 How to get Stream

There are two most common ways to create a Stream:

1. Through the static factory method of the Stream interface (note: the interface in Java 8 can have static methods);
2. Through the default method of the Collection interface (the default method: Default method, which is also a new feature in Java 8, is a band in the interface) There is an implemented method) – stream(), converts a Collection object into a Stream

3.1.1 Use the Stream static method to create a Stream

1. of method: There are two overload methods, one accepts variable-length parameters, and the other interface single value

Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");


2. Generator method: Generate an infinite length Stream, and its elements are generated through a given Supplier (this interface can be regarded as an object factory, and each call returns an object of a given type)

Stream.generate(new Supplier<Double>() {
    @Override
    public Double get() {
         return Math.random();
    }
});


Stream.generate(() -> Math.random());
Stream.generate(Math::random);
The three statements have the same function, but use the syntax of lambda expressions and method references to simplify the code. Each statement actually generates an infinite-length Stream with random values. This infinite length Stream is lazy loading, generally this

Streams of infinite length will be used with the limit() method of Stream.


3. iterate method: It also generates an infinite-length Stream. Unlike generator, its elements are generated by repeatedly calling a user-specified function for a given seed value (seed). The elements contained in it can be considered as: seed, f(seed), f(f(seed)) infinite loop

Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
This code is to first obtain a Stream of an infinite-length set of positive integers, and then take out the first 10 Print. Do remember to use the limit method, otherwise it will print indefinitely.

3.1.2 Get Stream through Collection subclass

The Collection interface has a stream method, so all its subclasses can get the corresponding Stream object.

public interface Collection<E> extends Iterable<E> {
      //其他方法省略
     default Stream<E> stream() {
          return StreamSupport.stream(spliterator(), false);
     }
}

3.2 Convert Stream

Converting Stream is actually converting a Stream into a new Stream through some behavior. Several commonly used conversion methods are defined in the Stream interface. Below we select several commonly used conversion methods to explain.
1. distinct: Perform deduplication operation on the elements contained in the Stream (the deduplication logic depends on the equals method of the element), and there are no duplicate elements in the newly generated Stream;

2. filter: Use the given filter function to filter the elements contained in the Stream, and the newly generated Stream only contains the elements that meet the conditions;

3. map: Use the given conversion function to perform the conversion operation on the elements contained in the Stream, and the newly generated Stream only contains the elements generated by the conversion. This method has three variants for primitive types: mapToInt, mapToLong and

mapToDouble. These three methods are also easy to understand. For example, mapToInt converts the original Stream into a new Stream, and the elements in the newly generated Stream are all int types. The reason why there are such three variants can be exempted from automatic

Additional consumption of boxing/unboxing;

4. flatMap: Similar to map, the difference is that each element is converted to a Stream object, and the elements in the child Stream will be compressed into the parent collection;

flatMap gives a piece of code to understand:

Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());

flatMap flattens the hierarchical structure in the input Stream, that is, extracts the bottom-level elements and puts them together. In the end, there is no List in the new Stream of the output, and they are all direct numbers.

 

5. peek: Generate a new Stream containing all the elements of the original Stream, and provide a consumption function (Consumer instance), and each element of the new Stream will execute the given consumption function when it is consumed;

6. limit: truncate a Stream to get its first N elements, if the number of elements contained in the original Stream is less than N, then get all its elements;

7. skip: Returns a new Stream composed of the remaining elements after discarding the first N elements of the original Stream. If the number of elements contained in the original Stream is less than N, then an empty Stream is returned;

 

Overall call example:

 

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println(“sum is:”+nums.stream().filter(num -> num != null).distinct().mapToInt(num -> num * 2).peek(System.out::println).skip(2).limit(4).sum());

This code demonstrates all the conversion methods described above (except flatMap), and briefly explains the meaning of this code: given a List of Integer type, get its corresponding Stream object, then filter out nulls, and then remove duplicates, then each element

Multiply by 2, and then print itself when each element is consumed, skip the first two elements, and finally go to the first four elements for the sum operation (a lot of explanations, it's like nonsense, because you basically see the method name. Know what to do. This is one of the great things about declarative programming

here! ). You can refer to the explanation of each method above to see what the final output is.

2
4
6
8
10
12
sum is:36

There may be such doubts: when performing multiple conversion operations on a Stream, each element of the Stream is converted each time, and it is performed multiple times, so the time complexity is that all operations are done in a for loop. N (number of conversions) times.

In fact, this is not the case. The conversion operations are all lazy, and multiple conversion operations will only be merged during the aggregation operation (see the next section), which is completed in one cycle. We can understand this simply, there is a collection of operation functions in Stream, and each conversion operation is to convert the

Put the conversion function into this collection, loop the collection corresponding to the Stream during the aggregation operation, and then execute all functions on each element.

3.3 Convergence (Reduce) Stream

Aggregate operations (also known as folds) take a sequence of elements as input and repeatedly use a merge operation to combine the elements of the sequence into a single aggregated result. Such as finding the sum or maximum value of a list of numbers, or accumulating these numbers into a List object.

The Stream interface has some general-purpose aggregation operations, such as reduce() and collect(); there are also some specific-purpose aggregation operations, such as sum(), max(), and count(). Note: The sum method is not available for all Stream objects, only IntStream and LongStream

And DoubleStream is instance-only.

The following will introduce the aggregation operation in two parts:

Variable aggregation: Accumulate the input elements into a variable container, such as Collection or StringBuilder;
other aggregation: After removing the variable aggregation, the rest is generally not by repeatedly modifying a variable object, but by putting The previous aggregation results are used as the next input parameters, and this is repeated. Such as reduce, count, allMatch;

3.3.1 Variable Aggregation

Variable aggregation corresponds to only one method: collect, which, as its name suggests, collects the required elements of a Stream into a result container (such as a Collection). Let's first look at the definition of the most general collect method (there are other override methods):

<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
Let's take a look at the meaning of these three parameters: Supplier supplier is a factory function, using to generate a new container; the BiConsumer accumulator is also a function that adds elements from the Stream to the resulting container; the BiConsumer combiner is also

A function to combine multiple result containers of intermediate states into one (used in concurrency). Confused? Here's some code!

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
List<Integer> numsWithoutNull = nums.stream().filter (num -> num != null)
.collect(() -> new ArrayList<Integer>(),
(list, item) -> list.add(item),
(list1, list2) -> list1.addAll(list2 ));
The above code is to filter out all nulls for a List whose elements are of type Integer, and then collect the remaining elements into a new List. Take a closer look at the three parameters of the collect method, all of which are functions in the form of lambdas.

The first function generates a new ArrayList instance;
the second function accepts two parameters, the first is the ArrayList object generated earlier, the second is the elements contained in the stream, the function body is to add the elements in the stream to the ArrayList object middle. The second function is called repeatedly until the elements of the original stream are consumed;
the third function also accepts two parameters, both of which are of type ArrayList. The function body is to add the second ArrayList to the first one. ;
but the collect method call above is also a bit too complicated, that's okay! Let's take a look at another override version of the collect method, which relies on [Collector]( http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collector.html ).

<R, A> R collect(Collector<? super T, A, R> collector);
This is much more refreshing! Java8 also provides us with the Collector tool class – [Collectors]( http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html ), which has defined some static Factory methods, such as:

Collectors.toCollection() collects into Collection, Collectors.toList() collects into List and Collectors.toSet() collects into Set. There are still many such static methods, and I won't introduce them one by one here. You can go directly to the JavaDoc.

Let's take a look at the simplification of the code using Collectors:

List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
collect(Collectors.toList());

3.3.2 Other aggregations

– reduce method: The reduce method is very general, and the count, sum, etc. introduced later can be implemented using it. The reduce method has three override methods, and this article introduces the two most commonly used ones. Let's first look at the first form of the reduce method, which is defined as follows:

Optional<T> reduce(BinaryOperator<T> accumulator);
accepts a parameter of type BinaryOperator, we can use lambda expression to use it.

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream(). reduce((sum, item) -> sum + item).get());
you can see that the reduce method accepts a function, this function has two parameters, the first parameter is the return value of the last function execution (also called the intermediate result), the second parameter is the element in the stream, this function adds these two values, and the resulting sum will be assigned to

The first parameter of this function is executed next time. It should be noted that: **The value of the first parameter is the first element of the Stream when it is executed for the first time, and the second parameter is the second element of the Stream**. The return value type of this method is Optional, which is prevented by Java8

A feasible method of NPE, which will be described in detail in the following articles, here is simply considered a container, which may contain 0 or 1 objects.
The visual result of this process is shown in the figure:

There is also a very common variant of the reduce method:

T reduce(T identity, BinaryOperator<T> accumulator);
This definition is basically the same as that described above, the difference is that it allows the user to provide an initial value for loop calculation, and if the Stream is empty, it returns the value directly. And this method will not return Optional, because it will not appear null value.

An example is given directly below, and no further explanation is given.

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream(). reduce(0, (sum, item) -> sum + item));
– count method: Get the number of elements in the Stream. Relatively simple, here is an example directly, without explanation.

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().count());

– Search related
– allMatch: Does all elements in the Stream meet the given matching conditions
– anyMatch: Does any element in the Stream meet the matching conditions
– findFirst: Returns the first element in the Stream, if the Stream is empty, Returns empty Optional
- noneMatch: Are all elements in the Stream do not meet the given matching conditions
- max and min: use the given comparator (Operator), return the maximum | minimum value in the Stream
given allMatch and max below example, the rest of the method the reader treats as an exercise.

See the source code print help
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println(ints.stream().allMatch( item -> item < 100));
ints.stream().max((o1, o2) -> o1.compareTo(o2)).ifPresent(System.out::println);

Reprinted from: https://www.cnblogs.com/aoeiuv/p/5911692.html

Guess you like

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