2017年10月19日 星期四

[ Java 文章收集 ] An Introduction to Functional Programming in Java 8: Part 3 - Streams

Source From Here 
Preface 
In the last part, we’ve learned about the Optional type and how to use it correctly. Today, we will learn about Streams, which you use as an functional alternative of working with Collections. Some method were already seen when we used Optionals, so be sure to check out the part about Optionals. 

Where do we use Streams? 
You might ask what’s wrong with the current way to store multiple objects? Why shouldn’t you use Lists, Sets and so on anymore? I want to point out: Nothing is wrong with them. But when you want to work functional (what you hopefully want after the last parts of this blog), you should consider using them. The standard workflow is to convert your data structure into a Stream. Then you want to work on them in a functional manner and in the end, you transform them back into the data structure of your choice

And that’s the reason we will learn to transform the most common data structures into streams. 

Streams are a wonderful new way to work with data collections. They were introduced in Java 8. One of the many reasons you should use them is the Cascade pattern that Streams use. This basically means that almost every Stream method returns the Stream again, so you can continue to work with it. In the next sections, you will see how this works and that it makes the code nicer. 

Streams are also immutable. So every time you manipulate it, you create a new Stream. Another nice thing about them is that they respect the properties of fP. If you convert a Data Structure into a Stream and work on it, the original data structure won’t be changed. So no side effects here! 

How to Convert Data Structures into Streams 

Convert Multiple Objects into a Stream 
If you want to make a Stream out of some objects, you can use the method Stream.of()
  1. public void convertObjects() {  
  2.     Stream objectStream = Stream.of("Hello""World");  
  3. }  
Converting Collections (Lists, Sets, …) and Arrays 
Luckily, Oracle has thought through the implementation of Streams in Java 8. Every Class that implements java.util.Collection has a new method called stream() which converts the collection into a Stream. Also Arrays can be converted easily with Arrays.stream(array). It’s as easy as it get’s. 
  1. public void convertStuff() {  
  2.     String[] array = {"apple""banana"};  
  3.     Set emptySet = new HashSet<>();  
  4.     List emptyList = new LinkedList<>();  
  5.   
  6.     Stream arrayStream = Arrays.stream(array);  
  7.     Stream setStream = emptySet.stream();  
  8.     Stream listStream = emptyList.stream();  
  9. }  
However, normally you won’t store a Stream in an object. You just work with them and convert them back into your desired data structure. 

Working with Streams 
As I already said, Streams are the way to work with data structures functional. And now we will learn about the most common methods to use. As a side note: In the next sections, I will use T as the type of the objects in the Stream. 

Methods we already know 
You can use some methods we already heard about when we learned about Optionals also with Streams. 

map 
This works pretty straight forward. Instead of manipulating one item, which might be in the Optional, we manipulate all items in a stream. So if you have a function that squares a number, you can use map to use this function over multiple numbers without writing a new function for lists. 
  1. public void showMap() {  
  2.     Stream.of(123)  
  3.         .map(num -> num * num)  
  4.         .forEach(System.out::println); // 1 4 9  
  5. }  
flatMap 
Like with Optionals, we use flatMap to going e.g from a Stream> to Stream. If you want to know more about look into part 2. Here, we want to concat multiple Lists into one big List. 
  1. public void showFlatMapLists() {  
  2.     List numbers1 = Arrays.asList(123);  
  3.     List numbers2 = Arrays.asList(456);  
  4.   
  5.     Stream.of(numbers1, numbers2) //Stream>  
  6.         .flatMap(List::stream)  //Stream  
  7.         .forEach(System.out::println); // 1 2 3 4 5 6  
  8. }  
Common Stream methods 
forEach 
The forEach method is like the ifPresent method from Optionals, so you use it when you have side effects. As already shown, you use it to e.g. print all objects in a stream. forEach is one of the few Stream methods that doesn’t return the Stream, so you use it as the last method of a Stream and only once. You should be careful when using forEach, because it causes side effects which we don’t want to have. So think twice if you could replace it with another method without side effects
  1. public void showForEach() {  
  2.     Stream.of(0123)  
  3.         .forEach(System.out::println); // 0 1 2 3  
  4. }  
filter 
Filter is a really basic method. It takes a ‘test’ function that takes a value and returns boolean. So it test every object in the Stream. If it passes the test, it will stay in the Stream or otherwise, it will be taken out. This ‘test’ function has the type Function. In the JavaDoc, you will see that the test function really is of the type Predicate. But this is just a short form for every function that takes one parameter and returns a boolean. 
  1. public void showFilter() {  
  2.     Stream.of(0123)  
  3.         .filter(num -> num < 2)  
  4.         .forEach(System.out::println); // 0 1  
  5. }  
Functions that can make your life way easier when creating ‘test’ functions are Predicate.negate() and Objects.nonNull(). The first one basically negates the test. Every object which doesn’t pass the original test will pass the negated test and vice versa. The second one can be used as a method reference to get rid of every null object in the Stream. This will help you to prevent NullPointerExeptions when e.g. mapping functions. 
  1. public void negateFilter() {  
  2.     Predicate small = num -> num < 2;  
  3.   
  4.     Stream.of(0123)  
  5.         .filter(small.negate()) // Now every big number passes  
  6.         .forEach(System.out::println); // 2 3  
  7. }  
  8.   
  9. public void filterNull() {  
  10.     Stream.of(01null3)  
  11.         .filter(Objects::nonNull)  
  12.         .map(num -> num * 2// without filter, you would've got a NullPointerExeception  
  13.         .forEach(System.out::println); // 0 2 6  
  14. }  
collect 
As I already said, you want to transform your stream back into another data structure. And that is what you use collect for. And most of the times, you convert it into a List or a Set
  1. public void showCollect() {  
  2.     List filtered = Stream.of(0123)  
  3.         .filter(num -> num < 2)  
  4.         .collect(Collectors.toList());  
  5. }  
But you can use collect for much more. For example, you can join Strings. Therefore, you don’t have the nasty delimiter in the end of the string. 
  1. public void showJoining() {  
  2.     String sentence = Stream.of("Who""are""you?")  
  3.         .collect(Collectors.joining(" "));  
  4.   
  5.     System.out.println(sentence); // Who are you?  
  6. }  
Shortcuts 
These are methods which you could mostly emulate by using mapfilter and collect, but these shortcut method are meant to be used because they declutter your code. 

reduce 
Reduce is a very cool function. It takes a start parameter of type T and a Function of type BiFunction. If you have a BiFunction where all types are the same, BinaryOperator is a shortcut for that. 

It basically stores all objects in the stream to one object. You can concat all Strings into one String, sum all numbers and so on. There, your start parameters would be the empty String or zero. This function helps a lot to make your code more readable if you know how to use it. 
  1. public void showReduceSum() {  
  2.     Integer sum = Stream.of(123)  
  3.         .reduce(0, Integer::sum);  
  4.   
  5.     System.out.println(sum); // 6  
  6. }  
Now I will give a little bit more information how reduce works. Here, it sums the first number with the sum of the second and the sum of the third and start parameter. As you can see, this produces a long chain of functions. In the end, we have sum(1, sum(2, sum(3, 0))). And this will be computed from right to left, or from the inside out. This is also the reason we need a start parameter, because otherwise the chain would’nt have a point where it could end. 

sorted 
You can also use Streams to sort your data structure. The class type of the objects in the Stream doesn’t even have to implement Comperable, because you can write your own Comperable. This is basically a BiFunction, but Comperator is a shortcut for all BiFunctions that take 2 arguments of the same type and return an int. 

And this int, like in the compareTo() function, shows us if the first object is “smaller” than the second one (int < 0), is as big as the second (int == 0), or is bigger than the second one (int > 0). The sorted function of the Stream will interpret these ints and will sort the elements with the help of them. 
  1. public void showSort() {  
  2.     Stream.of(3240)  
  3.         .sorted((c1, c2) -> c1 - c2)  
  4.         .forEach(System.out::println); // 0 2 3 4  
  5. }  
Other Kinds of Streams 
There are also special types of Streams which only contains numbers. With these new Streams you also have a new set of methods. Here, I will introduce IntStream and Sum, but there are also LongStream, DoubleStream,… . You can read more about them in the JavaDoc. To convert a normal Stream into an IntStream, you have to use mapToInt. It does exactly the same as the normal map, but you get a IntStream back. Of course, you have to give the mapToInt function another function which will return an int. 

In the example, I will show you how to sum numbers without reduce, but with an IntStream
  1. public void sumWithIntStream() {  
  2.     Integer sum = Stream.of(0123)  
  3.         .mapToInt(num -> num)  
  4.         .sum();  
  5. }  
Use Streams for Tests 
Tests can also be used to test your methods. I will use the method anyMatch here, but countmax and so on can help you too. If something goes wrong in your program, use peek to log data. It’s like forEach, but also returns the stream. As always, look into the JavaDoc to find other cool methods. 

anyMatch is a little bit like filter, but it tells you if anything passes the filter. You can use this in assertTrue() tests, where you just want to look if at least one object has a specific property. In the next example, I will test if a specific name was stored in the DB. 
  1. @Test  
  2. public void testIfNameIsStored() {  
  3.     String testName = "Albert Einstein";  
  4.   
  5.     Datebase names = new Datebase();  
  6.     names.drop();  
  7.   
  8.     db.put(testName);  
  9.     assertTrue(db.getData()  
  10.         .stream()  
  11.         .anyMatch(name -> name.equals(testName)));  
  12. }  
Shortcuts of Shortcuts 
And after I showed you some shortcut methods, I want to tell you that there are many more. There are even shortcuts of shortcuts! One Example would be forEachOrdered, which combines forEach and sorted. If you are interested in other helpful methods, look into the JavaDoc. I’m sure you are prepared to understand it and find the methods that you need. Always remember: If your code looks ugly, there’s a better method to use ;). 

A Bigger Example 
In this example, we want to send a message to every user whose birthday’s today. 

The User Class 
A user is defined by their username and birthday. The birthdays will be in the format “day.month.year”, but we won’t do much checking for this in today’s example. 
  1. public class User {  
  2.   
  3.     private String username;  
  4.     private String birthday;  
  5.   
  6.     public User(String username, String birthday) {  
  7.         this.username = username;  
  8.         this.birthday = birthday;  
  9.     }  
  10.   
  11.     public String getUsername() {  
  12.         return username;  
  13.     }  
  14.   
  15.     public String getBirthday() {  
  16.         return birthday;  
  17.     }  
  18.   
  19. }  
To store all users, we will use a List here. In a real program, you might want to switch to a DB. 
  1. public class MainClass {  
  2.   
  3.     public static void main() {  
  4.         List users = new LinkedList<>();  
  5.   
  6.         User birthdayChild = new User("peter""20.02.1990");  
  7.         User otherUser = new User("kid""23.02.2008");  
  8.         User birthdayChild2 = new User("bruce""20.02.1980");  
  9.   
  10.         users.addAll(Arrays.asList(birthdayChild, otherUser, birthdayChild2));  
  11.   
  12.         greetAllBirthdayChildren(users);  
  13.     }  
  14.   
  15.     private static void greetAllBirthdayChildren(List users) {  
  16.         // Next Section  
  17.     }  
  18.   
  19. }  
The Greeting 
Now, we want to greet the birthday children. So first off, we have to filter out all Users whose birthday is today. After this, we have to message them. So let’s do this. I won’t implement sendMessage(String message, User receiver) here, but it just sends a message to a given user. 
  1. public static void greetAllBirthdayChildren(List users) {  
  2.     String today = "20.02"//Just to make the example easier. In production, you would use LocalDateTime or so.  
  3.     users.stream()  
  4.         .filter(user -> user.getBirthday().startsWith(today))  
  5.         .forEach(user -> sendMessage("Happy birthday, ".concat(user.getUsername()).concat("!"), user));  
  6. }  
  7.   
  8. private static void sendMessage(String message, User receiver) {  
  9.     //...  
  10. }  
And now we can send greetings to the users. How nice and easy was that?! 

Parallelism 
Streams can also be executed parallel. By default, every Stream isn’t parallel, but you can use .parallelStream() with Streams to make them parallel. Although it can be cool to use this to make your program faster, you should be careful with it. As shown on this Site, things like sorting can be messed up by parallelism. So be prepared to run into nasty bugs with parallel Streams, although it can make your program significantly faster. 

Conclusion 
We have learned a lot about Streams in Java. We learned how to convert a data structure into a Stream, how to work with a Stream and how to convert your Stream back into a data structure. I have introduced the most common methods and when you should use them. In the end, we tested our knowledge with a bigger example where we greeted all birthday children. 

Supplement 
Part 0 - Motivation 
Part 1 - Functions as Objects 
Part 2 - Optionals 
Part 3 - Streams 
Part 4 - Splitter

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...