Stream 是Java 8非常重要的一個API,它提供了許多現成的過濾、排序、轉換、收集等方法,並支援並行(parallel)處理,可以幫助我們更高效地處理資料。
許多人寫程式時,判斷只想到用if、要遍歷元素只想到for或while,而沒有善用語言本身提供的工具,原因常常是對語言本身不熟悉,例如在大學生作業或菜鳥工程師中,常見到這種「波動拳」寫法:
而對軟體設計模式(例如網站常用MVC模式)沒有概念,就會產生出像這種JSP中的麵條式代碼,HTML版面代碼跟JSP業務代碼混雜:
<html>
<head>
<title>Login example</title>
<%!
private boolean validateUser(String userName, String password) {
...
}
%>
...
</head>
<body>
<%
if (request.getMethod().equals("GET") ) { %>
<form method="POST" target="login.jsp">
<!-- login form fields go here -->
</form> <% }
else {
String userName = request.getParameter("username");
String password = request.getParameter("password");
if (validateUser(userName, password)) {
response.sendRedirect("mainpage.jsp");
}
...
} %>
</body>
</html>
最糟的則像是這種Servlet中的麵條式代碼,同時有者Java、HTML、Javascript、CSS,難以想像要修改HTML竟是要打開一個Java檔案,也難以用debug工具來幫忙除錯:
public class HelloWorld extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<script>");
out.println("function getData(){
retrun ajax.get(document.getElementById(\"input_text\").value);
}");
out.println("</script>")
out.println("<style>");
out.println("body {background: gary}");
out.println("</style>")
out.println("<title>Welcome to our Site</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1> please input your data</h1>");
out.println("</body>");
out.println("</html>");
out.close();
}
}
上面這些代碼只會造成後續除錯的困難及維護的成本,要跟別人合作也會有困難,就像一台車子雖然會動,但線路跟零件沒有遵循一般規範的位置去擺放,到一般修車廠變得沒有人會修,後續的維護及升級也會造成困擾,無法因應需求的改變,軟體的生命週期變短,常常結果就是砍掉重練。
假設我們有個英文句子,我們要去統計其中有多少個長度大於3的單字,以往的做法大概是像這樣:
package exec05;
public class WordsCount {
public static void main(String[] args) {
String st = "Java is the best language in the world, and we all love it!";
String[] words = st.split("[\\P{L}]+"); //非英文字母做為分隔
long count = 0;
for (String w : words) {
if (w.length() > 3) {
count++;
}
}
System.out.println(count);
}
}
也就是將單字陣列一個個做迭代,找出符合條件的單字並統計數量。若改為Stream的做法呢?
long count = Stream.of(words).filter(w -> w.length() > 3).count();
上面的做法則是先將陣列轉為Stream,再在過濾方法塞入判斷條件,再算出符合的數目,是不是很簡潔呢?Stream設計的精神在於,我們只需要告訴Stream「做什麼」,而不需要去知道「怎麼做」。
甚至可以並行處理,利用多執行緒運算來提高效率!
long count = Stream.of(words).parallel().filter(w -> w.length() > 3).count();
基本上Stream的使用可分為三個步驟:
1.建立Stream
2.使用中繼操作(Intermediate operations)
3.使用終結操作(Terminal operations)
建立Stream有許多方式,以下列舉幾個常用的方法:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
Stream<Integer> stream = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
Stream<String> stream = Arrays.stream(new String[] {"I", "You", "He"});
List<String> list = Arrays.asList("a1", "a2", "a3");
Stream<String> stream = list.stream();
Set<String> set = new HashSet<>(Arrays.asList("john", "mary", "steve"));
set.stream().forEach(System.out::println);
Stream<Double> stream = Stream.generate(Math::random); //無限個亂數
Stream<Integer> stream = Stream.iterate(1, e -> e*2); //1, 2, 4, 8, 16.....無限
IntStream stream = IntStream.range(1, 10);
IntStream stream = "abcde".codePoints(); //unicode值
IntStream stream = "abcde".chars(); //utf-16值
Stream<String> stream = Pattern.compile("\\s+").splitAsStream("a b c d e");
Path p = Paths.get("c:\\words.txt");
try (Stream stream = Files.lines(p)){ //自動關閉資源
stream.forEach(System.out::println);
} catch (IOException ex) {
...
}
Stream的方法常會用到幾個特定的函式介面,這些介面通常都存放於java.util.function套件之中,大致上可分為Supplier、Consumer、Function、Predicate。
Supplier<T>: (void) → T (提供者,只出不進)
BooleanSupplier: (void) → boolean (提供者,只boolean出不進)
IntSupplier: (void) → int (提供者,只int出不進)
LongSupplier: (void) → long (提供者,只long出不進)
DoubleSupplier: (void) → double (提供者,只double出不進)
Consumer<T>: (T) → void (消費者,只進不出)
BiConsumer<T, U>: (T, U) → void (消費者,只二進不出)
IntConsumer: (int) → void (消費者,只int進不出)
LongConsumer: (long) → void (消費者,只long進不出)
DoubleConsumer: (double) → void (消費者,只double進不出)
ObjIntConsumer<T>: (T, int) → void (消費者,只T+int進不出)
ObjLongConsumer<T>: (T, long) → void (消費者,只T+long進不出)
ObjDoubleConsumer<T>: (T, double) → void (消費者,只T+double進不出)
Function<T, R>: (T) → R (函數,一進一出)
BiFunction<T, U, R>: (T, U) → R (函數,二進一出)
UnaryOperator<T>: (T) → T (函數,同一進同一出) (Function的子介面)
BinaryOperator<T>: (T, T) → T (函數,同二進同一出) (BiFunction的子介面)
IntFunction<T>: (int) → T (函數,一int進一出)
LongFunction<T>: (long) → T (函數,一long進一出)
DoubleFunction<T>: (double) → T (函數,一double進一出)
ToIntFunction<T>: (T) → int (函數,一進一int出)
ToLongFunction<T>: (T) → long (函數,一進一long出)
ToDoubleFunction<T>: (T) → double (函數,一進一double出)
Predicate<T>: (T) → Boolean (判斷,一進一布林值出)
BiPredicate<T, U>: (T, U) → Boolean (判斷,二進一布林值出)
IntPredicate: (int) → Boolean (判斷,一int進一布林值出)
LongPredicate: (long) → Boolean (判斷,一long進一布林值出)
DoublePredicate: (double) → Boolean (判斷,一double進一布林值出)