<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://aman748.github.io/Gridea_Blog</id>
    <title>Aman&apos;s Blog</title>
    <updated>2019-08-18T20:29:34.275Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://aman748.github.io/Gridea_Blog"/>
    <link rel="self" href="https://aman748.github.io/Gridea_Blog/atom.xml"/>
    <subtitle>天行健，君子以自强不息。</subtitle>
    <logo>https://aman748.github.io/Gridea_Blog/images/avatar.png</logo>
    <icon>https://aman748.github.io/Gridea_Blog/favicon.ico</icon>
    <rights>All rights reserved 2019, Aman&apos;s Blog</rights>
    <entry>
        <title type="html"><![CDATA[Java 8 中的 Lambda 表达式]]></title>
        <id>https://aman748.github.io/Gridea_Blog/post/java-8-zhong-de-lambda-biao-da-shi</id>
        <link href="https://aman748.github.io/Gridea_Blog/post/java-8-zhong-de-lambda-biao-da-shi">
        </link>
        <updated>2019-08-18T19:34:58.000Z</updated>
        <content type="html"><![CDATA[<!--more-->
<p><img src="https://pic3.zhimg.com/80/v2-c7cb18cbd37262ffad7b04e50fb13b92_hd.jpg" alt=""></p>
<p>我们知道，对于一个Java变量，我们可以赋给其一个**“值”**。</p>
<p><img src="https://pic3.zhimg.com/50/v2-ab6545c49383236a4af3f28a47886090_hd.jpg" alt=""></p>
<p>如果你想把**“一块代码”**赋给一个Java变量，应该怎么做呢？</p>
<p>比如，我想把右边那块代码，赋给一个叫做aBlockOfCode的Java变量：</p>
<p><img src="https://pic2.zhimg.com/50/v2-1cc87e82fba0872c2cae3fee08e8fe41_hd.jpg" alt=""></p>
<p>在Java 8之前，这个是做不到的。但是Java 8问世之后，利用Lambda特性，就可以做到了。</p>
<p><img src="https://pic3.zhimg.com/50/v2-145a556d86806c3163391a13428e3f03_hd.jpg" alt=""></p>
<p>当然，这个并不是一个很简洁的写法。所以，为了使这个赋值操作更加elegant, 我们可以移除一些没用的声明。</p>
<p><img src="https://pic3.zhimg.com/50/v2-a712753b42972e094a548ae02fa82987_hd.jpg" alt=""></p>
<p>这样，我们就成功的非常优雅的把“一块代码”赋给了一个变量。<strong>而“这块代码”，或者说“这个被赋给一个变量的函数”，就是一个Lambda表达式</strong>。</p>
<p>但是这里仍然有一个问题，就是变量aBlockOfCode的类型应该是什么？</p>
<p>在Java 8里面，**所有的Lambda的类型都是一个接口，而Lambda表达式本身，也就是”那段代码“，需要是这个接口的实现。**这是我认为理解Lambda的一个关键所在，简而言之就是，<strong>Lambda表达式本身就是一个接口的实现</strong>。直接这样说可能还是有点让人困扰，我们继续看看例子。我们给上面的aBlockOfCode加上一个类型：</p>
<p><img src="https://pic4.zhimg.com/50/v2-55de66060b4cb70193ddc7fea201b257_hd.jpg" alt=""></p>
<p>这种只有**一个接口函数需要被实现的接口类型，我们叫它”函数式接口“。**为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现，变成“非函数接口”，我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了：</p>
<p><img src="https://pic4.zhimg.com/50/v2-2c57e7411de227d1eb09c327d01fb766_hd.jpg" alt=""></p>
<p>这样，我们就得到了一个完整的Lambda表达式声明：</p>
<p><img src="https://pic4.zhimg.com/50/v2-02eedc528fcee115f5ed0b7b045846d7_hd.jpg" alt=""></p>
<p><img src="https://pic4.zhimg.com/50/v2-b3c9ad03a5333f5e61c3ee8186210634_hd.jpg" alt=""></p>
<h2 id="lambda表达式有什么作用"><strong>Lambda表达式有什么作用?</strong></h2>
<p><strong>最直观的作用就是使得代码变得异常简洁。</strong></p>
<p>我们可以对比一下Lambda表达式和传统的Java对同一个接口的实现：</p>
<p><img src="https://pic2.zhimg.com/50/v2-dbd46cf9d188d0fde25db700c23dcc79_hd.jpg" alt=""></p>
<p>这两种写法本质上是等价的。但是显然，Java 8中的写法更加优雅简洁。并且，由于Lambda可以直接赋值给一个变量，<strong>我们就可以直接把Lambda作为参数传给函数, 而传统的Java必须有明确的接口实现的定义，初始化才行：</strong></p>
<p><img src="https://pic3.zhimg.com/50/v2-28606f4328308baf7f70a36bd689e5ea_hd.jpg" alt=""></p>
<p>有些情况下，这个接口实现只需要用到一次。传统的Java 7必须要求你定义一个“污染环境”的接口实现MyInterfaceImpl，而相较之下Java 8的Lambda, 就显得干净很多。</p>
<p><img src="https://pic1.zhimg.com/50/v2-413d712fb74b8406502af05031cf8d4c_hd.jpg" alt=""></p>
<h2 id="lambda结合functionalinterface-lib-foreach-streammethod-reference等新特性可以使代码变的更加简洁">Lambda结合FunctionalInterface Lib, forEach, stream()，method reference等新特性可以使代码变的更加简洁！</h2>
<p>直接上例子。</p>
<p>假设Person的定义和List的值都给定。</p>
<p><img src="https://pic3.zhimg.com/50/v2-02ae14b4c341e2303ae86e2da6a038a2_hd.jpg" alt=""></p>
<p>现在需要你打印出guiltyPersons List里面所有LastName以“Z”开头的人的FirstName。</p>
<p><strong>原生态Lambda写法</strong>：定义两个函数式接口，定义一个静态函数，调用静态函数并给参数赋值Lambda表达式。</p>
<p><img src="https://pic4.zhimg.com/50/v2-fdef41934be8804fa244e89c84a567f1_hd.jpg" alt=""></p>
<p>这个代码实际上已经比较简洁了，但是我们还可以更简洁么？</p>
<p>当然可以。在Java 8中有一个函数式接口的包，里面定义了大量可能用到的函数式接口（<a href="https://link.zhihu.com/?target=https%3A//docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">java.util.function (Java Platform SE 8 )</a>）。所以，我们在这里压根都不需要定义NameChecker和Executor这两个函数式接口，直接用Java 8函数式接口包里的Predicate和Consumer就可以了——因为他们这一对的接口定义和NameChecker/Executor其实是一样的。</p>
<p><img src="https://pic2.zhimg.com/50/v2-ad5b3e8d225bf9e6d988c6dc83819637_hd.jpg" alt=""></p>
<p><strong>第一步简化 - 利用函数式接口包：</strong></p>
<p><img src="https://pic4.zhimg.com/50/v2-493168d1a72120d69b0d13b4711c3c9f_hd.jpg" alt=""></p>
<p>静态函数里面的for each循环其实是非常碍眼的。这里可以利用Iterable自带的forEach()来替代。forEach()本身可以接受一个Consumer 参数。</p>
<p><strong>第二步简化 - 用Iterable.forEach()取代foreach loop：</strong></p>
<p><img src="https://pic4.zhimg.com/50/v2-bdd6f5a6dbf65b578080f7b7ad5ab6cf_hd.jpg" alt=""></p>
<p>由于静态函数其实只是对List进行了一通操作，这里我们可以甩掉静态函数，直接使用stream()特性来完成。stream()的几个方法都是接受Predicate，Consumer等参数的（<a href="https://link.zhihu.com/?target=https%3A//docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html">java.util.stream (Java Platform SE 8 )</a>）。你理解了上面的内容，stream()这里就非常好理解了，并不需要多做解释。</p>
<p><strong>第三步简化 - 利用stream()替代静态函数：</strong></p>
<p><img src="https://pic1.zhimg.com/50/v2-e196d987f852b9b8e26a6a9dac648a06_hd.jpg" alt=""></p>
<p>对比最开始的Lambda写法，这里已经非常非常简洁了。但是如果，我们的要求变一下，变成print这个人的全部信息，及p -&gt; System.out.println(p); 那么还可以利用Method reference来继续简化。所谓Method reference, 就是用已经写好的别的Object/Class的method来代替Lambda expression。格式如下：</p>
<p><img src="https://pic3.zhimg.com/50/v2-12622326a5682285ce235d96291f3bb8_hd.jpg" alt=""></p>
<p><strong>第四步简化 - 如果是println(p)，则可以利用Method reference代替forEach中的Lambda表达式：</strong></p>
<p><img src="https://pic4.zhimg.com/50/v2-f29e6569d0265b91794565ae81d54265_hd.jpg" alt=""></p>
<p>这基本上就是能写的最简洁的版本了。</p>
<p><img src="https://pic4.zhimg.com/50/v2-ad2b43af522ea121d3b751c4a0f881c6_hd.jpg" alt=""></p>
<h2 id="lambda配合optional可以使java对于null的处理变的异常优雅">Lambda配合Optional可以使Java对于null的处理变的异常优雅</h2>
<p>这里假设我们有一个person object，以及一个person object的Optional wrapper:</p>
<p><img src="https://pic3.zhimg.com/50/v2-e78d71b9699504e46d86d77aabec42cf_hd.jpg" alt=""></p>
<p>Optional如果不结合Lambda使用的话，并不能使原来繁琐的null check变的简单。</p>
<p><img src="https://pic4.zhimg.com/50/v2-ce82e2c3e69caab9acdec9d4e42cde0e_hd.jpg" alt=""></p>
<p><strong>只有当Optional结合Lambda一起使用的时候，才能发挥出其真正的威力！</strong></p>
<p>我们现在就来对比一下下面四种常见的null处理中，Java 8的Lambda+Optional和传统Java两者之间对于null的处理差异。</p>
<p><strong>情况一 - 存在则开干</strong></p>
<p><img src="https://pic2.zhimg.com/50/v2-f41305009c93effa8fe047631d5342ed_hd.jpg" alt=""></p>
<p><strong>情况二 - 存在则返回，无则返回屁</strong></p>
<p><img src="https://pic4.zhimg.com/50/v2-cfa0e3298ba94efa29dc9cb8b32356fe_hd.jpg" alt=""></p>
<p><strong>情况三 - 存在则返回，无则由函数产生</strong></p>
<p><img src="https://pic2.zhimg.com/50/v2-4091bb966ac575fd83d5fa07dd7c2dce_hd.jpg" alt=""></p>
<p><strong>情况四 - 夺命连环null检查</strong></p>
<p><img src="https://pic1.zhimg.com/50/v2-7bc0ac86cd29a1cea92a853d73f41cec_hd.jpg" alt=""></p>
<p>由上述四种情况可以清楚地看到，Optional+Lambda可以让我们少写很多ifElse块。尤其是对于情况四那种夺命连环null检查，传统java的写法显得冗长难懂，而新的Optional+Lambda则清新脱俗，清楚简洁。</p>
<p><img src="https://pic4.zhimg.com/50/v2-e6b653cd6eced03376d5feb2e5943be3_hd.jpg" alt=""></p>
<p>关于Java的Lambda, 还有东西需要讨论和学习。比如如何handle lambda exception，如何利用Lambda的特性来进行parallel processing等。总之，我只是一如既往地介绍个大概，让你大概知道，哦！原来是这样子就OK了。网上关于Lambda有很多相关的教程，多看多练。假以时日，必定有所精益。</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Java 8 中的 Streams API 详解]]></title>
        <id>https://aman748.github.io/Gridea_Blog/post/java-8-zhong-de-streams-api-xiang-jie</id>
        <link href="https://aman748.github.io/Gridea_Blog/post/java-8-zhong-de-streams-api-xiang-jie">
        </link>
        <updated>2019-08-18T19:22:49.000Z</updated>
        <summary type="html"><![CDATA[<p>Streams 的背景，以及 Java 8 中的使用详解</p>
]]></summary>
        <content type="html"><![CDATA[<p>Streams 的背景，以及 Java 8 中的使用详解</p>
<!-- more -->
<h2 id="为什么需要-stream">为什么需要 Stream</h2>
<p>Stream 作为 Java 8 的一大亮点，它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它也不同于 StAX 对 XML 解析的 Stream，也不是 Amazon Kinesis 对大数据实时处理的 Stream。Java 8 中的 Stream 是对集合（Collection）对象功能的增强，它专注于对集合对象进行各种非常便利、高效的聚合操作（aggregate operation），或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式，极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作，并发模式能够充分利用多核处理器的优势，使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码，就可以很方便地写出高性能的并发程序。所以说，Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。</p>
<h3 id="什么是聚合操作">什么是聚合操作</h3>
<p>在传统的 J2EE 应用中，Java 代码经常不得不依赖于关系型数据库的聚合操作来完成诸如：</p>
<ul>
<li>客户每月平均消费金额</li>
<li>最昂贵的在售商品</li>
<li>本周完成的有效订单（排除了无效的）</li>
<li>取十个数据样本作为首页推荐<br>
这类的操作。</li>
</ul>
<p>但在当今这个数据大爆炸的时代，在数据来源多样化、数据海量化的今天，很多时候不得不脱离 RDBMS，或者以底层返回的数据为基础进行更上层的数据统计。而 Java 的集合 API 中，仅仅有极少量的辅助型方法，更多的时候是程序员需要用 Iterator 来遍历集合，完成相关的聚合应用逻辑。这是一种远不够高效、笨拙的方法。在 Java 7 中，如果要发现 type 为 grocery 的所有交易，然后返回以交易值降序排序好的交易 ID 集合，我们需要这样写：</p>
<h5 id="清单-1-java-7-的排序-取值实现">清单 1. Java 7 的排序、取值实现</h5>
<pre><code class="language-java">List&lt;Transaction&gt; groceryTransactions = new Arraylist&lt;&gt;();
for(Transaction t: transactions){
 if(t.getType() == Transaction.GROCERY){
 groceryTransactions.add(t);
 }
}
Collections.sort(groceryTransactions, new Comparator(){
 public int compare(Transaction t1, Transaction t2){
 return t2.getValue().compareTo(t1.getValue());
 }
});
List&lt;Integer&gt; transactionIds = new ArrayList&lt;&gt;();
for(Transaction t: groceryTransactions){
 transactionsIds.add(t.getId());
}
</code></pre>
<p>而在 Java 8 使用 Stream，代码更加简洁易读；而且使用并发模式，程序执行速度更快。</p>
<h5 id="清单-2-java-8-的排序-取值实现">清单 2. Java 8 的排序、取值实现</h5>
<pre><code class="language-java">List&lt;Integer&gt; transactionsIds = transactions.parallelStream().
 filter(t -&gt; t.getType() == Transaction.GROCERY).
 sorted(comparing(Transaction::getValue).reversed()).
 map(Transaction::getId).
 collect(toList());
</code></pre>
<p>Stream 不是集合元素，它不是数据结构并不保存数据，它是有关算法和计算的，它更像一个高级版本的 Iterator。原始版本的 Iterator，用户只能显式地一个一个遍历元素并对其执行某些操作；高级版本的 Stream，用户只要给出需要对其包含的元素执行什么操作，比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等，Stream 会隐式地在内部进行遍历，做出相应的数据转换。</p>
<p>Stream 就如同一个迭代器（Iterator），单向，不可往复，数据只能遍历一次，遍历过一次后即用尽了，就好比流水从面前流过，一去不复返。</p>
<p>而和迭代器又不同的是，Stream 可以并行化操作，迭代器只能命令式地、串行化操作。顾名思义，当使用串行方式去遍历时，每个 item 读完后再读下一个 item。而使用并行去遍历时，数据会被分成多个段，其中每一个都在不同的线程中处理，然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架（JSR166y）来拆分任务和加速处理过程。Java 的并行 API 演变历程基本如下：</p>
<ol>
<li>1.0-1.4 中的 java.lang.Thread</li>
<li>5.0 中的 java.util.concurrent</li>
<li>6.0 中的 Phasers 等</li>
<li>7.0 中的 Fork/Join 框架</li>
<li>8.0 中的 Lambda<br>
Stream 的另外一大特点是，数据源本身可以是无限的。</li>
</ol>
<h3 id="流的构成">流的构成</h3>
<p>当我们使用一个流的时候，通常包括三个基本步骤：</p>
<p>获取一个数据源（source）→ 数据转换→执行操作获取想要的结果，每次转换原有 Stream 对象不改变，返回一个新的 Stream 对象（可以有多次转换），这就允许对其操作可以像链条一样排列，变成一个管道，如下图所示。</p>
<h5 id="图-1-流管道-stream-pipeline-的构成">图 1. 流管道 (Stream Pipeline) 的构成</h5>
<p><a href="#N100B3">点击查看大图</a></p>
<p>有多种方式生成 Stream Source：</p>
<ul>
<li>
<p>Collection.stream()</p>
</li>
<li>
<p>Collection.parallelStream()</p>
</li>
<li>
<p>Arrays.stream(T array) or Stream.of()<br>
从 BufferedReader</p>
<ul>
<li>java.io.BufferedReader.lines()</li>
</ul>
</li>
<li>
<p>java.util.stream.IntStream.range()</p>
</li>
<li>
<p>java.nio.file.Files.walk()</p>
</li>
<li>
<p>其它</p>
<ul>
<li>Random.ints()</li>
<li>BitSet.stream()</li>
<li>Pattern.splitAsStream(java.lang.CharSequence)</li>
<li>JarFile.stream()</li>
</ul>
</li>
</ul>
<p>流的操作类型分为两种：</p>
<ul>
<li><strong>Intermediate</strong> ：一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流，做出某种程度的数据映射/过滤，然后返回一个新的流，交给下一个操作使用。这类操作都是惰性化的（lazy），就是说，仅仅调用到这类方法，并没有真正开始流的遍历。</li>
<li><strong>Terminal</strong> ：一个流只能有一个 terminal 操作，当这个操作执行后，流就被使用“光”了，无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行，才会真正开始流的遍历，并且会生成一个结果，或者一个 side effect。<br>
在对于一个 Stream 进行多次转换操作 (Intermediate 操作)，每次都对 Stream 的每个元素进行转换，而且是执行多次，这样时间复杂度就是 N（转换次数）个 for 循环里把所有操作都做掉的总和吗？其实不是这样的，转换操作都是 lazy 的，多个转换操作只会在 Terminal 操作的时候融合起来，一次循环完成。我们可以这样简单的理解，Stream 里有个操作函数的集合，每次转换操作就是把转换函数放入这个集合中，在 Terminal 操作的时候循环 Stream 对应的集合，然后对每个元素执行所有的函数。</li>
</ul>
<p>还有一种操作被称为 <strong>short-circuiting</strong> 。用以指：</p>
<ul>
<li>对于一个 intermediate 操作，如果它接受的是一个无限大（infinite/unbounded）的 Stream，但返回一个有限的新 Stream。</li>
<li>对于一个 terminal 操作，如果它接受的是一个无限大的 Stream，但能在有限的时间计算出结果。<br>
当操作一个无限大的 Stream，而又希望在有限时间内完成操作，则在管道内拥有一个 short-circuiting 操作是必要非充分条件。</li>
</ul>
<h5 id="清单-3-一个流操作的示例">清单 3. 一个流操作的示例</h5>
<pre><code class="language-java">int sum = widgets.stream()
.filter(w -&gt; w.getColor() == RED)
 .mapToInt(w -&gt; w.getWeight())
 .sum();
</code></pre>
<p>stream() 获取当前小物件的 source，filter 和 mapToInt 为 intermediate 操作，进行数据筛选和转换，最后一个 sum() 为 terminal 操作，对符合条件的全部小物件作重量求和。</p>
<h2 id="流的使用详解">流的使用详解</h2>
<p>简单说，对 Stream 的使用就是实现一个 filter-map-reduce 过程，产生一个最终结果，或者导致一个副作用（side effect）。</p>
<h3 id="流的构造与转换">流的构造与转换</h3>
<p>下面提供最常见的几种构造 Stream 的样例。</p>
<h5 id="清单-4-构造流的几种常见方法">清单 4. 构造流的几种常见方法</h5>
<pre><code class="language-java">// 1. Individual values
Stream stream = Stream.of(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;);
// 2. Arrays
String [] strArray = new String[] {&quot;a&quot;, &quot;b&quot;, &quot;c&quot;};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List&lt;String&gt; list = Arrays.asList(strArray);
stream = list.stream();
</code></pre>
<p>需要注意的是，对于基本数值型，目前有三种对应的包装类型 Stream：</p>
<p>IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long> &gt;、Stream<Double>，但是 boxing 和 unboxing 会很耗时，所以特别为这三种基本数值型提供了对应的 Stream。</p>
<p>Java 8 中还没有提供其它数值型 Stream，因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种 Stream 进行。</p>
<h5 id="清单-5-数值流的构造">清单 5. 数值流的构造</h5>
<pre><code class="language-java">IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);
</code></pre>
<pre><code class="language-java">// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List&lt;String&gt; list1 = stream.collect(Collectors.toList());
List&lt;String&gt; list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();
</code></pre>
<p>一个 Stream 只可以使用一次，上面的代码为了简洁而重复使用了数次。</p>
<h3 id="流的操作">流的操作</h3>
<p>接下来，当把一个数据结构包装成 Stream 后，就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。</p>
<p>map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered</p>
<p>forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator</p>
<p>anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit</p>
<p>我们下面看一下 Stream 的比较典型用法。</p>
<p><strong>map/flatMap</strong></p>
<p>我们先来看 map。如果你熟悉 scala 这类函数式语言，对这个方法应该很了解，它的作用就是把 input Stream 的每一个元素，映射成 output Stream 的另外一个元素。</p>
<h5 id="清单-7-转换大写">清单 7. 转换大写</h5>
<pre><code class="language-java">List&lt;String&gt; output = wordList.stream().
map(String::toUpperCase).
collect(Collectors.toList());
</code></pre>
<p>这段代码把所有的单词转换为大写。</p>
<h5 id="清单-8-平方数">清单 8. 平方数</h5>
<pre><code class="language-java">List&lt;Integer&gt; nums = Arrays.asList(1, 2, 3, 4);
List&lt;Integer&gt; squareNums = nums.stream().
map(n -&gt; n * n).
collect(Collectors.toList());
</code></pre>
<p>这段代码生成一个整数 list 的平方数 {1, 4, 9, 16}。</p>
<p>从上面例子可以看出，map 生成的是个 1:1 映射，每个输入元素，都按照规则转换成为另外一个元素。还有一些场景，是一对多映射关系的，这时需要 flatMap。</p>
<h5 id="清单-9-一对多">清单 9. 一对多</h5>
<pre><code class="language-java">Stream&lt;List&lt;Integer&gt;&gt; inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream&lt;Integer&gt; outputStream = inputStream.
flatMap((childList) -&gt; childList.stream());
</code></pre>
<p>flatMap 把 input Stream 中的层级结构扁平化，就是将最底层元素抽出来放到一起，最终 output 的新 Stream 里面已经没有 List 了，都是直接的数字。</p>
<p><strong>filter</strong></p>
<p>filter 对原始 Stream 进行某项测试，通过测试的元素被留下来生成一个新 Stream。</p>
<h5 id="清单-10-留下偶数">清单 10. 留下偶数</h5>
<pre><code class="language-java">Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens =
Stream.of(sixNums).filter(n -&gt; n%2 == 0).toArray(Integer[]::new);
</code></pre>
<p>经过条件“被 2 整除”的 filter，剩下的数字为 {2, 4, 6}。</p>
<h5 id="清单-11-把单词挑出来">清单 11. 把单词挑出来</h5>
<pre><code class="language-java">List&lt;String&gt; output = reader.lines().
 flatMap(line -&gt; Stream.of(line.split(REGEXP))).
 filter(word -&gt; word.length() &gt; 0).
 collect(Collectors.toList());
</code></pre>
<p>这段代码首先把每行的单词用 flatMap 整理到新的 Stream，然后保留长度不为 0 的，就是整篇文章中的全部单词了。</p>
<p><strong>forEach</strong></p>
<p>forEach 方法接收一个 Lambda 表达式，然后在 Stream 的每一个元素上执行该表达式。</p>
<h5 id="清单-12-打印姓名foreach-和-pre-java8-的对比">清单 12. 打印姓名（forEach 和 pre-java8 的对比）</h5>
<pre><code class="language-java">// Java 8
roster.stream()
 .filter(p -&gt; p.getGender() == Person.Sex.MALE)
 .forEach(p -&gt; System.out.println(p.getName()));
// Pre-Java 8
for (Person p : roster) {
 if (p.getGender() == Person.Sex.MALE) {
 System.out.println(p.getName());
 }
}
</code></pre>
<p>对一个人员集合遍历，找出男性并打印姓名。可以看出来，forEach 是为 Lambda 而设计的，保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的，非常方便。当需要为多核系统优化时，可以 parallelStream().forEach()，只是此时原有元素的次序没法保证，并行的情况下将改变串行时操作的行为，此时 forEach 本身的实现不需要调整，而 Java8 以前的 for 循环 code 可能需要加入额外的多线程逻辑。</p>
<p>但一般认为，forEach 和常规 for 循环的差异不涉及到性能，它们仅仅是函数式风格与传统 Java 风格的差别。</p>
<p>另外一点需要注意，forEach 是 terminal 操作，因此它执行后，Stream 的元素就被“消费”掉了，你无法对一个 Stream 进行两次 terminal 运算。下面的代码是错误的：</p>
<pre><code class="language-java">stream.forEach(element -&gt; doOneThing(element));
     stream.forEach(element -&gt; doAnotherThing(element));
</code></pre>
<p>相反，具有相似功能的 intermediate 操作 peek 可以达到上述目的。如下是出现在该 api javadoc 上的一个示例。</p>
<h5 id="清单-13-peek-对每个元素执行操作并返回一个新的-stream">清单 13. peek 对每个元素执行操作并返回一个新的 Stream</h5>
<pre><code class="language-java">Stream.of(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;, &quot;four&quot;)
 .filter(e -&gt; e.length() &gt; 3)
 .peek(e -&gt; System.out.println(&quot;Filtered value: &quot; + e))
 .map(String::toUpperCase)
 .peek(e -&gt; System.out.println(&quot;Mapped value: &quot; + e))
 .collect(Collectors.toList());
</code></pre>
<p>forEach 不能修改自己包含的本地变量值，也不能用 break/return 之类的关键字提前结束循环。</p>
<p><strong>findFirst</strong></p>
<p>这是一个 termimal 兼 short-circuiting 操作，它总是返回 Stream 的第一个元素，或者空。</p>
<p>这里比较重点的是它的返回值类型：Optional。这也是一个模仿 Scala 语言中的概念，作为一个容器，它可能含有某值，或者不包含。使用它的目的是尽可能避免 NullPointerException。</p>
<h5 id="清单-14-optional-的两个用例">清单 14. Optional 的两个用例</h5>
<pre><code class="language-java">String strA = &quot; abcd &quot;, strB = null;
print(strA);
print(&quot;&quot;);
print(strB);
getLength(strA);
getLength(&quot;&quot;);
getLength(strB);
public static void print(String text) {
 // Java 8
 Optional.ofNullable(text).ifPresent(System.out::println);
 // Pre-Java 8
 if (text != null) {
 System.out.println(text);
 }
 }
public static int getLength(String text) {
 // Java 8
return Optional.ofNullable(text).map(String::length).orElse(-1);
 // Pre-Java 8
// return if (text != null) ? text.length() : -1;
 };
</code></pre>
<p>在更复杂的 if (xx != null) 的情况中，使用 Optional 代码的可读性更好，而且它提供的是编译时检查，能极大的降低 NPE 这种 Runtime Exception 对程序的影响，或者迫使程序员更早的在编码阶段处理空值问题，而不是留到运行时再发现和调试。</p>
<p>Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。</p>
<p><strong>reduce</strong></p>
<p>这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值（种子），然后依照运算规则（BinaryOperator），和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说，字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于</p>
<p>Integer sum = integers.reduce(0, (a, b) -&gt; a+b); 或</p>
<p>Integer sum = integers.reduce(0, Integer::sum);</p>
<p>也有没有起始值的情况，这时会把 Stream 的前面两个元素组合起来，返回的是 Optional。</p>
<h5 id="清单-15-reduce-的用例">清单 15. reduce 的用例</h5>
<pre><code class="language-java">// 字符串连接，concat = &quot;ABCD&quot;
String concat = Stream.of(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;).reduce(&quot;&quot;, String::concat); 
// 求最小值，minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
// 求和，sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和，sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤，字符串连接，concat = &quot;ace&quot;
concat = Stream.of(&quot;a&quot;, &quot;B&quot;, &quot;c&quot;, &quot;D&quot;, &quot;e&quot;, &quot;F&quot;).
 filter(x -&gt; x.compareTo(&quot;Z&quot;) &gt; 0).
 reduce(&quot;&quot;, String::concat);
</code></pre>
<p>上面代码例如第一个示例的 reduce()，第一个参数（空白字符）即为起始值，第二个参数（String::concat）为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce()，由于可能没有足够的元素，返回的是 Optional，请留意这个区别。</p>
<p><strong>limit/skip</strong></p>
<p>limit 返回 Stream 的前面 n 个元素；skip 则是扔掉前 n 个元素（它是由一个叫 subStream 的方法改名而来）。</p>
<h5 id="清单-16-limit-和-skip-对运行次数的影响">清单 16. limit 和 skip 对运行次数的影响</h5>
<pre><code class="language-java">public void testLimitAndSkip() {
 List&lt;Person&gt; persons = new ArrayList();
 for (int i = 1; i &lt;= 10000; i++) {
 Person person = new Person(i, &quot;name&quot; + i);
 persons.add(person);
 }
List&lt;String&gt; personList2 = persons.stream().
map(Person::getName).limit(10).skip(3).collect(Collectors.toList());
 System.out.println(personList2);
}
private class Person {
 public int no;
 private String name;
 public Person (int no, String name) {
 this.no = no;
 this.name = name;
 }
 public String getName() {
 System.out.println(name);
 return name;
 }
}
</code></pre>
<p>输出结果为：</p>
<pre><code class="language-java">name1
name2
name3
name4
name5
name6
name7
name8
name9
name10
[name4, name5, name6, name7, name8, name9, name10]
</code></pre>
<p>这是一个有 10，000 个元素的 Stream，但在 short-circuiting 操作 limit 和 skip 的作用下，管道中 map 操作指定的 getName() 方法的执行次数为 limit 所限定的 10 次，而最终返回结果在跳过前 3 个元素后只有后面 7 个返回。</p>
<p>有一种情况是 limit/skip 无法达到 short-circuiting 目的的，就是把它们放在 Stream 的排序操作后，原因跟 sorted 这个 intermediate 操作有关：此时系统并不知道 Stream 排序后的次序如何，所以 sorted 中的操作看上去就像完全没有被 limit 或者 skip 一样。</p>
<h5 id="清单-17-limit-和-skip-对-sorted-后的运行次数无影响">清单 17. limit 和 skip 对 sorted 后的运行次数无影响</h5>
<pre><code class="language-java">List&lt;Person&gt; persons = new ArrayList();
 for (int i = 1; i &lt;= 5; i++) {
 Person person = new Person(i, &quot;name&quot; + i);
 persons.add(person);
 }
List&lt;Person&gt; personList2 = persons.stream().sorted((p1, p2) -&gt; 
p1.getName().compareTo(p2.getName())).limit(2).collect(Collectors.toList());
System.out.println(personList2);
</code></pre>
<p>上面的示例对清单 13 做了微调，首先对 5 个元素的 Stream 排序，然后进行 limit 操作。输出结果为：</p>
<pre><code class="language-java">name2
name1
name3
name2
name4
name3
name5
name4
[stream.StreamDW$Person@816f27d, stream.StreamDW$Person@87aac27]
</code></pre>
<p>即虽然最后的返回元素数量是 2，但整个管道中的 sorted 表达式执行次数没有像前面例子相应减少。</p>
<p>最后有一点需要注意的是，对一个 parallel 的 Steam 管道来说，如果其元素是有序的，那么 limit 操作的成本会比较大，因为它的返回对象必须是前 n 个也有一样次序的元素。取而代之的策略是取消元素间的次序，或者不要用 parallel Stream。</p>
<p><strong>sorted</strong></p>
<p>对 Stream 的排序通过 sorted 进行，它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后，再排序，这能帮助程序明显缩短执行时间。我们对清单 14 进行优化：</p>
<h5 id="清单-18-优化排序前进行-limit-和-skip">清单 18. 优化：排序前进行 limit 和 skip</h5>
<pre><code class="language-java">List&lt;Person&gt; persons = new ArrayList();
 for (int i = 1; i &lt;= 5; i++) {
 Person person = new Person(i, &quot;name&quot; + i);
 persons.add(person);
 }
List&lt;Person&gt; personList2 = persons.stream().limit(2).sorted((p1, p2) -&gt; p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);
</code></pre>
<p>结果会简单很多：</p>
<pre><code class="language-java">name2
name1
[stream.StreamDW$Person@6ce253f1, stream.StreamDW$Person@53d8d10a]
</code></pre>
<p>当然，这种优化是有 business logic 上的局限性的：即不要求排序后再取值。</p>
<p><strong>min/max/distinct</strong></p>
<p>min 和 max 的功能也可以通过对 Stream 元素先排序，再 findFirst 来实现，但前者的性能会更好，为 O(n)，而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。</p>
<h5 id="清单-19-找出最长一行的长度">清单 19. 找出最长一行的长度</h5>
<pre><code class="language-java">BufferedReader br = new BufferedReader(new FileReader(&quot;c:\\SUService.log&quot;));
int longest = br.lines().
 mapToInt(String::length).
 max().
 getAsInt();
br.close();
System.out.println(longest);
</code></pre>
<p>下面的例子则使用 distinct 来找出不重复的单词。</p>
<h5 id="清单-20-找出全文的单词转小写并排序">清单 20. 找出全文的单词，转小写，并排序</h5>
<pre><code class="language-java">List&lt;String&gt; words = br.lines().
 flatMap(line -&gt; Stream.of(line.split(&quot; &quot;))).
 filter(word -&gt; word.length() &gt; 0).
 map(String::toLowerCase).
 distinct().
 sorted().
 collect(Collectors.toList());
br.close();
System.out.println(words);
</code></pre>
<p><strong>Match</strong></p>
<p>Stream 有三个 match 方法，从语义上说：</p>
<ul>
<li>allMatch：Stream 中全部元素符合传入的 predicate，返回 true</li>
<li>anyMatch：Stream 中只要有一个元素符合传入的 predicate，返回 true</li>
<li>noneMatch：Stream 中没有一个元素符合传入的 predicate，返回 true<br>
它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件，就 skip 剩下的所有元素，返回 false。对清单 13 中的 Person 类稍做修改，加入一个 age 属性和 getAge 方法。</li>
</ul>
<h5 id="清单-21-使用-match">清单 21. 使用 Match</h5>
<pre><code class="language-java">List&lt;Person&gt; persons = new ArrayList();
persons.add(new Person(1, &quot;name&quot; + 1, 10));
persons.add(new Person(2, &quot;name&quot; + 2, 21));
persons.add(new Person(3, &quot;name&quot; + 3, 34));
persons.add(new Person(4, &quot;name&quot; + 4, 6));
persons.add(new Person(5, &quot;name&quot; + 5, 55));
boolean isAllAdult = persons.stream().
 allMatch(p -&gt; p.getAge() &gt; 18);
System.out.println(&quot;All are adult? &quot; + isAllAdult);
boolean isThereAnyChild = persons.stream().
 anyMatch(p -&gt; p.getAge() &lt; 12);
System.out.println(&quot;Any child? &quot; + isThereAnyChild);
</code></pre>
<p>输出结果：</p>
<pre><code class="language-java">All are adult? false
 Any child? true
</code></pre>
<p><strong>Stream.generate</strong></p>
<p>通过实现 Supplier 接口，你可以自己来控制流的生成。这种情形通常用于随机数、常量的 Stream，或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream，默认是串行（相对 parallel 而言）但无序的（相对 ordered 而言）。由于它是无限的，在管道中，必须利用 limit 之类的操作限制 Stream 大小。</p>
<h5 id="清单-22-生成-10-个随机整数">清单 22. 生成 10 个随机整数</h5>
<pre><code class="language-java">Random seed = new Random();
Supplier&lt;Integer&gt; random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -&gt; (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);
</code></pre>
<p>Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据的时候，用某种自动的规则给每一个变量赋值；或者依据公式计算 Stream 的每个元素值。这些都是维持状态信息的情形。</p>
<h5 id="清单-23-自实现-supplier">清单 23. 自实现 Supplier</h5>
<pre><code class="language-java">Stream.generate(new PersonSupplier()).
limit(10).
forEach(p -&gt; System.out.println(p.getName() + &quot;, &quot; + p.getAge()));
private class PersonSupplier implements Supplier&lt;Person&gt; {
 private int index = 0;
 private Random random = new Random();
 @Override
 public Person get() {
 return new Person(index++, &quot;StormTestUser&quot; + index, random.nextInt(100));
 }
}
</code></pre>
<p>输出结果：</p>
<pre><code class="language-java">StormTestUser1, 9
StormTestUser2, 12
StormTestUser3, 88
StormTestUser4, 51
StormTestUser5, 22
StormTestUser6, 28
StormTestUser7, 81
StormTestUser8, 51
StormTestUser9, 4
StormTestUser10, 76
</code></pre>
<p><strong>Stream.iterate</strong></p>
<p>iterate 跟 reduce 操作很像，接受一个种子值，和一个 UnaryOperator（例如 f）。然后种子值成为 Stream 的第一个元素，f(seed) 为第二个，f(f(seed)) 第三个，以此类推。</p>
<p><strong>清单 24. 生成一个等差数列</strong></p>
<pre><code class="language-java">Stream.iterate(0, n -&gt; n + 3).limit(10). forEach(x -&gt; System.out.print(x + &quot; &quot;));.
</code></pre>
<p>输出结果：</p>
<pre><code class="language-java">0 3 6 9 12 15 18 21 24 27
</code></pre>
<p>与 Stream.generate 相仿，在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小。</p>
<h3 id="进阶用-collectors-来进行-reduction-操作">进阶：用 Collectors 来进行 reduction 操作</h3>
<p>java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作，例如转变输出为 Collection，把 Stream 元素进行归组。</p>
<p><strong>groupingBy/partitioningBy</strong></p>
<h5 id="清单-25-按照年龄归组">清单 25. 按照年龄归组</h5>
<pre><code class="language-java">Map&lt;Integer, List&lt;Person&gt;&gt; personGroups = Stream.generate(new PersonSupplier()).
 limit(100).
 collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
 Map.Entry&lt;Integer, List&lt;Person&gt;&gt; persons = (Map.Entry) it.next();
 System.out.println(&quot;Age &quot; + persons.getKey() + &quot; = &quot; + persons.getValue().size());
}
</code></pre>
<p>上面的 code，首先生成 100 人的信息，然后按照年龄归组，相同年龄的人放到同一个 list 中，可以看到如下的输出：</p>
<pre><code class="language-java">Age 0 = 2
Age 1 = 2
Age 5 = 2
Age 8 = 1
Age 9 = 1
Age 11 = 2
……
</code></pre>
<pre><code class="language-java">Map&lt;Boolean, List&lt;Person&gt;&gt; children = Stream.generate(new PersonSupplier()).
 limit(100).
 collect(Collectors.partitioningBy(p -&gt; p.getAge() &lt; 18));
System.out.println(&quot;Children number: &quot; + children.get(true).size());
System.out.println(&quot;Adult number: &quot; + children.get(false).size());
</code></pre>
<p>输出结果：</p>
<pre><code class="language-java">Children number: 23 
     Adult number: 77
</code></pre>
<p>在使用条件“年龄小于 18”进行分组后可以看到，不到 18 岁的未成年人是一组，成年人是另外一组。partitioningBy 其实是一种特殊的 groupingBy，它依照条件测试的是否两种结果来构造返回的数据结构，get(true) 和 get(false) 能即为全部的元素对象。</p>
<h2 id="结束语">结束语</h2>
<p>总之，Stream 的特性可以归纳为：</p>
<ul>
<li>
<p>它没有内部存储，它只是用操作管道从 source（数据结构、数组、generator function、IO channel）抓取数据。</p>
</li>
<li>
<p>它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream，而不是从 source 删除那些元素。</p>
</li>
<li>
<p>所有 Stream 的操作必须以 lambda 表达式为参数</p>
</li>
<li>
<p>不支持索引访问</p>
</li>
<li>
<p>你可以请求第一个元素，但无法请求第二个，第三个，或最后一个。不过请参阅下一项。</p>
</li>
<li>
<p>很多 Stream 操作是向后延迟的，一直到它弄清楚了最后需要多少数据才会开始。</p>
</li>
<li>
<p>Intermediate 操作永远是惰性化的。</p>
</li>
<li>
<p>当一个 Stream 是并行化的，就不需要再写多线程代码，所有对它的操作会自动并行进行的。</p>
</li>
<li>
<p>可以是无限的</p>
<ul>
<li>集合有固定大小，Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。</li>
</ul>
</li>
</ul>
<p><a href="http://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html">Java 8 中的 Streams API 详解</a></p>
]]></content>
    </entry>
</feed>