1. Introduction of Streams
Today’s Stream refers to the classes in the java.util. Stream package. Stream can easily convert the previous combination class to Stream and process in a Stream way, greatly simplifying our programming, Stream package, the most core is interface Stream
From the figure above we can see that Stream is inherited from BaseStream. A lot of very practical methods defined in the Stream, such as filter, map, flatmap, forEach, reduce, collect and so on. We’re going to talk about each of these.
1.1 create a Stream
Streams can be created in a number of ways. After Java introduced streams, all collection classes added a Stream () method that gives you a direct view of the Stream. It can also be created using the stream. of method:
//Stream Creation
String[] arr = new String[]{"a"."b"."c"};
Stream<String> stream = Arrays.stream(arr);
stream = Stream.of("a"."b"."c");
Copy the code
1.2 Streams Multi-threading
If we want to use multiple threads to process collection class data, Stream provides a very convenient multithreaded method parallelStream() :
//Multi-threading
List<String> list =new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("abc");
list.add("ccc");
list.add("ddd");
list.parallelStream().forEach(element -> doPrint(element));
Copy the code
1.3 Basic operations of Stream
Stream operations fall into two categories: intermediate operations, which return a Stream and can therefore be called cascaded. The other is the termination operation, which returns the type defined by Stream.
//Operations
long count = list.stream().distinct().count();
Copy the code
In the example above, distinct() returns a Stream, so the operation can be cascaded, and the last count() is a terminating operation, returning the last value.
Matching
Stream provides three match methods anyMatch(), allMatch(), and noneMatch(). Let’s see how to use them:
//Matching
boolean isValid = list.stream().anyMatch(element -> element.contains("h"));
boolean isValidOne = list.stream().allMatch(element -> element.contains("h"));
boolean isValidTwo = list.stream().noneMatch(element -> element.contains("h"));
Copy the code
Filtering
The filter() method allows us to filter the data in the Stream to get what we need:
Stream<String> filterStream = list.stream().filter(element -> element.contains("d"));
Copy the code
In the example above we selected the String containing the letter “d” from the list.
Mapping
A map simply reprocesses the values in a Stream and returns the processed values as a new Stream.
//Mapping
Stream<String> mappingStream = list.stream().map(element -> convertElement(element));
private static String convertElement(String element) {
return "element"+"abc";
}
Copy the code
In the above example we add “ABC” to each value in the list and return a new Stream.
FlatMap
A flatMap is similar to a Map, but it is different.
How do you understand that?
Suppose we have a CustBook class:
@Data
public class CustBook {
List<String> bookName;
}
Copy the code
CustBook defines a bookName field.
Let’s take a look at what Map returns:
List<CustBook> users = new ArrayList<>();
users.add(new CustBook());
Stream<Stream<String>> userStreamMap
= users.stream().map(user -> user.getBookName().stream());
Copy the code
In the above code, the map converts each user to a stream, so the final result is a stream that returns a stream.
If we just want a String, we can use the FlatMap:
List<CustBook> users = new ArrayList<>();
users.add(new CustBook());
Stream<String> userStream
= users.stream().flatMap(user -> user.getBookName().stream());
Copy the code
Simply put, a FlatMap is to flatten a hierarchy from scratch.
Reduction
Reduce () takes two parameters, the first is the start value, followed by a function representing the accumulation.
//Reduction
List<Integer> integers = Arrays.asList(1.1.1);
Integer reduced = integers.stream().reduce(100, (a, b) -> a + b);
Copy the code
Reduce (100, (a, b) -> a + b));
Collecting
The collect() method converts the Stream back into a collection class for easy processing and presentation:
List<String> resultList
= list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());
Copy the code
2. Classification and use of functional interface
Java 8 introduced lambda expressions, which actually represent an anonymous function.
Before Java 8, you needed a new class implementation to use anonymous functions, but with lambda expressions, everything is very brief.
Let’s look at an example from thread pools:
//ExecutorService using class
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new Runnable() {
@Override
public void run(a) {
log.info("new runnable"); }});Copy the code
Executorservice.submit needs to receive a Runnable class. In the example above we added a new Runnable class and implemented its run () method.
The above example, if rewritten as a lambda expression, would look like this:
//ExecutorService using lambda
executorService.submit(()->log.info("new runnable"));
Copy the code
As simple as it seems, using lambda expressions can omit the construction of anonymous classes and make them more readable.
So can all anonymous classes be refactored using lambda expressions? Also is not.
Let’s take a look at the Runnable class:
@FunctionalInterface
public interface Runnable
Copy the code
The Runnable class has an @functionalInterface annotation on it. This annotation is the Functional Interface we’ll be talking about today.
2.1 the Functional Interface
A FunctionalInterface is an Interface with the @functional Interface annotation. It is characterized by the fact that only one subclass has to implement the abstract method. If the abstract method is preceded by the default keyword, no calculation is performed.
This makes sense because Functional Interface, when rewritten as a lambda expression, does not specify which method to implement, which can cause problems if there are more than one method to implement.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
Copy the code
Functional Interfaces are typically found in the java.util.function package.
Depending on the method parameters to be implemented and the return value, Functional interfaces can be classified into many different types, which are described below.
2.2 Function: Each parameter has one return value
The Function interface defines a method that takes one parameter and returns one parameter.
@FunctionalInterface
public interface Function<T.R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);Copy the code
Normally when we’re dealing with a collection class, we use Function.
Map<String, Integer> nameMap = new HashMap<>();
Integer value = nameMap.computeIfAbsent("name", s -> s.length());
Copy the code
In the example above, we call the Map’s computeIfAbsent method, passing in a Function.
The above example can be rewritten even shorter:
Integer value1 = nameMap.computeIfAbsent("name", String::length);
Copy the code
Function does not specify the types of arguments and return values. If you need to pass specific arguments, you can use IntFunction, LongFunction, DoubleFunction:
@FunctionalInterface
public interface IntFunction<R> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
R apply(int value);
}
Copy the code
ToIntFunction, ToLongFunction, ToDoubleFunction can be used if a specific parameter needs to be returned:
@FunctionalInterface
public interface ToDoubleFunction<T> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
double applyAsDouble(T value);
}
Copy the code
If you want to specify both parameters and return values, You can use DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction:
@FunctionalInterface
public interface LongToIntFunction {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
int applyAsInt(long value);
}
Copy the code
2.3 BiFunction: Receives two parameters and returns one value
If you need to accept two arguments and a return value, you can use BiFunction: BiFunction, ToDoubleBiFunction, ToIntBiFunction, ToLongBiFunction, etc.
@FunctionalInterface
public interface BiFunction<T.U.R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
Copy the code
Let’s look at an example of BiFunction:
//BiFunction
Map<String, Integer> salaries = new HashMap<>();
salaries.put("alice".100);
salaries.put("jack".200);
salaries.put("mark".300);
salaries.replaceAll((name, oldValue) ->
name.equals("alice")? oldValue : oldValue +200);
Copy the code
2.4 Supplier: Function not included
If none of the parameters are required, Supplier can be used:
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get(a);
}
Copy the code
2.5 Consumer: Receives a parameter and returns no value
A Consumer takes a parameter, but does not return any value. Let’s look at the definition of a Consumer:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
Copy the code
Look at a specific application for Consumer:
//Consumer
nameMap.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));
Copy the code
2.6 Predicate: Takes a parameter and returns Boolean
Predicate takes a parameter and returns a Boolean value:
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
Copy the code
This is great if used for filtering collection classes:
//Predicate
List<String> names = Arrays.asList("A"."B"."C"."D"."E");
List<String> namesWithA = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
Copy the code
2.7 Operator: Receives and returns the same type
An Operator receives and returns the same type. There are many types of operators: UnaryOperator BinaryOperator, DoubleUnaryOperator, IntUnaryOperator, LongUnaryOperator, DoubleBinaryOperator, IntBinaryOperator, LongBinaryOperator, etc.
@FunctionalInterface
public interface IntUnaryOperator {
/**
* Applies this operator to the given operand.
*
* @param operand the operand
* @return the operator result
*/
int applyAsInt(int operand);
Copy the code
Let’s look at an example of a BinaryOperator:
//Operator
List<Integer> values = Arrays.asList(1.2.3.4.5);
int sum = values.stream()
.reduce(0, (i1, i2) -> i1 + i2);
Copy the code
3. Lambda expression best practices
Lambda expressions a functional programming framework introduced by Java 8. We also covered the basic use of Lambda expressions in previous articles.
This article builds on the previous articles and explores the best practices in practical use of Lambda expressions in more detail.
3.1 Use standard Functional interfaces preferentially
As mentioned in the previous article, Java defines many function interfaces in the java.util.function package. It covers almost every type we can think of.
Suppose we customize the following Functional interface:
@FunctionalInterface
public interface Usage {
String method(String string);
}
Copy the code
Then we need to pass that interface in a test method:
public String test(String string, Usage usage) {
return usage.method(string);
}
Copy the code
The function interface we defined above needs to implement method, receive a String, and return a String. We can use Function instead:
public String test(String string, Function<String, String> fn) {
return fn.apply(string);
}
Copy the code
The advantage of using a standard interface is that you don’t have to reinvent the wheel.
3.2 Using the @functionalInterface annotation
Although @functional Interface is not required, you can define a FunctionalInterface without using @functional Interface.
But using @functional Interface can alert you when you violate the FunctionalInterface definition.
If you are maintaining a large project, the @functionalInterface annotation makes it clear to others what the class does.
This makes the code more canonical and more usable.
So we need to define it this way:
@FunctionalInterface
public interface Usage {
String method(String string);
}
Copy the code
Instead of:
public interface Usage {
String method(String string);
}
Copy the code
3.3 Do not abuse Default Methods in Functional Interfaces
A Functional Interface is an Interface that has only one unimplemented abstract method.
If there are more than one method in the Interface, you can provide a default implementation for it using the default keyword.
But we know that interfaces can be multi-inherited, that one class can implement multiple interfaces. If the same default method is defined in multiple interfaces, an error is reported.
In general, the default keyword is used in upgrade projects to avoid code errors.
3.4 Using Lambda expressions to instantiate Functional Interfaces
Again, the example above:
@FunctionalInterface
public interface Usage {
String method(String string);
}
Copy the code
To instantiate Usage, we can use the new keyword:
Usage usage = new Usage() {
@Override
public String method(String string) {
returnstring; }};Copy the code
But the best way to do this is to use a lambda expression:
Usage usage = parameter -> parameter;
Copy the code
3.5 Do not override methods that take Functional Interface as an argument
How do you understand that? Let’s look at the following two methods:
public class ProcessorImpl implements Processor {
@Override
public String process(Callable<String> c) throws Exception {
// implementation details
}
@Override
public String process(Supplier<String> s) {
// implementation details}}Copy the code
The method names of the two methods are the same, only the arguments passed are different. But both arguments are Functional Interface, and both can be represented by the same lambda expression.
At the time of invocation:
String result = processor.process(() -> "test");
Copy the code
Because you can’t tell which method you’re calling, you’ll get an error.
The best thing to do is to change the names of the two methods to be different.
3.6 Lambda expressions and inner classes are different
Although we talked earlier about replacing inner classes with lambda expressions. But the scope is different.
Within the inner class, a new scope is created, within which you can define new variables and reference them with this.
But in a Lambda expression, there is no new scope defined, and if you use this in a Lambda expression, you refer to the outer class.
Here’s an example:
private String value = "Outer scope value";
public String scopeExperiment(a) {
Usage usage = new Usage() {
String value = "Inner class value";
@Override
public String method(String string) {
return this.value; }}; String result = usage.method("");
Usage usageLambda = parameter -> {
String value = "Lambda value";
return this.value;
};
String resultLambda = usageLambda.method("");
return "Results: result = " + result +
", resultLambda = " + resultLambda;
}
Copy the code
Results: result = Inner class value, resultLambda = Outer scope value
3.7 Lambda Expression Be as concise as possible
Usually one line of code. If you have a lot of logic, you can wrap that logic into a method that you call in a lambda expression.
Since a lambda expression is ultimately an expression, the shorter the expression, the better.
Java uses type inference to determine the type of arguments passed in, so we try not to pass the argument types in the arguments of lambda expressions, as follows:
(a, b) -> a.toLowerCase() + b.toLowerCase();
Copy the code
Instead of:
(String a, String b) -> a.toLowerCase() + b.toLowerCase();
Copy the code
If there is only one argument, no parentheses are required:
a -> a.toLowerCase();
Copy the code
Instead of:
(a) -> a.toLowerCase();
Copy the code
Return values do not need to return:
a -> a.toLowerCase();
Copy the code
Instead of:
a -> {return a.toLowerCase()};
Copy the code
3.8 Usage Reference
To make lambda expressions more concise, we can use method references when they are available:
a -> a.toLowerCase();
Copy the code
Can be replaced with:
String::toLowerCase;
Copy the code
3.9 Effectively Final variables
If a non-final variable is referenced ina lambda expression, an error is reported.
What does effectively final mean? This is an almost final meaning. As long as a variable is assigned only once, the compiler will treat it as effectively final.
String localVariable = "Local";
Usage usage = parameter -> {
localVariable = parameter;
return localVariable;
};
Copy the code
In the example above, localVariable is assigned twice, so it is not Effectively Final and will compile an error.
Why do you set it this way? Because lambda expressions are often used in parallel computation, Effectively Final variables can prevent unpredictable changes when multiple threads are accessing the variable simultaneously.
4. Implement if/else logic in stream expressions
In Stream processing, we often run into if/else cases. What do we do with this problem?
Remember that in our previous article, Lambda Best Practices, we said that lambda expressions should be as concise as possible, and do not include bloated business logic in them.
Let’s look at a concrete example.
4.1 Traditional writing methods
If we had a list of 1 to 10, and we wanted to pick out the odd and even numbers, the traditional way of writing it, we would use it like this:
public void inForEach(a){
List<Integer> ints = Arrays.asList(1.2.3.4.5.6.7.8.9.10);
ints.stream()
.forEach(i -> {
if (i.intValue() % 2= =0) {
System.out.println("i is even");
} else {
System.out.println("i is old"); }}); }Copy the code
In the example above, we put the if/else logic into forEach, which is fine, but the code is very bloated.
So let’s see how we can rewrite this.
4.2 use the filter
We can rewrite the if/else logic as two filters:
List<Integer> ints = Arrays.asList(1.2.3.4.5.6.7.8.9.10);
Stream<Integer> evenIntegers = ints.stream()
.filter(i -> i.intValue() % 2= =0);
Stream<Integer> oddIntegers = ints.stream()
.filter(i -> i.intValue() % 2! =0);
Copy the code
With these two filters, use for each in the stream after the filter:
evenIntegers.forEach(i -> System.out.println("i is even"));
oddIntegers.forEach(i -> System.out.println("i is old"));
Copy the code
Okay, so the code is pretty neat.
5. Use stream in map
A Map is a very common collection type in Java. We often need to iterate over a Map to get some value. Java 8 introduced the concept of a Stream, so how do we use a Stream in a Map?
5.1 Basic Concepts
A Map has a key, a value and an Entry that represents the key and the value as a whole.
Create a Map:
Map<String, String> someMap = new HashMap<>();
Copy the code
Get entrySet for Map:
Set<Map.Entry<String, String>> entries = someMap.entrySet();
Copy the code
Get key for map:
Set<String> keySet = someMap.keySet();
Copy the code
Obtain the value of the map:
Collection<String> values = someMap.values();
Copy the code
We can see above that there are several collections: Map, Set, Collection.
Map does not have a stream, but the other two have a stream method:
Stream<Map.Entry<String, String>> entriesStream = entries.stream();
Stream<String> valuesStream = values.stream();
Stream<String> keysStream = keySet.stream();
Copy the code
We can iterate over the map through several other streams.
5.2 Use Stream to obtain map keys
Let’s first add some values to the map:
someMap.put("jack"."20");
someMap.put("bill"."35");
Copy the code
Above we added the name and age fields.
If we want to find a key with age=20, we can do this:
Optional<String> optionalName = someMap.entrySet().stream()
.filter(e -> "20".equals(e.getValue()))
.map(Map.Entry::getKey)
.findFirst();
log.info(optionalName.get());
Copy the code
If the value is Optional, we can handle it if the value does not exist:
optionalName = someMap.entrySet().stream()
.filter(e -> "Non ages".equals(e.getValue()))
.map(Map.Entry::getKey).findFirst();
log.info("{}",optionalName.isPresent());
Copy the code
In the example above, we call isPresent to determine whether age exists.
If there are multiple values, we can write:
someMap.put("alice"."20");
List<String> listnames = someMap.entrySet().stream()
.filter(e -> e.getValue().equals("20"))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
log.info("{}",listnames);
Copy the code
Above we call collect(collators.tolist ()) to convert the value to a List.
5.3 Using stream to obtain the value of a map
We can also retrieve the value of the map:
List<String> listAges = someMap.entrySet().stream()
.filter(e -> e.getKey().equals("alice"))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
log.info("{}",listAges);
Copy the code
Up here we matched the key to Alice’s value.
6. Operation types in Stream and use of Peek
Java 8 Stream as a stream operation has two types of operation, intermediate operation and abort operation. What’s the difference between the two?
Let’s look at an example of Peek:
Stream<String> stream = Stream.of("one"."two"."three"."four");
stream.peek(System.out::println);
Copy the code
In the example above, we intended to print the value of Stream, but there was actually no output.
Why is that?
6.1 Intermediate Operations and Termination Operations
A Java 8 stream consists of three parts. Data source, zero or one or more intermediate operations, one or zero termination operations.
The intermediate operation is the processing of the data. Note that the intermediate operation is lazy and does not start immediately. It will be executed until the operation is terminated.
The termination operation is the start operation of the stream, and only by adding the termination operation will the stream actually start.
So, problem solved, peek is an intermediate operation, so the above example has no output.
6.2 peek
Take a look at peek’s documentation: Peek is primarily used for debug purposes.
Let’s look at the use of the debug function:
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());
Copy the code
The above example output:
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR
Copy the code
In the example above, we output the intermediate value of stream to facilitate debugging.
Why only debug? Let’s look at another example:
Stream.of("one"."two"."three"."four").peek(u -> u.toUpperCase())
.forEach(System.out::println);
Copy the code
In the example above, we use peek to convert an Element into an Upper case. Then output:
one
two
three
four
Copy the code
You can see that the elements in the stream are not converted to uppercase.
Here’s another map comparison:
Stream.of("one"."two"."three"."four").map(u -> u.toUpperCase())
.forEach(System.out::println);
Copy the code
Output:
ONE
TWO
THREE
FOUR
Copy the code
You can see that the map is actually transforming the elements.
There are exceptions for Peek, of course. What if we had an object in the Stream?
@Data
@AllArgsConstructor
static class User{
private String name;
}
Copy the code
List<User> userList=Stream.of(new User("a"),new User("b"),new User("c")).peek(u->u.setName("kkk")).collect(Collectors.toList());
log.info("{}",userList);
Copy the code
Output results:
10:25:59.784 [main] INFO com.flydean.peekusage - [peekusage.user (name= KKK), peekusage.user (name= KKK), PeekUsage.User(name=kkk)]Copy the code
We see that the actual result changes if it’s an object.
Why the difference between Peek and Map?
Let’s look at the definitions of peek and map:
Stream<T> peek(Consumer<? super T> action)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Copy the code
Peek receives a Consumer and Map receives a Function.
Consumer does not return a value. It does something to the elements in the Stream, but the data is not returned to the Stream, so the elements in the Stream remain the same.
Function returns a value, which means that all operations on elements of the Stream are returned to the Stream as a new result.
This is why the Peek String does not change and the Peek Object sends changes.
7. Exception handling in lambda expressions
Lambda expressions were introduced in Java 8. Lambda expressions make our code simpler and our business logic clearer, but Functional Interfaces used in lambda expressions don’t handle exceptions very well. Because the Functional interfaces provided by the JDK generally do not throw exceptions, this means that we need to handle the exceptions ourselves manually.
Since exceptions are divided into Unchecked Exceptions and Checked Exceptions, we’ll discuss them separately.
7.1 Handling Unchecked Exceptions
Unchecked Exceptions are also called runtimeExceptions, which usually occur because there is something wrong with our code. Runtimeexceptions do not need to be caught. That is, if you have a RuntimeException, you can compile without a catch.
Let’s look at an example:
List<Integer> integers = Arrays.asList(1.2.3.4.5);
integers.forEach(i -> System.out.println(1 / i));
Copy the code
The example will compile successfully, but with a ArithmeticException if there is a 0 in the list.
This is an Unchecked Exception, but we still want to handle it:
integers.forEach(i -> {
try {
System.out.println(1 / i);
} catch (ArithmeticException e) {
System.err.println(
"Arithmetic Exception occured : "+ e.getMessage()); }});Copy the code
In the example above, we use try and catch to handle exceptions, which is simple but breaks the best practice for lambda expressions. The code becomes bloated.
We move the try and catch into a wrapper method:
static Consumer<Integer> lambdaWrapper(Consumer<Integer> consumer) {
return i -> {
try {
consumer.accept(i);
} catch (ArithmeticException e) {
System.err.println(
"Arithmetic Exception occured : "+ e.getMessage()); }}; }Copy the code
Then the original call will look like this:
integers.forEach(lambdaWrapper(i -> System.out.println(1 / i)));
Copy the code
But the wrapper fixed catching ArithmeticException above, which we adapted into a more generic class:
static <T, E extends Exception> Consumer<T>
consumerWrapperWithExceptionClass(Consumer<T> consumer, Class<E> clazz) {
return i -> {
try {
consumer.accept(i);
} catch (Exception ex) {
try {
E exCast = clazz.cast(ex);
System.err.println(
"Exception occured : " + exCast.getMessage());
} catch (ClassCastException ccEx) {
throwex; }}}; }Copy the code
The above class is passed a class and cast it to the exception. If it can be cast, it is handled, otherwise the exception is thrown.
After that, we call it like this:
integers.forEach(
consumerWrapperWithExceptionClass(
i -> System.out.println(1 / i),
ArithmeticException.class));
Copy the code
7.2 Handling Checked Exceptions
Checked Exceptions are exceptions that must be handled. Let’s look at an example:
static void throwIOException(Integer integer) throws IOException {}Copy the code
List<Integer> integers = Arrays.asList(1.2.3.4.5);
integers.forEach(i -> throwIOException(i));
Copy the code
Above we defined a method that throws an IOException, which is a checked Exception that needs to be handled, so in the following forEach, the program will fail to compile because the Exception was not handled.
The easiest way to do this is to try and catch, as follows:
integers.forEach(i -> {
try {
throwIOException(i);
} catch (IOException e) {
throw newRuntimeException(e); }});Copy the code
Of course, the disadvantages of this have already been discussed. In the same way, we can define a new wrapper method:
static <T> Consumer<T> consumerWrapper( ThrowingConsumer
throwingConsumer)
,> {
return i -> {
try {
throwingConsumer.accept(i);
} catch (Exception ex) {
throw newRuntimeException(ex); }}; }Copy the code
We call it like this:
integers.forEach(consumerWrapper(i -> throwIOException(i)));
Copy the code
We can also encapsulate exceptions:
static <T, E extends Exception> Consumer<T> consumerWrapperWithExceptionClass( ThrowingConsumer
throwingConsumer, Class
exceptionClass)
,> {
return i -> {
try {
throwingConsumer.accept(i);
} catch (Exception ex) {
try {
E exCast = exceptionClass.cast(ex);
System.err.println(
"Exception occured : " + exCast.getMessage());
} catch (ClassCastException ccEx) {
throw newRuntimeException(ex); }}}; }Copy the code
Then call it like this:
integers.forEach(consumerWrapperWithExceptionClass(
i -> throwIOException(i), IOException.class));
Copy the code
8. Throw Exception in stream
In the previous article, we talked about how to handle exceptions in a stream, transforming Checked Exceptions to Unchecked Exceptions.
Here’s how we did it:
static <T> Consumer<T> consumerWrapper( ThrowingConsumer
throwingConsumer)
,> {
return i -> {
try {
throwingConsumer.accept(i);
} catch (Exception ex) {
throw newRuntimeException(ex); }}; }Copy the code
The exception is caught and then encapsulated as RuntimeException.
Encapsulating a RuntimeException always feels like a little bit of a problem, so is there a better way?
8.1 Throw tips
As you know from Java type inference, if this is the form, then T will be considered a RuntimeException!
Let’s look at an example:
public class RethrowException {
public static <T extends Exception, R> R throwException(Exception t) throws T {
throw (T) t; // just throw it, convert checked exception to unchecked exception}}Copy the code
In the class above, we define a throwException method that takes an Exception argument and converts it to T, which in this case is unchecked Exception.
Here’s how to use it:
@Slf4j
public class RethrowUsage {
public static void main(String[] args) {
try {
throwIOException();
} catch(IOException e) { log.error(e.getMessage(),e); RethrowException.throwException(e); }}static void throwIOException(a) throws IOException{
throw new IOException("io exception"); }}Copy the code
In the example above, we transform an IOException to an unchecked Exception.
9. Usage of stream Collectors
In Java Streams, we usually need to convert a stream to a collection class, so we need to use the stream.collect method. The collect method requires passing in a Collector type, which is cumbersome to implement, requiring several interfaces.
Java therefore provides a simpler tool class, the Collectors, to facilitate building Collectors.
We will talk more about the use of Collectors.
Suppose we have two lists like this:
List<String> list = Arrays.asList("jack"."bob"."alice"."mark");
List<String> duplicateList = Arrays.asList("jack"."jack"."alice"."mark");
Copy the code
The list above is a list with no duplicates, and the list with duplicates. In the following example, we will use the two lists above to illustrate the use of Collectors.
9.1 Collectors. ToList ()
List<String> listResult = list.stream().collect(Collectors.toList());
log.info("{}",listResult);
Copy the code
Convert stream to list. The converted list here is an ArrayList, and if you want to convert to a specific list, you need to use the toCollection method.
9.2 Collectors. ToSet ()
Set<String> setResult = list.stream().collect(Collectors.toSet());
log.info("{}",setResult);
Copy the code
ToSet converts Stream toSet. This is a HashSet. If you need to specify a set specifically, you need to use the toCollection method.
Because there is no duplicate element in set, if we use duplicateList to transform, we will find that there is only one Jack in the final result.
Set<String> duplicateSetResult = duplicateList.stream().collect(Collectors.toSet());
log.info("{}",duplicateSetResult);
Copy the code
9.3 Collectors. ToCollection ()
ToMap,toSet, toCollection(), toCollection(), toCollection(), toCollection()
List<String> custListResult = list.stream().collect(Collectors.toCollection(LinkedList::new));
log.info("{}",custListResult);
Copy the code
In the example above, we converted to LinkedList.
9.4 Collectors. ToMap ()
ToMap takes two arguments, the first is keyMapper and the second is valueMapper:
Map<String, Integer> mapResult = list.stream()
.collect(Collectors.toMap(Function.identity(), String::length));
log.info("{}",mapResult);
Copy the code
If there are duplicate values in the stream, the transformation will report an IllegalStateException:
Map<String, Integer> duplicateMapResult = duplicateList.stream()
.collect(Collectors.toMap(Function.identity(), String::length));
Copy the code
How to solve this problem? We can do this:
Map<String, Integer> duplicateMapResult2 = duplicateList.stream()
.collect(Collectors.toMap(Function.identity(), String::length, (item, identicalItem) -> item));
log.info("{}",duplicateMapResult2);
Copy the code
Add a third parameter, mergeFunction, to toMap to resolve the conflict.
9.5 Collectors. CollectingAndThen ()
CollectingAndThen allows us to do one more operation on the generated collection.
List<String> collectAndThenResult = list.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), l -> {return newArrayList<>(l); })); log.info("{}",collectAndThenResult);
Copy the code
9.6 Collectors. Joining ()
Joining is used to join elements in a stream:
String joinResult = list.stream().collect(Collectors.joining());
log.info("{}",joinResult);
String joinResult1 = list.stream().collect(Collectors.joining(""));
log.info("{}",joinResult1);
String joinResult2 = list.stream().collect(Collectors.joining(""."prefix"."suffix"));
log.info("{}",joinResult2);
Copy the code
You can have no arguments, you can have one argument, you can have three arguments, depending on what you want.
9.7 Collectors. Counting ()
Counting is mainly used to count the number of elements in stream:
Long countResult = list.stream().collect(Collectors.counting());
log.info("{}",countResult);
Copy the code
9.8 Collectors. SummarizingDouble/Long/Int ()
SummarizingDouble/Long/Int generate the statistics for the elements in the stream, the returned result is a statistics class:
IntSummaryStatistics intResult = list.stream()
.collect(Collectors.summarizingInt(String::length));
log.info("{}",intResult);
Copy the code
Output results:
22:22:35. [the main] INFO 238 com. Flydean. CollectorUsage - IntSummaryStatistics {count = 4, sum = 16, min = 3, business = 4.000000, max=5}Copy the code
9.9 Collectors. AveragingDouble/Long/Int ()
AveragingDouble /Long/Int()
Double averageResult = list.stream().collect(Collectors.averagingInt(String::length));
log.info("{}",averageResult);
Copy the code
9.10 Collectors. SummingDouble/Long/Int ()
SummingDouble /Long/Int()
Double summingResult = list.stream().collect(Collectors.summingDouble(String::length));
log.info("{}",summingResult);
Copy the code
9.11 Collectors. MaxBy ()/minBy ()
MaxBy ()/minBy() returns the maximum or minimum value in stream, depending on the supplied Comparator:
Optional<String> maxByResult = list.stream().collect(Collectors.maxBy(Comparator.naturalOrder()));
log.info("{}",maxByResult);
Copy the code
9.12 Collectors. GroupingBy ()
GroupingBy groups according to certain attributes and returns a Map:
Map<Integer, Set<String>> groupByResult = list.stream()
.collect(Collectors.groupingBy(String::length, Collectors.toSet()));
log.info("{}",groupByResult);
Copy the code
9.13 Collectors. PartitioningBy ()
PartitioningBy is a special groupingBy. PartitioningBy returns a Map with a Boolean key that divides the stream into two parts, one that matches the PartitioningBy condition, Some of them don’t meet the criteria:
Map<Boolean, List<String>> partitionResult = list.stream()
.collect(Collectors.partitioningBy(s -> s.length() > 3));
log.info("{}",partitionResult);
Copy the code
Here’s the result:
22:39:37. [the main] INFO 082 com. Flydean. CollectorUsage - {, true or false = [Bob] = [jack, Alice, mark]}Copy the code
The results were split in two.
Create a custom Collector
Earlier in the Java Collectors article, we explained that the collect method of stream can call the toList() or toMap() methods of the Collectors method to convert the result to a specific collection class.
Today we’ll show you how to customize a Collector.
10.1 the Collector is introduced
Let’s look at the definition of Collector:
The Collector interface needs to implement the five interfaces: Supplier (), Accumulator (), Combiner (), Finisher (), and Accumulator ().
Collector also provides two static of methods to make it easy to create a Collector instance.
We can see that the parameters of the two methods correspond one-to-one to the interface that the Collector interface needs to implement.
Each of these parameters is explained below:
- supplier
Supplier is a function that creates a new mutable collection. In other words Supplier is used to create an initial collection.
- accumulator
Accumulator The accumulator defines an accumulator that is used to add primitive elements to the collection.
- combiner
The combiner is used to combine two sets into one.
- finisher
A finisher converts a collection to the final collection type.
- characteristics
What are the characteristics of this set? This is not a required parameter.
Collector defines three parameter types: T is the type of the input element, A is the cumulative type of the Reduction operation (Supplier’s initial type), and R is the final return type. Let’s draw a diagram to see the conversion relationship between these types:
With these parameters in hand, let’s look at how to use them to construct a custom Collector.
10.2 User-defined Collector
We use the Collector’s of method to create an immutable Set:
public static <T> Collector<T, Set<T>, Set<T>> toImmutableSet() {
return Collector.of(HashSet::new, Set::add,
(left, right) -> {
left.addAll(right);
return left;
}, Collections::unmodifiableSet);
}
Copy the code
The above example, we HashSet: : new as: supplier, Set: : add as accumulator, the custom of a method as a combiner, finally use the Collections: : unmodifiableSet will be Set into the immutable.
In fact, the above method can be more general:
public static <T, A extends Set<T>> Collector<T, A, Set<T>> toImmutableSet(
Supplier<A> supplier) {
return Collector.of(
supplier,
Set::add, (left, right) -> {
left.addAll(right);
return left;
}, Collections::unmodifiableSet);
}
Copy the code
In the above method, we take supplier as a parameter and define it externally.
Take a look at the tests for the above two methods:
@Test
public void toImmutableSetUsage(a){
Set<String> stringSet1=Stream.of("a"."b"."c"."d")
.collect(ImmutableSetCollector.toImmutableSet());
log.info("{}",stringSet1);
Set<String> stringSet2=Stream.of("a"."b"."c"."d")
.collect(ImmutableSetCollector.toImmutableSet(LinkedHashSet::new));
log.info("{}",stringSet2);
}
Copy the code
Output:
INFO com.flydean.ImmutableSetCollector - [a, b, c, d]
INFO com.flydean.ImmutableSetCollector - [a, b, c, d]
Copy the code
11. Stream reduce detailed explanation and misunderstanding
The Stream API provides predefined reduce operations such as count(), Max (), min(), sum(), and so on. If we need to write the reduce logic ourselves, we can use the Reduce method.
This article will examine the use of the Reduce method in detail and give specific examples.
11.1 the reduce,
There are three types of reduce in the Stream class that accept 1 argument, 2 arguments, and 3 arguments.
Optional<T> reduce(BinaryOperator<T> accumulator);
Copy the code
This method takes a BinaryOperator argument. BinaryOperator is an @functionalInterface and needs to implement the method:
R apply(T t, U u);
Copy the code
Accumulator tells the Reduce method how to accumulate the data in stream.
Here’s an example:
List<Integer> intList = Arrays.asList(1.2.3);
Optional<Integer> result1=intList.stream().reduce(Integer::sum);
log.info("{}",result1);
Copy the code
The above example outputs:
com.flydean.ReduceUsage - Optional[6]
Copy the code
The one parameter example is simple. No more here.
Let’s look at an example with two parameters:
T reduce(T identity, BinaryOperator<T> accumulator);
Copy the code
This method takes two parameters: identity and Accumulator. There is an extra parameter identity.
You may have been told in some articles that identity is the reduce initialization value and can be specified as follows:
Integer result2=intList.stream().reduce(100, Integer::sum);
log.info("{}",result2);
Copy the code
In the example above, we calculated a value of 106.
If we change stream to parallelStream:
Integer result3=intList.parallelStream().reduce(100, Integer::sum);
log.info("{}",result3);
Copy the code
So that’s 306.
Why 306? Because in parallel, the initial sum of each thread is 100, and the final three threads add up to 306.
Parallel and non-parallel computing results are not the same. This is not a JDK problem.
Accumulator.apply (identity, t) == t for all t
So it’s not right to pass in 100, because sum (100+1)! = 1.
Where the identity of the sum method can only be 0.
If we use 0 as identity, stream and parallelStream will compute the same. That’s what identity is really about.
Let’s look at the method with three parameters:
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
Copy the code
Instead of the previous method, there is a new Combiner, which combines the results of multi-threaded calculations.
Combiner.apply (u, accumulator.apply(u, t)) == Accumulator.apply (u, t)
Accumulator is BiFunction and combiner is BinaryOperator.
public interface BinaryOperator<T> extends BiFunction<T.T.T>
Copy the code
BinaryOperator is a subinterface of BiFunction. BiFunction defines the apply method to be implemented.
In fact, the implementation of the lower reduce method only uses the apply method, and does not use other methods in the interface, so I guess the difference here is just for simple distinction.
Although Reduce is a common approach, it is important to follow the identity specification, not all identities are appropriate.
12. Spliterator in stream
Spliterator is an interface introduced in Java 8 that is commonly used with streams to traverse and split sequences.
Spliterator is needed wherever stream is used, such as List, Collection, IO channel, etc.
Let’s first look at the definition of the stream method in a Collection:
default Stream<E> stream(a) {
return StreamSupport.stream(spliterator(), false);
}
Copy the code
default Stream<E> parallelStream(a) {
return StreamSupport.stream(spliterator(), true);
}
Copy the code
As you can see, both parallel and non-parallel streams are constructed using StreamSupport and need to pass a spliterator argument.
Ok, now that we know what a spliterator does, let’s look at its structure:
Spliterator has four methods that must be implemented, which we’ll discuss in detail next.
12.1 tryAdvance
TryAdvance is what happens to an element in the stream, and returns true if it exists, false otherwise.
If we don’t want to handle the elements that follow the stream, we can simply return false in tryAdvance. Using this feature, we can interrupt processing of the stream. This is an example that I will cover in a later article.
12.2 trySplit
TrySplit tries to split an existing stream, usually in the case of parallelStream, because in the case of concurrent streams, we need to use multiple threads to process the different elements of the stream. TrySplit is a method of splitting the elements of the stream.
Ideally, trySplit should split the stream into two equal parts to maximize performance.
12.3 estimateSize
EstimateSize represents the elements to be processed in Spliterator, and is generally different before and after trySplit, as we’ll see in a specific example later.
12.4 characteristics
Characteristics represent the characteristics of the Spliterator. There are eight characteristics of the Spliterator:
public static final int ORDERED = 0x00000010;// indicates that the elements are ordered (the same result every time)
public static final int DISTINCT = 0x00000001;// indicates that elements are not repeated
public static final int SORTED = 0x00000004;// indicates that the elements are arranged in a certain order (with a specified comparator)
public static final int SIZED = 0x00000040;//That means the size is fixedpublic static final int NONNULL = 0x00000100;// There is no null element
public static final int IMMUTABLE = 0x00000400;// Indicates that the element is immutable
public static final int CONCURRENT = 0x00001000;// indicates that iterators can be operated by multiple threads
public static final int SUBSIZED = 0x00004000;// Indicates that child Spliterators have the SIZED feature
Copy the code
A Spliterator can have multiple features, which can be performed with or operation to obtain the final characteristics.
12.5 Give an Example
We discussed some key methods of Spliterator above, now let’s take a concrete example:
@AllArgsConstructor
@Data
public class CustBook {
private String name;
}
Copy the code
Define a CustBook class with a name variable in it.
Define a method to generate a list of custBooks:
public static List<CustBook> generateElements(a) {
return Stream.generate(() -> new CustBook("cust book"))
.limit(1000)
.collect(Collectors.toList());
}
Copy the code
We define a call method and call tryAdvance in the call method, passing in our custom handling method. Here we change the name of book and add additional information.
public String call(Spliterator<CustBook> spliterator) {
int current = 0;
while (spliterator.tryAdvance(a -> a.setName("test name"
.concat("- add new name")))) {
current++;
}
return Thread.currentThread().getName() + ":" + current;
}
Copy the code
Finally, write the test method:
@Test
public void useTrySplit(a){
Spliterator<CustBook> split1 = SpliteratorUsage.generateElements().spliterator();
Spliterator<CustBook> split2 = split1.trySplit();
log.info("before tryAdvance: {}",split1.estimateSize());
log.info("Characteristics {}",split1.characteristics());
log.info(call(split1));
log.info(call(split2));
log.info("after tryAdvance {}",split1.estimateSize());
}
Copy the code
The result of this operation is as follows:
23:10:08. [the main] INFO 852 com. Flydean. SpliteratorUsage - before tryAdvance: 500 23:10:08. [the main] INFO 857 com. Flydean. SpliteratorUsage - 16464 23:10:08 Characteristics. [the main] 858 INFO Com. Flydean. SpliteratorUsage - the main: 500 23:10:08. 858 [main] INFO com. Flydean. SpliteratorUsage - the main: 500 23:10:08. 858 [main] INFO com.flydean.SpliteratorUsage - after tryAdvance 0Copy the code
The List has a total of 1000 pieces of data. After a call to trySplit, the List is divided into two parts with 500 pieces of data each.
Notice that after the tryAdvance call, estimateSize turns to 0, indicating that all elements have been processed.
Characteristics=16464; Ox4050 = ORDERED or SIZED or subsidzed;
This is also the basic feature of ArrayList.
13. Break stream’s foreach
We usually need to iterate through the data in a Java Stream, and foreach is the most common method.
But sometimes we don’t want to process all the data, or sometimes the Stream can be very long, or infinite at all.
One approach is to filter out the data we need to process and then foreach through it.
So how do we break the stream directly? Today’s article focuses on that question.
13.1 use Spliterator
In the last article we talked about Spliterator. In the tryAdvance method, if it returns false, the Spliterator will stop processing subsequent elements.
With this in mind, we can create a custom Spliterator.
Suppose we have a stream like this:
Stream<Integer> ints = Stream.of(1.2.3.4.5.6.7.8.9.10);
Copy the code
We want to define an operation that stops when x is greater than 5.
We define a generic Spliterator:
public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
private Spliterator<T> splitr;
private Predicate<T> predicate;
private volatile boolean isMatched = true;
public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {
super(splitr.estimateSize(), 0);
this.splitr = splitr;
this.predicate = predicate;
}
@Override
public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem) && isMatched) {
consumer.accept(elem);
} else {
isMatched = false; }});returnhadNext && isMatched; }}Copy the code
In the class above, predicate is the predicate we’re going to pass in, and we’ve overwritten tryAdvance by including the predicate, Predicate.test(elem), so that it returns false if the condition is not met.
Here’s how it works:
@Slf4j
public class CustomSpliteratorUsage {
public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
return StreamSupport.stream(customSpliterator, false);
}
public static void main(String[] args) {
Stream<Integer> ints = Stream.of(1.2.3.4.5.6.7.8.9.10);
List<Integer> result =
takeWhile(ints, x -> x < 5) .collect(Collectors.toList()); log.info(result.toString()); }}Copy the code
We define a takeWhile method that accepts the Stream and predicate conditions.
The predicate continues only when the predicate condition is met, so let’s see what the output looks like:
[main] INFO com.flydean.CustomSpliteratorUsage - [1.2.3.4]
Copy the code
13.2 Customizing forEach Methods
In addition to using Spliterator, we can also customize the forEach method to use our own traversal logic:
public class CustomForEach {
public static class Breaker {
private volatile boolean shouldBreak = false;
public void stop(a) {
shouldBreak = true;
}
boolean get(a) {
returnshouldBreak; }}public static <T> void forEach(Stream<T> stream, BiConsumer<T, Breaker> consumer) {
Spliterator<T> spliterator = stream.spliterator();
boolean hadNext = true;
Breaker breaker = new Breaker();
while(hadNext && ! breaker.get()) { hadNext = spliterator.tryAdvance(elem -> { consumer.accept(elem, breaker); }); }}}Copy the code
In the example above, we introduced an external variable into the forEach to determine whether to enter spliterator.tryadvance.
Here’s how it works:
@Slf4j
public class CustomForEachUsage {
public static void main(String[] args) {
Stream<Integer> ints = Stream.of(1.2.3.4.5.6.7.8.9.10);
List<Integer> result = new ArrayList<>();
CustomForEach.forEach(ints, (elem, breaker) -> {
if (elem >= 5 ) {
breaker.stop();
} else{ result.add(elem); }}); log.info(result.toString()); }}Copy the code
Above, we use the new forEach method and reset the judgment flag through the judgment condition to achieve the purpose of breaking the stream.
14. Use of predicate chain
Predicate is a FunctionalInterface that represents a method that takes an argument and returns a Boolean. Usually used in a stream filter to indicate whether the filter conditions are met.
boolean test(T t);
Copy the code
14.1 Basic Usage
Predicate can be used as a filter for a stream:
@Test
public void basicUsage(a){
List<String> stringList=Stream.of("a"."b"."c"."d").filter(s -> s.startsWith("a")).collect(Collectors.toList());
log.info("{}",stringList);
}
Copy the code
The example above is so basic that I won’t go into it here.
14.2 Using Multiple Filters
If we have multiple Predicate conditions, we can use multiple filters to filter for them:
public void multipleFilters(a){
List<String> stringList=Stream.of("a"."ab"."aac"."ad").filter(s -> s.startsWith("a"))
.filter(s -> s.length()>1)
.collect(Collectors.toList());
log.info("{}",stringList);
}
Copy the code
In the example above, we add a filter, and in the filter we add a Predicate.
14.3 Using Compound Predicate
Predicate is defined by taking a parameter and returning a Boolean value, so if we have multiple test conditions, we can combine them into a test method:
@Test
public void complexPredicate(a){
List<String> stringList=Stream.of("a"."ab"."aac"."ad")
.filter(s -> s.startsWith("a") && s.length()>1)
.collect(Collectors.toList());
log.info("{}",stringList);
}
Copy the code
In the example above, we use s.tartswith (“a”) && s.length()>1 as the implementation of test.
14.4 combination Predicate
Although the Predicate is an interface, there are several default ways for you to combine the predicates.
For example, Predicate.and(), Predicate.or(), and Predicate.negate().
Here’s their example:
@Test
public void combiningPredicate(a){
Predicate<String> predicate1 = s -> s.startsWith("a");
Predicate<String> predicate2 = s -> s.length() > 1;
List<String> stringList1 = Stream.of("a"."ab"."aac"."ad")
.filter(predicate1.and(predicate2))
.collect(Collectors.toList());
log.info("{}",stringList1);
List<String> stringList2 = Stream.of("a"."ab"."aac"."ad")
.filter(predicate1.or(predicate2))
.collect(Collectors.toList());
log.info("{}",stringList2);
List<String> stringList3 = Stream.of("a"."ab"."aac"."ad")
.filter(predicate1.or(predicate2.negate()))
.collect(Collectors.toList());
log.info("{}",stringList3);
}
Copy the code
In fact, we don’t need to show you a predicate, as long as a lambda expression satisfies the predicate interface, you can call it a predicate. The and, or, and negate operations can also be called:
List<String> stringList4 = Stream.of("a"."ab"."aac"."ad")
.filter(((Predicate<String>)a -> a.startsWith("a"))
.and(a -> a.length() > 1))
.collect(Collectors.toList());
log.info("{}",stringList4);
Copy the code
14.5 Set operations for Predicate
If we have a set of Predicate, we can use the reduce method to merge them:
@Test
public void combiningPredicateCollection(a){
List<Predicate<String>> allPredicates = new ArrayList<>();
allPredicates.add(a -> a.startsWith("a"));
allPredicates.add(a -> a.length() > 1);
List<String> stringList = Stream.of("a"."ab"."aac"."ad")
.filter(allPredicates.stream().reduce(x->true, Predicate::and))
.collect(Collectors.toList());
log.info("{}",stringList);
}
Copy the code
In the example above, we call the reduce method to operate on the Predicate in the collection.
15. Build an infinite stream in
In Java, we can convert a particular collection to a stream, so in some cases, like in a test environment, we need to construct a stream with a certain number of elements, what do we do?
Here we can build an infinite stream and then call the limit method to limit the number of returns.
15.1 Basic Usage
Let’s start with an example of creating an infinite Stream using stream.iterate:
@Test
public void infiniteStream(a){
Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 1);
List<Integer> collect = infiniteStream
.limit(10)
.collect(Collectors.toList());
log.info("{}",collect);
}
Copy the code
In the example above, we create a 0,1,2,3,4…. by calling the stream. iterate method Infinite stream.
Then call LIMIT (10) to get the first 10 of them. Finally, the collect method is called to transform it into a collection.
Look at the output:
INFO com.flydean.InfiniteStreamUsage - [0.1.2.3.4.5.6.7.8.9]
Copy the code
15.2 User-defined Type
What if we want to output a collection of custom types?
First, we define a custom type:
@Data
@AllArgsConstructor
public class IntegerWrapper {
private Integer integer;
}
Copy the code
Then create this custom type using the generator from stream.generate:
public static IntegerWrapper generateCustType(a){
return new IntegerWrapper(new Random().nextInt(100));
}
@Test
public void infiniteCustType(a){
Supplier<IntegerWrapper> randomCustTypeSupplier = InfiniteStreamUsage::generateCustType;
Stream<IntegerWrapper> infiniteStreamOfCustType = Stream.generate(randomCustTypeSupplier);
List<IntegerWrapper> collect = infiniteStreamOfCustType
.skip(10)
.limit(10)
.collect(Collectors.toList());
log.info("{}",collect);
}
Copy the code
Look at the output:
INFO com.flydean.InfiniteStreamUsage - [IntegerWrapper(integer=46), IntegerWrapper(integer=42), IntegerWrapper(integer=67), IntegerWrapper(integer=11), IntegerWrapper(integer=14), IntegerWrapper(integer=80), IntegerWrapper(integer=15), IntegerWrapper(integer=19), IntegerWrapper(integer=72), IntegerWrapper(integer=41)]
Copy the code
16. Customize thread pool for parallelStream
By default, the ForkJoinPool creates one thread per processor, and parallelStream creates one thread per processor if not specified. Will use this shared thread pool to submit tasks.
What if we want to use a custom ForkJoinPool?
16.1 Common Operations
If we want to do an addition from 1 to 1000, we can do this with parallel stream:
List<Integer> integerList= IntStream.range(1.1000).boxed().collect(Collectors.toList());
ForkJoinPool customThreadPool = new ForkJoinPool(4);
Integer total= integerList.parallelStream().reduce(0, Integer::sum);
log.info("{}",total);
Copy the code
Output results:
INFO com.flydean.CustThreadPool - 499500
Copy the code
16.2 Using a Customized ForkJoinPool
The example above uses the shared Thread pool. Let’s see how to commit a parallel stream using a custom thread pool:
List<Integer> integerList= IntStream.range(1.1000).boxed().collect(Collectors.toList());
ForkJoinPool customThreadPool = new ForkJoinPool(4);
Integer actualTotal = customThreadPool.submit(
() -> integerList.parallelStream().reduce(0, Integer::sum)).get();
log.info("{}",actualTotal);
Copy the code
In the example above, we defined a four-thread ForkJoinPool and used it to commit the parallelStream.
Output results:
INFO com.flydean.CustThreadPool - 499500
Copy the code
If you do not want to use a common thread pool, you can use a custom ForkJoinPool to commit.
17. To summarize
This article provides a unified introduction to the use of Stream and lambda expressions, covering the small details of both Stream and lambda expressions. I hope you enjoyed it.
The code for this article is github.com/ddean2009/l…
This article PDFjava – stream – lambda – all – in – one. PDF
The most popular interpretation, the most profound dry goods, the most concise tutorial, many you do not know the small skills waiting for you to find!
Welcome to follow my public number: “procedures those things”, understand technology, more understand you!