<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://hltj.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://hltj.me/" rel="alternate" type="text/html" hreflang="zh-Hans" /><updated>2025-10-20T16:29:31+00:00</updated><id>https://hltj.me/feed.xml</id><title type="html">灰蓝时光</title><subtitle>个人原创与心得分享。大到编程、架构、技术管理、培训技能，小到瞬时感悟与点滴心得。
</subtitle><entry><title type="html">wxMEdit 3.2 发布了！新增法文翻译、RISC-V 打包</title><link href="https://hltj.me/wxmedit/2023/07/09/wxmedit-32.html" rel="alternate" type="text/html" title="wxMEdit 3.2 发布了！新增法文翻译、RISC-V 打包" /><published>2023-07-09T14:18:14+00:00</published><updated>2023-07-09T14:18:14+00:00</updated><id>https://hltj.me/wxmedit/2023/07/09/wxmedit-32</id><content type="html" xml:base="https://hltj.me/wxmedit/2023/07/09/wxmedit-32.html"><![CDATA[<p>刚刚发布了 wxMEdit 3.2，并开启了版本更新检测。</p>

<p>wxMEdit 是对编码和十六进制编辑支持很好的文本编辑器，尤其是在十六进制模式下也能支持各种编码。
wxMEdit 是 MadEdit 的后继，并对其做了很多改进。下载地址为：</p>

<p><a href="https://wxmedit.github.io/zh_CN/downloads.html">https://wxmedit.github.io/zh_CN/downloads.html</a></p>

<p>欢迎大家下载使用，有问题可以在 <a href="https://github.com/wxMEdit/wxMEdit/issues">GitHub 上反馈</a>或者通过其他方式联系我。</p>

<!--more-->

<style>
kbd {
  background-color: #eee;
  border-radius: 3px;
  border: 1px solid #b4b4b4;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 2px 0 0 rgba(255, 255, 255, 0.7) inset;
  color: #333;
  display: inline-block;
  font-family: Arial,Helvetica,sans-serif;
  font-size: 0.85em;
  line-height: 1;
  padding: 2px 4px;
  white-space: nowrap;
}
</style>

<p>wxMEdit 3.2 由共同维护人 jerome KASPER 重新设计了图标，包括程序主图标及菜单/工具栏图标，给人耳目一新之感。
jerome KASPER 同时还为 wxMEdit 添加了法文翻译。</p>

<p><img src="/assets/wxmedit/wxmedit_fr_deepin.png" alt="wxMEdit 3.2 切换到法文，在 deepin 中运行" /></p>

<p>wxMEdit 3.2 的重大改进还有：
新增 Win64、国产发行版以及 RISC-V 架构的包，
支持国家最新中文编码标准 GB18030-2022，
Unicode 支持到 15.0，
Windows 下支持 High DPI，
支持 GTK + 3 与 Wayland，
调整了一部分快捷键。</p>

<p>调整快捷键是个不兼容变更，主要是去掉冲突、冗余的快捷键，增加小键盘快捷键，参见 <a href="https://github.com/wxMEdit/wxMEdit/wiki/Keyboard-shortcuts-change-in-wxMEdit-3.2">wxMEdit 3.2 中的快捷键变更</a>。</p>

<p>wxMEdit 3.2 相比 wxMEdit 3.1 兼容性更好、更加稳定，修复了多个崩溃问题、兼容性问题以及一些其他小问题。
其中有一个值得一提的是修复 <kbd>AltGr</kbd> 输入字符问题，
国内可能用这个功能比较少（也有，比如切换到支持输入拼音声调键盘布局），欧美用到的更多一些。
从 2018 年到今年也花了很多精力定位及修复该问题。
期间了解到 Windows 下的 <kbd>AltGr</kbd> 与 <kbd>Ctrl</kbd>-<kbd>Alt</kbd> 等效，wxWidgets 也采取了相同方案，
这也是调整部分快捷键的一个原因，将原有的 7 个 <kbd>Ctrl</kbd>-<kbd>Alt</kbd>-<kbd>字母</kbd> 快捷键调整为
<kbd>Shift</kbd>-<kbd>Alt</kbd>-<kbd>字母</kbd>。</p>

<p>此外 wxMEdit 3.2 所打发布包也与以往不同。
对于 Windows 版，新增了原生 Windows 64 位的包，同时提供了 7-zip 与 zip 格式；
另外为了解决新版 ICU 库不支持 Windows XP 的问题，用到了 <a href="https://github.com/Chuyu-Team/VC-LTL5">VC-LTL</a> 与 <a href="https://github.com/Chuyu-Team/YY-Thunks">YY-Thunks</a>，
经验证完全解决了 XP 的问题；
不过有点遗憾的是这样打出来的包在 ReactOS 下无法运行，因此为 ReactOS 单独打包，ReactOS 包的 ICU 版本停留在 58，只能支持到 Unicode 9。
而为 Linux 发行版打包才是大头，这个周末打了好多包。除了为国际主流发行版打包外，还支持了国产根社区的三个发行版：openEuler、openKylin、deepin
（另外两个 OpenAnolis、OpenCloudOS 的根社区版未提供 wxWidgets，无法打包）。
此外，由于 RISC-V 生态的兴起，wxMEdit 3.2 也为多款操作系统（Ubuntu、FreeBSD、openSUSE、openEuler、openKylin、deepin）提供 RISC-V 架构的包，但是由于打包环境问题，还有几个未完成，将会在接下来一两周内补上。</p>

<p>wxMEdit 3.2 的改动记录如下：</p>
<ol>
  <li>添加新功能：以人类可读的格式显示文件大小。</li>
  <li>添加了始终使用特定编码打开文件的选项。</li>
  <li>新增对 GTK+ 3 以及 Wayland 的支持。</li>
  <li>新增对 Windows 下的 High DPI 的支持。</li>
  <li>重新设计了图标（由 jerome KASPER 设计）。</li>
  <li>更改了一些快捷键，请参见 <a href="https://github.com/wxMEdit/wxMEdit/wiki/Keyboard-shortcuts-change-in-wxMEdit-3.2">wxMEdit 3.2 中的快捷键变更</a>。</li>
  <li>将 GB18030 支持更新到了最新标准 GB18030-2022。</li>
  <li>更新了 Unicode 15.0 的 Unicode 块描述（需要 ICU 72 或更高版本）</li>
  <li>修复了文本模式下行长超过限制时导致崩溃的问题。</li>
  <li>修复了在“关于 wxMEdit”对话框点击“确定”按钮时导致崩溃的问题。</li>
  <li>修复了在按窗口自动换行时文本中包含一些制表符导致无法响应的问题。</li>
  <li>修复了 wxMEdit 3.1 中破坏的三击选中一行的行为。</li>
  <li>修复了（使用 wxMSW-2.8 构建的）MadEdit/wxMEdit 中信息窗口每次高度减少 4 的问题。</li>
  <li>修复了重启后字体与编码未恢复的问题。</li>
  <li>修复了无法通过 AltGr 输入字符的问题。</li>
  <li>修复了 Linux 下光标闪烁与选区反色的渲染问题。</li>
  <li>修复了 Windows 10 下 IME 候选窗口不跟随光标的问题。</li>
  <li>添加了法文翻译（jerome KASPER）。</li>
  <li>更新了简体中文翻译。</li>
  <li>其他小修改及问题修复。</li>
</ol>

<blockquote>
  <h5 id="修订记录">修订记录</h5>
  <p><small>2023-07-10：小调整并附图</small></p>
</blockquote>]]></content><author><name></name></author><category term="wxmedit" /><summary type="html"><![CDATA[刚刚发布了 wxMEdit 3.2，并开启了版本更新检测。 wxMEdit 是对编码和十六进制编辑支持很好的文本编辑器，尤其是在十六进制模式下也能支持各种编码。 wxMEdit 是 MadEdit 的后继，并对其做了很多改进。下载地址为： https://wxmedit.github.io/zh_CN/downloads.html 欢迎大家下载使用，有问题可以在 GitHub 上反馈或者通过其他方式联系我。]]></summary></entry><entry><title type="html">【灰蓝 Java 训练】如何处理空值</title><link href="https://hltj.me/java/2021/01/09/java-exercise-nulls.html" rel="alternate" type="text/html" title="【灰蓝 Java 训练】如何处理空值" /><published>2021-01-09T11:06:55+00:00</published><updated>2021-01-09T11:06:55+00:00</updated><id>https://hltj.me/java/2021/01/09/java-exercise-nulls</id><content type="html" xml:base="https://hltj.me/java/2021/01/09/java-exercise-nulls.html"><![CDATA[<blockquote>
  <p>这个系列以练习为主，可能不会有多少讲述（当然本篇例外），可以作为初学者的自学验收之用。</p>
</blockquote>

<p>Java 中有非受限的空值，并且不知哪时会引发 NPE（即 <code class="language-plaintext highlighter-rouge">NullPointerException</code>），解决这个问题对于 Android 开发来说很简单——用 Kotlin 就好了。
其实不仅限于 Android，对于服务端开发来说终极方案也应该是迁移到 Kotlin。
因为只要用 Java，空值问题就没办法彻底解决（之前在<a href="https://hltj.me/lang/2019/07/08/modern-lang-optional-value.html">《现代编程语言系列2：安全表达可选值》</a>中也提到过这点），而 JVM 平台主流工业级语言中只有 Kotlin 很好地解决了这一问题。</p>

<p>但是对于服务端开发来说，常有各种非技术原因不能在项目中以 Kotlin 取代 Java，对于这些项目来说显然没办法彻底解决空值问题。
那么有没有一些方法与工具可以让空值问题处理起来尽可能规范、简易些呢？这里有几点经验分享。
<!--more--></p>

<h2 id="npe-防御">NPE 防御</h2>
<p>一些典型场景的 NPE 可以通过编码习惯来防御——某些静态分析工具或许也能检测到一些问题，但很难完美覆盖；
没办法，只能通过编码规范、程序员的自律来解决了。
其中比较常见的两个场景是空值比较以及使用不可变集合。</p>

<h3 id="空值比较">空值比较</h3>
<p>对实际值为 <code class="language-plaintext highlighter-rouge">null</code> 的变量调用包括 <code class="language-plaintext highlighter-rouge">equals()</code> 在内的任何方法都会导致 NPE。
因此比较可空值（通常为变量）与非空值（通常为常量，不尽然）时，以可空值为参数对非空值调 <code class="language-plaintext highlighter-rouge">equals()</code> 即可避免这个问题。
例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="s">"Hello"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">nullableStr</span><span class="o">))</span> <span class="o">{</span>
    <span class="err">……</span>
<span class="o">}</span>
</code></pre></div></div>

<p>如果比较两个可空值怎么办呢？用 <code class="language-plaintext highlighter-rouge">Objects.equals()</code>，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="nc">Objects</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">nullableObj1</span><span class="o">,</span> <span class="n">nullableObj2</span><span class="o">))</span> <span class="o">{</span>
    <span class="err">……</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="不可变集合不支持空值">不可变集合不支持空值</h3>
<p>Java 9 引入的 <code class="language-plaintext highlighter-rouge">List.of()</code>、<code class="language-plaintext highlighter-rouge">Set.of()</code>、<code class="language-plaintext highlighter-rouge">Map.of()</code>、<code class="language-plaintext highlighter-rouge">Map.entry()</code> 以及 Java 10 引入的 <code class="language-plaintext highlighter-rouge">List.copyOf()</code>、<code class="language-plaintext highlighter-rouge">Set.copyOf()</code>、<code class="language-plaintext highlighter-rouge">Map.copyOf()</code>、<code class="language-plaintext highlighter-rouge">Collectors.toUnmodifiableList()</code>、<code class="language-plaintext highlighter-rouge">Collectors.toUnmodifiableSet()</code>、<code class="language-plaintext highlighter-rouge">Collectors.toUnmodifiableMap()</code> 等均不支持 <code class="language-plaintext highlighter-rouge">null</code>，其中构造不可变的 <code class="language-plaintext highlighter-rouge">Map</code> 与 <code class="language-plaintext highlighter-rouge">Map.Entry</code> 时 key、value 均不能为 <code class="language-plaintext highlighter-rouge">null</code>。
还需要注意的一点是不能以 <code class="language-plaintext highlighter-rouge">null</code> 值调用不可变集合的 <code class="language-plaintext highlighter-rouge">contains()</code> / <code class="language-plaintext highlighter-rouge">containsKey()</code> / <code class="language-plaintext highlighter-rouge">containsValue()</code> / <code class="language-plaintext highlighter-rouge">containsAll()</code> 方法，其中 <code class="language-plaintext highlighter-rouge">containsAll()</code> 还要求参数集合中不能有 <code class="language-plaintext highlighter-rouge">null</code>。</p>

<p>例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="c1">// NPE：元素不可以有空值</span>
<span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="mi">2</span><span class="o">);</span> <span class="c1">// NPE：key 不可有空值</span>
<span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="s">"b"</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="c1">// NPE：value 不可有空值</span>
<span class="nc">Map</span><span class="o">.</span><span class="na">entry</span><span class="o">(</span><span class="s">"hello"</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="c1">// NPE：value 不可有空值</span>

<span class="kt">var</span> <span class="n">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;();</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="mi">1</span><span class="o">);</span>
<span class="nc">Map</span><span class="o">.</span><span class="na">copyOf</span><span class="o">(</span><span class="n">map</span><span class="o">);</span> <span class="c1">// OK</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="mi">2</span><span class="o">);</span>
<span class="nc">Map</span><span class="o">.</span><span class="na">copyOf</span><span class="o">(</span><span class="n">map</span><span class="o">);</span> <span class="c1">// NPE：key 不可有空值</span>

<span class="c1">// NPE：元素不可以有空值</span>
<span class="nc">Stream</span><span class="o">.</span><span class="na">of</span><span class="o">((</span><span class="nc">String</span><span class="o">)</span><span class="kc">null</span><span class="o">).</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toUnmodifiableList</span><span class="o">());</span> 

<span class="c1">// NPE：不能用 null 调用不可变集合的 containsKey() 方法</span>
<span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="s">"b"</span><span class="o">).</span><span class="na">containsKey</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
</code></pre></div></div>

<p>千万不要因为这点而放弃不可变集合。
不可变集合本身有很多优势，Java 10 及以后版本也推荐使用不可变集合。
只是需要特别注意上述几点：构造字面值时不能有 <code class="language-plaintext highlighter-rouge">null</code>、调用 <code class="language-plaintext highlighter-rouge">contains()</code> / <code class="language-plaintext highlighter-rouge">containsKey()</code> 等之前需要判断参数是否为 <code class="language-plaintext highlighter-rouge">null</code>、调用 <code class="language-plaintext highlighter-rouge">collect()</code> / <code class="language-plaintext highlighter-rouge">copyOf()</code> / <code class="language-plaintext highlighter-rouge">containsAll()</code> 之前去除参数集合中的 <code class="language-plaintext highlighter-rouge">null</code> 值。
例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">isInSet</span> <span class="o">=</span> <span class="n">nullableStr</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nc">Set</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"hello"</span><span class="o">,</span> <span class="s">"world"</span><span class="o">).</span><span class="na">contains</span><span class="o">(</span><span class="n">nullableStr</span><span class="o">);</span>

<span class="nc">Stream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"hello"</span><span class="o">,</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="kc">null</span><span class="o">,</span> <span class="s">"world"</span><span class="o">).</span><span class="na">filter</span><span class="o">(</span><span class="nl">Objects:</span><span class="o">:</span><span class="n">nonNull</span><span class="o">).</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>

<span class="kt">var</span> <span class="n">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;();</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"a"</span><span class="o">,</span> <span class="mi">1</span><span class="o">);</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="mi">2</span><span class="o">);</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"b"</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>

<span class="n">map</span><span class="o">.</span><span class="na">entrySet</span><span class="o">().</span><span class="na">stream</span><span class="o">()</span>
        <span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">entry</span> <span class="o">-&gt;</span> <span class="n">entry</span><span class="o">.</span><span class="na">getKey</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">entry</span><span class="o">.</span><span class="na">getValue</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>
        <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toUnmodifiableMap</span><span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">Entry</span><span class="o">::</span><span class="n">getKey</span><span class="o">,</span> <span class="nc">Map</span><span class="o">.</span><span class="na">Entry</span><span class="o">::</span><span class="n">getValue</span><span class="o">));</span>
</code></pre></div></div>

<h2 id="optional">Optional</h2>
<p>Java 中解决可选值问题，首先应该想到的就是 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html"><code class="language-plaintext highlighter-rouge">Optional</code></a>。
<code class="language-plaintext highlighter-rouge">Optional</code> 是 Java 8 引入的可选值类型，用于在很多场景中取代 <code class="language-plaintext highlighter-rouge">null</code> 来表达可选值，进而避免 <code class="language-plaintext highlighter-rouge">null</code> 所带来问题。</p>

<h3 id="optional-的用法"><code class="language-plaintext highlighter-rouge">Optional</code> 的用法</h3>
<p><code class="language-plaintext highlighter-rouge">Optional</code> 的核心方法有 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#map(java.util.function.Function)"><code class="language-plaintext highlighter-rouge">map()</code></a>、<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#flatMap(java.util.function.Function)"><code class="language-plaintext highlighter-rouge">flatMap()</code></a>、<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#orElse(T)"><code class="language-plaintext highlighter-rouge">orElse()</code></a> 与 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#orElseGet(java.util.function.Supplier)"><code class="language-plaintext highlighter-rouge">orElseGet()</code></a>。</p>

<p><strong><code class="language-plaintext highlighter-rouge">map()</code>：由一个可选值得到另一个可选值</strong></p>

<p>例如对于一个可选的字符串，计算其长度：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">lengthOpt</span> <span class="o">=</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">nullableString</span><span class="o">).</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">length</span><span class="o">);</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">flatMap()</code>：处理可选值嵌套情况</strong></p>

<p>例如，从可空整数列表中取第一个正值，从列表中取第一个元素可以用 <code class="language-plaintext highlighter-rouge">Stream&lt;Integer&gt;#findFirst()</code>，该方法返回 <code class="language-plaintext highlighter-rouge">Optional&lt;Integer&gt;</code>，如果继续用 <code class="language-plaintext highlighter-rouge">map()</code> 的话，会得到可选值的可选值：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;&gt;</span> <span class="n">intOptOpt</span> <span class="o">=</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">nullableIntList</span><span class="o">).</span><span class="na">map</span><span class="o">(</span><span class="n">intList</span> <span class="o">-&gt;</span>
        <span class="n">intList</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">filter</span><span class="o">(</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">).</span><span class="na">findFirst</span><span class="o">()</span>
<span class="o">);</span>
</code></pre></div></div>

<p>而用 <code class="language-plaintext highlighter-rouge">flatMap()</code> 会将两层 <code class="language-plaintext highlighter-rouge">Optional</code> 打平为一层：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">intOpt</span> <span class="o">=</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">nullableIntList</span><span class="o">).</span><span class="na">flatMap</span><span class="o">(</span><span class="n">intList</span> <span class="o">-&gt;</span>
        <span class="n">intList</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">filter</span><span class="o">(</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">).</span><span class="na">findFirst</span><span class="o">()</span>
<span class="o">);</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">orElse()</code>、<code class="language-plaintext highlighter-rouge">orElseGet()</code>：由可选值得到值，如果无值取默认值</strong></p>

<p>例如，对于可选字符串，有值取长度，无值取 <code class="language-plaintext highlighter-rouge">0</code>，可以用 <code class="language-plaintext highlighter-rouge">orElse()</code> 得到一个整数：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">length</span> <span class="o">=</span> <span class="n">strOpt</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">length</span><span class="o">).</span><span class="na">orElse</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
</code></pre></div></div>

<p>如果默认值需要惰性求值，那么还可以用 <code class="language-plaintext highlighter-rouge">orElseGet()</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">lengthOrRandom</span> <span class="o">=</span> <span class="n">strOpt</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">length</span><span class="o">).</span><span class="na">orElseGet</span><span class="o">(()</span> <span class="o">-&gt;</span>
        <span class="k">new</span> <span class="nf">Random</span><span class="o">().</span><span class="na">nextInt</span><span class="o">()</span>
<span class="o">);</span>
</code></pre></div></div>

<p>还有一个特别的场景，就是将 <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> 转换为可空的 <code class="language-plaintext highlighter-rouge">T</code> 值时千万不能想当然地调 <code class="language-plaintext highlighter-rouge">get()</code>——它在无值时抛 <code class="language-plaintext highlighter-rouge">NoSuchElementException</code> 而不是返回 <code class="language-plaintext highlighter-rouge">null</code>（参见其<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#get()">文档</a>）。
正确的方式是 <code class="language-plaintext highlighter-rouge">orElse(null)</code>。例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Integer</span> <span class="n">lengthOrNull</span> <span class="o">=</span> <span class="n">strOpt</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">length</span><span class="o">).</span><span class="na">orElse</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Optional</code> 的其他方法在特定场景也很实用，由于方法不多，大家可以直接读其文档，在此不再赘述。</p>

<h3 id="optional-的问题"><code class="language-plaintext highlighter-rouge">Optional</code> 的问题</h3>
<p>很多文章称 <code class="language-plaintext highlighter-rouge">Optional</code> 是 Java 8 针对 NPE 问题甚至<a href="https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare">十亿美元问题</a>的解决方案，实际上远非如此。且不说在 Java 中没办法强制使用 <code class="language-plaintext highlighter-rouge">Optional</code> 而不用 <code class="language-plaintext highlighter-rouge">null</code>，即便 <code class="language-plaintext highlighter-rouge">Optional</code> 自身用起来也有很多局限性。</p>

<p><strong><code class="language-plaintext highlighter-rouge">Optional</code> 对象自身也可能为 <code class="language-plaintext highlighter-rouge">null</code></strong></p>

<p>一个特别坑的问题是 <code class="language-plaintext highlighter-rouge">Optional</code> 是引用类型，因此一个 <code class="language-plaintext highlighter-rouge">Optional</code> 对象自身也可能为 <code class="language-plaintext highlighter-rouge">null</code>，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">strOpt</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">strOpt</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">length</span><span class="o">);</span> <span class="c1">// NPE</span>
</code></pre></div></div>

<p>当然 <code class="language-plaintext highlighter-rouge">Optional</code> 的官方文档称 <code class="language-plaintext highlighter-rouge">Optional</code> 类型值不应该为 <code class="language-plaintext highlighter-rouge">null</code>，在 IDEA 中写上述代码也会标出警告，但 Java 语法与编译器并不会为此提供任何保障或约束。</p>

<p><strong><code class="language-plaintext highlighter-rouge">Optional</code> 不可序列化</strong></p>

<p><code class="language-plaintext highlighter-rouge">Optional</code> 不可序列化，这就意味着需要序列化的场景还得用 <code class="language-plaintext highlighter-rouge">null</code> 来表达可选值，这也是上文特别提到由 <code class="language-plaintext highlighter-rouge">Optional</code> 转换可空值的主要原因。
鉴于此，<code class="language-plaintext highlighter-rouge">Optional</code> 不适合作为类的字段，也不适合作为方法的入参，只适用于局部变量、返回值以及表达式中间结果等场景。
IDEA 也会对 <code class="language-plaintext highlighter-rouge">Optional</code> 用作字段或者参数时标记警告。</p>

<p><strong>不够简洁</strong></p>

<p>与受限空值语法相比，<code class="language-plaintext highlighter-rouge">Optional</code> 用法要冗长的多，当然这很符合 Java 的历史风格。
对于受限空值的 <code class="language-plaintext highlighter-rouge">?.</code> 语法，<code class="language-plaintext highlighter-rouge">Optional</code> 要用 <code class="language-plaintext highlighter-rouge">map()</code> 与 <code class="language-plaintext highlighter-rouge">flatMap()</code>；对于 <code class="language-plaintext highlighter-rouge">?:</code>/<code class="language-plaintext highlighter-rouge">??</code> 语法要用 <code class="language-plaintext highlighter-rouge">orElse()</code> 或 <code class="language-plaintext highlighter-rouge">orElseGet()</code>。
其实对于采用可选值类型的其他语言来说可能都有类似问题，但是 <code class="language-plaintext highlighter-rouge">Haskell</code>、<code class="language-plaintext highlighter-rouge">Scala</code> 有推导式，<code class="language-plaintext highlighter-rouge">Haskell</code>、<code class="language-plaintext highlighter-rouge">Scala</code>、<code class="language-plaintext highlighter-rouge">OCaml</code>、<code class="language-plaintext highlighter-rouge">F#</code> 等语言还支持自定义操作符，从而简化可选值的用法。
不幸的是 Java 不具备这些语法。</p>

<h2 id="可空性注解">可空性注解</h2>
<p>在 Java 中没办法强制区分可空与非空类型，而有时又希望能在字段、参数或者返回值上标注是否可空，以便 IDE 或者其他静态检查工具能够识别并给出提醒。</p>

<p>我们看个示例，实现一个简陋版的 <code class="language-plaintext highlighter-rouge">?:</code>/<code class="language-plaintext highlighter-rouge">??</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">defaultWith</span><span class="o">(</span><span class="no">T</span> <span class="n">obj</span><span class="o">,</span> <span class="no">T</span> <span class="n">defaultVal</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">obj</span><span class="o">).</span><span class="na">orElse</span><span class="o">(</span><span class="n">defaultVal</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>当 <code class="language-plaintext highlighter-rouge">obj</code> 非空时该方法返回 <code class="language-plaintext highlighter-rouge">obj</code>，否则返回 <code class="language-plaintext highlighter-rouge">defaultVal</code>。
但是实际上事与愿违，这个方法在 <code class="language-plaintext highlighter-rouge">obj</code> 为空时不会返回 <code class="language-plaintext highlighter-rouge">defaultVal</code>，而是抛 NPE。
原因是 <code class="language-plaintext highlighter-rouge">Optional.of()</code> 只接受非空参数，如果参数为空就会抛 NPE。
遗憾的是编写与编译这段代码都不会收到任何警告或提醒，因为无论 IDE 还是编译器都无从知晓这个方法的入参 <code class="language-plaintext highlighter-rouge">obj</code> 可能为空。</p>

<p>此时，如果我们给 <code class="language-plaintext highlighter-rouge">obj</code> 加一个 <code class="language-plaintext highlighter-rouge">org.jetbrains.annotations.Nullable</code> 注解，IDEA 就会对方法中使用 <code class="language-plaintext highlighter-rouge">obj</code> 调用 <code class="language-plaintext highlighter-rouge">Optional.of()</code> 标出警告提醒：“Argument ‘obj’ might be null”。
改为调用 <code class="language-plaintext highlighter-rouge">Optional.ofNullable()</code> 警告就消失了。
而如果希望 <code class="language-plaintext highlighter-rouge">defaultVal</code> 要求非空的话，还可以对 <code class="language-plaintext highlighter-rouge">defaultVal</code> 标注 <code class="language-plaintext highlighter-rouge">org.jetbrains.annotations.NotNull</code>，这样一来方法的返回值也会非空，同样可以标注 <code class="language-plaintext highlighter-rouge">NotNull</code>：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@NotNull</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">defaultWith</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="no">T</span> <span class="n">obj</span><span class="o">,</span> <span class="nd">@NotNull</span> <span class="no">T</span> <span class="n">defaultVal</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">obj</span><span class="o">).</span><span class="na">orElse</span><span class="o">(</span><span class="n">defaultVal</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>当然，这只是关于可空性注解使用场景的一个示例，实际上并不需要我们自己造一个这样的轮子，因为有现成的轮子可用（见下文）。
上述 <code class="language-plaintext highlighter-rouge">Nullable</code> 与 <code class="language-plaintext highlighter-rouge">NotNull</code> 两个注解来自于 <a href="https://www.jetbrains.com/help/idea/nullable-and-notnull-annotations.html">JetBrains 的 java-annotations</a>。类似的还有 <a href="https://checkerframework.org/manual/#nullness-checker">Checker Framework 的 Nullness Checker</a>、<a href="https://javadoc.io/doc/com.github.spotbugs/spotbugs-annotations/latest/index.html">spotbugs-annotations</a>、<a href="https://projectlombok.org/features/NonNull">Lombok 的 NonNull</a> 等。</p>

<h2 id="apache-commons">Apache Commons</h2>
<p><a href="https://commons.apache.org/">Apache Commons</a> 有多个库提供了简化空值使用的工具。例如实现类似 <code class="language-plaintext highlighter-rouge">?:</code>/<code class="language-plaintext highlighter-rouge">??</code> 的功能，使用 <code class="language-plaintext highlighter-rouge">Optional</code> 需要这样写：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">nonNullVal</span> <span class="o">=</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">obj</span><span class="o">).</span><span class="na">orElse</span><span class="o">(</span><span class="n">nonNullDefault</span><span class="o">);</span>
</code></pre></div></div>

<p>而使用 Apache Commons 只需调用一个类似上文实现的静态方法就可以了。</p>

<h3 id="stringutils">StringUtils</h3>
<p><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html"><code class="language-plaintext highlighter-rouge">StringUtils</code></a> 是 <a href="https://commons.apache.org/proper/commons-lang/">Commons Lang</a> 中的一个工具类，其中包含一系列与字符串空值相关的方法。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#isEmpty-java.lang.CharSequence-"><code class="language-plaintext highlighter-rouge">isEmpty()</code></a> 与 <a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#isNotEmpty-java.lang.CharSequence-"><code class="language-plaintext highlighter-rouge">isNotEmpty()</code></a></strong></p>

<p>前者判断一个字符序列是否为 <code class="language-plaintext highlighter-rouge">null</code> 或空序列，后者与之相反——判断一个字符序列既非 <code class="language-plaintext highlighter-rouge">null</code> 也非空序列。
这两个方法非常实用，现实业务中对字符串为 <code class="language-plaintext highlighter-rouge">null</code> 或空串时走同样处理分支的场景很常见。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#defaultString-java.lang.String-java.lang.String-"><code class="language-plaintext highlighter-rouge">defaultString()</code></a></strong></p>

<p><code class="language-plaintext highlighter-rouge">defaultString(str, defaultStr)</code> 如果 <code class="language-plaintext highlighter-rouge">str</code> 非 <code class="language-plaintext highlighter-rouge">null</code> 返回 <code class="language-plaintext highlighter-rouge">str</code>，否则返回 <code class="language-plaintext highlighter-rouge">defaultStr</code>。
还有一个单参重载版本：<code class="language-plaintext highlighter-rouge">defaultString(str)</code> 如果非 <code class="language-plaintext highlighter-rouge">null</code> 返回 <code class="language-plaintext highlighter-rouge">str</code>，否则返回空串。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#defaultIfEmpty-T-T-"><code class="language-plaintext highlighter-rouge">defaultIfEmpty()</code></a></strong></p>

<p><code class="language-plaintext highlighter-rouge">defaultIfEmpty(str, defaultStr)</code> 如果 <code class="language-plaintext highlighter-rouge">str</code> 既非 <code class="language-plaintext highlighter-rouge">null</code> 也非空串返回 <code class="language-plaintext highlighter-rouge">str</code>，否则返回 <code class="language-plaintext highlighter-rouge">defaultStr</code>。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#getIfEmpty-T-java.util.function.Supplier-"><code class="language-plaintext highlighter-rouge">getIfEmpty()</code></a></strong></p>

<p>相当于默认值采用惰性求值的 <code class="language-plaintext highlighter-rouge">defaultIfEmpty()</code>，<code class="language-plaintext highlighter-rouge">getIfEmpty(str, () -&gt; ……)</code> 当 <code class="language-plaintext highlighter-rouge">str</code> 既非 <code class="language-plaintext highlighter-rouge">null</code> 也非空串时返回 <code class="language-plaintext highlighter-rouge">str</code>，否则对 lambda 表达式求值并以其返回值作为 <code class="language-plaintext highlighter-rouge">getIfEmpty()</code> 的返回值。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#firstNonEmpty-T...-"><code class="language-plaintext highlighter-rouge">firstNonEmpty()</code></a></strong></p>

<p>对于一系列值，返回第一个既非 <code class="language-plaintext highlighter-rouge">null</code> 也非空串的。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#isAllEmpty-java.lang.CharSequence...-"><code class="language-plaintext highlighter-rouge">isAllEmpty()</code></a>、<a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#isAnyEmpty-java.lang.CharSequence...-"><code class="language-plaintext highlighter-rouge">isAnyEmpty()</code></a>、<a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#isNoneEmpty-java.lang.CharSequence...-"><code class="language-plaintext highlighter-rouge">isNoneEmpty()</code></a></strong></p>

<p>对于一系列值，判断是否<strong>全都是</strong>、<strong>其中有</strong>、<strong>全都不是</strong> <code class="language-plaintext highlighter-rouge">null</code> 或空串。</p>

<p>除了这些明显与空值相关的方法外，<code class="language-plaintext highlighter-rouge">StringUtils</code> 的其他方法也都会对 <code class="language-plaintext highlighter-rouge">null</code> 特殊处理而不是引发 NPE（个别的会抛其他异常）。</p>

<h3 id="commons-collections">Commons Collections</h3>
<p>与 <code class="language-plaintext highlighter-rouge">StringUtils</code> 类似，<a href="https://commons.apache.org/proper/commons-collections/">Commons Collections</a> 中的 <code class="language-plaintext highlighter-rouge">CollectionUtils</code>、<code class="language-plaintext highlighter-rouge">IterableUtils</code>、<code class="language-plaintext highlighter-rouge">ListUtils</code>、<code class="language-plaintext highlighter-rouge">SetUtils</code>、<code class="language-plaintext highlighter-rouge">MapUtils</code> 等也有提供 <code class="language-plaintext highlighter-rouge">null</code> 与空集合合并处理的方法，只是没有那么丰富。</p>

<p><strong><code class="language-plaintext highlighter-rouge">emptyIfNull()</code></strong></p>

<p><code class="language-plaintext highlighter-rouge">CollectionUtils</code>、<code class="language-plaintext highlighter-rouge">IterableUtils</code>、<code class="language-plaintext highlighter-rouge">ListUtils</code>、<code class="language-plaintext highlighter-rouge">SetUtils</code>、<code class="language-plaintext highlighter-rouge">MapUtils</code> 等均有提供该方法，如果参数非 <code class="language-plaintext highlighter-rouge">null</code> 返回参数本身，否则返回对应类型的空集合。</p>

<p><strong><code class="language-plaintext highlighter-rouge">isEmpty()</code> 与 <code class="language-plaintext highlighter-rouge">isNotEmpty()</code></strong></p>

<p><code class="language-plaintext highlighter-rouge">CollectionUtils</code>（可以用于 <code class="language-plaintext highlighter-rouge">Collection</code> 及其子类型如 <code class="language-plaintext highlighter-rouge">List</code>、<code class="language-plaintext highlighter-rouge">Set</code>） 与 <code class="language-plaintext highlighter-rouge">MapUtils</code> 提供了这两个方法。前者判断是否为 <code class="language-plaintext highlighter-rouge">null</code> 或为空集合，后者相反——判断既非 <code class="language-plaintext highlighter-rouge">null</code> 也非空集合。</p>

<p><code class="language-plaintext highlighter-rouge">CollectionUtils</code>、<code class="language-plaintext highlighter-rouge">IterableUtils</code>、<code class="language-plaintext highlighter-rouge">ListUtils</code>、<code class="language-plaintext highlighter-rouge">SetUtils</code>、<code class="language-plaintext highlighter-rouge">MapUtils</code> 等提供的大多数其他方法也都会对 <code class="language-plaintext highlighter-rouge">null</code> 特殊处理而不是引发 NPE，个别会抛 NPE 的方法文档中也有说明。</p>

<h3 id="objectutils">ObjectUtils</h3>
<p>更通用的情况还可以用 Commons Lang 中的工具类 <a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html"><code class="language-plaintext highlighter-rouge">ObjectUtils</code></a>。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#defaultIfNull-T-T-"><code class="language-plaintext highlighter-rouge">defaultIfNull()</code></a></strong></p>

<p>类似上文自行实现的 <code class="language-plaintext highlighter-rouge">defaultWith()</code>，不过并没有标可空性注解。
<code class="language-plaintext highlighter-rouge">defaultIfNull(obj, defaultVal)</code> 当 <code class="language-plaintext highlighter-rouge">obj</code> 非空时返回 <code class="language-plaintext highlighter-rouge">obj</code> 否则返回 <code class="language-plaintext highlighter-rouge">defaultVal</code>。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#getIfNull-T-java.util.function.Supplier-"><code class="language-plaintext highlighter-rouge">getIfNull()</code></a></strong></p>

<p>相当于默认值采用惰性求值的 <code class="language-plaintext highlighter-rouge">defaultIfNull()</code>，<code class="language-plaintext highlighter-rouge">getIfNull(obj, () -&gt; ……)</code> 当 <code class="language-plaintext highlighter-rouge">obj</code> 非空时返回 <code class="language-plaintext highlighter-rouge">obj</code>，否则对 lambda 表达式求值并以其返回值作为 <code class="language-plaintext highlighter-rouge">getIfNull()</code> 的返回值。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#firstNonNull-T...-"><code class="language-plaintext highlighter-rouge">firstNonNull()</code></a></strong></p>

<p>对于一系列值，返回第一个非空的。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#getFirstNonNull-java.util.function.Supplier...-"><code class="language-plaintext highlighter-rouge">getFirstNonNull()</code></a></strong></p>

<p>可以看作是惰性求值版的 <code class="language-plaintext highlighter-rouge">firstNonNull()</code>，对于一系列求值过程返回第一个求值结果非空的结果。
例如 <code class="language-plaintext highlighter-rouge">getFirstNonNull(() -&gt; null, () -&gt; "hello", () -&gt; throw new IllegalStateException())</code> 会返回 <code class="language-plaintext highlighter-rouge">"hello"</code> 而不会执行后面的求值过程，因此不会抛 <code class="language-plaintext highlighter-rouge">IllegalStateException</code>。</p>

<p><strong><a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#allNotNull-java.lang.Object...-"><code class="language-plaintext highlighter-rouge">allNotNull</code></a>、<a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#allNull-java.lang.Object...-"><code class="language-plaintext highlighter-rouge">allNull</code></a>、<a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#anyNotNull-java.lang.Object...-"><code class="language-plaintext highlighter-rouge">anyNotNull</code></a>、<a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/ObjectUtils.html#anyNull-java.lang.Object...-"><code class="language-plaintext highlighter-rouge">anyNull</code></a></strong></p>

<p>对于一系列值，判断是否<strong>全都非</strong>、<strong>全都是</strong>、<strong>其中有非</strong>、<strong>其中有</strong>空值。</p>

<p><code class="language-plaintext highlighter-rouge">ObjectUtils</code> 中的大多数其他方法也都会对空值特殊处理而不是引发 NPE。除了 <code class="language-plaintext highlighter-rouge">StringUtils</code>、<code class="language-plaintext highlighter-rouge">ObjectUtils</code> 之外，Commons Lang 中的 <a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/BooleanUtils.html"><code class="language-plaintext highlighter-rouge">BooleanUtils</code></a>、<a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/math/NumberUtils.html"><code class="language-plaintext highlighter-rouge">NumberUtils</code></a> 也都提供了一系列空安全的工具方法。</p>

<h2 id="其他">其他</h2>
<p>抛砖引玉，欢迎补充。</p>

<h2 id="小结">小结</h2>
<p>Java 语言自身目前没办法彻底解决空值问题，不过有一些方法、工具可以用：</p>
<ul>
  <li>NPE 防御：空值比较、不可变集合不支持空值</li>
  <li><code class="language-plaintext highlighter-rouge">Optional</code></li>
  <li>可空性注解</li>
  <li>Apache Commons</li>
</ul>

<p>光说不练无异于纸上谈兵，接下来的练习才是重中之重。</p>

<h2 id="练习">练习</h2>
<p>以下练习中未标注解的变量，如果变量名以 <code class="language-plaintext highlighter-rouge">x</code> 开头也表示可能为空。</p>
<blockquote>
  <p>为什么不直接用 <code class="language-plaintext highlighter-rouge">nullableXyz</code> 这种更明显的方式？因为现实代码中通常更不明显。</p>
</blockquote>

<h3 id="1-纠错">1、 纠错</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">xMethod</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"POST"</span><span class="o">))</span> <span class="o">{</span>
   <span class="n">doPost</span><span class="o">();</span>
<span class="o">}</span>

<span class="k">if</span> <span class="o">(</span><span class="n">xArg1</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">xArg2</span><span class="o">))</span> <span class="o">{</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"arg1 == arg2"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="2纠错">2、纠错</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">map1</span> <span class="o">=</span> <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"abc"</span><span class="o">,</span> <span class="mi">10</span><span class="o">,</span> <span class="s">"def"</span><span class="o">,</span> <span class="mi">20</span><span class="o">,</span> <span class="n">xStr</span><span class="o">,</span> <span class="mi">30</span><span class="o">);</span>

<span class="kt">var</span> <span class="n">list1</span> <span class="o">=</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="o">-</span><span class="mi">3</span><span class="o">,</span> <span class="mi">9</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="mi">15</span><span class="o">);</span>
<span class="kt">var</span> <span class="n">set1</span> <span class="o">=</span> <span class="nc">Set</span><span class="o">.</span><span class="na">copyOf</span><span class="o">(</span><span class="n">list1</span><span class="o">);</span>

<span class="k">if</span> <span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"hello"</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="s">"world"</span><span class="o">,</span> <span class="mi">2</span><span class="o">).</span><span class="na">containsKey</span><span class="o">(</span><span class="n">xStr</span><span class="o">))</span> <span class="o">{</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"either 'hello' or 'world'"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="3加注可空性注解">3、加注可空性注解</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">,</span> <span class="no">U</span><span class="o">&gt;</span> <span class="no">U</span> <span class="nf">mapSome</span><span class="o">(</span><span class="no">T</span> <span class="n">x</span><span class="o">,</span> <span class="nc">Function</span><span class="o">&lt;</span><span class="no">T</span><span class="o">,</span> <span class="no">U</span><span class="o">&gt;</span> <span class="n">mapper</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">x</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="kc">null</span><span class="o">:</span> <span class="n">mapper</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">x</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="4用-optional-重构上题-mapsome">4、用 <code class="language-plaintext highlighter-rouge">Optional</code> 重构上题 <code class="language-plaintext highlighter-rouge">mapSome()</code></h3>
<blockquote>
  <p>注：只是练习 <code class="language-plaintext highlighter-rouge">Optional</code> 的使用，上题的实现并不需要以 <code class="language-plaintext highlighter-rouge">Optional</code> 取代。</p>
</blockquote>

<h3 id="5用-optional-重构">5、用 <code class="language-plaintext highlighter-rouge">Optional</code> 重构</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Integer</span> <span class="n">xInt</span> <span class="o">=</span> <span class="nc">Math</span><span class="o">.</span><span class="na">random</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mf">0.8</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="nc">Math</span><span class="o">.</span><span class="na">random</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mf">0.5</span> <span class="o">?</span> <span class="mi">5</span> <span class="o">:</span> <span class="mi">12</span><span class="o">;</span>

<span class="c1">// 重构以下代码</span>
<span class="kt">var</span> <span class="n">x1</span> <span class="o">=</span> <span class="o">(</span><span class="n">xInt</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">xInt</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="n">xInt</span> <span class="o">/</span> <span class="mi">2</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">x1</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">x1</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="6用-optional-重构-gettitledcontent">6、用 <code class="language-plaintext highlighter-rouge">Optional</code> 重构 <code class="language-plaintext highlighter-rouge">getTitledContent()</code></h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@NotNull</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getUpperTitle</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Post</span> <span class="n">post</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">post</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">post</span><span class="o">.</span><span class="na">getTitle</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">warning</span><span class="o">(</span><span class="s">"no title"</span><span class="o">)</span>
        <span class="k">return</span> <span class="s">"- UNTITLED -"</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="k">return</span> <span class="n">post</span><span class="o">.</span><span class="na">getTitle</span><span class="o">().</span><span class="na">toUpperCase</span><span class="o">();</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Post</span> <span class="o">{</span>
    <span class="nd">@Nullable</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getTitle</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="7用-stringutils-将-getchoice-的实现重构成一行代码">7、用 <code class="language-plaintext highlighter-rouge">StringUtils</code> 将 <code class="language-plaintext highlighter-rouge">getChoice()</code> 的实现重构成一行代码</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="nf">getChoice</span><span class="o">(</span><span class="nc">String</span> <span class="n">choice</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">highest</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">choice</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">choice</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span>
        <span class="k">return</span> <span class="n">choice</span><span class="o">;</span>

    <span class="k">if</span> <span class="o">(</span><span class="n">highest</span><span class="o">)</span>
        <span class="k">return</span> <span class="s">"High"</span><span class="o">;</span>

    <span class="k">return</span> <span class="s">"Low"</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="8使用-common-collections-重构-getidsstring">8、使用 Common Collections 重构 <code class="language-plaintext highlighter-rouge">getIdsString()</code></h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="no">IMPLICIT_IDS</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">101</span><span class="o">,</span> <span class="mi">111</span><span class="o">,</span> <span class="mi">191</span><span class="o">);</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getIdsString</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Collection</span><span class="o">&lt;</span><span class="nd">@NotNull</span> <span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">ids</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">ids</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="no">IMPLICIT_IDS</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
                <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Object:</span><span class="o">:</span><span class="n">toString</span><span class="o">)</span>
                <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">joining</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="k">return</span> <span class="nc">Stream</span><span class="o">.</span><span class="na">concat</span><span class="o">(</span><span class="n">ids</span><span class="o">.</span><span class="na">stream</span><span class="o">(),</span> <span class="no">IMPLICIT_IDS</span><span class="o">.</span><span class="na">stream</span><span class="o">())</span>
            <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">Object:</span><span class="o">:</span><span class="n">toString</span><span class="o">)</span>
            <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">joining</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="9使用-booleanutils-重构">9、使用 <code class="language-plaintext highlighter-rouge">BooleanUtils</code> 重构</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">toHex</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Boolean</span> <span class="n">useUpper</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">String</span> <span class="n">s</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">n</span><span class="o">,</span> <span class="mi">16</span><span class="o">);</span>
    <span class="k">return</span> <span class="n">useUpper</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">useUpper</span> <span class="o">?</span> <span class="n">s</span><span class="o">.</span><span class="na">toUpperCase</span><span class="o">()</span> <span class="o">:</span> <span class="n">s</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="10使用-objectutils-重构">10、使用 <code class="language-plaintext highlighter-rouge">ObjectUtils</code> 重构</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">Instant</span> <span class="nf">tomorrowOf</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">Instant</span> <span class="n">x</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">x</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">log</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"the base Date is null"</span><span class="o">);</span>
        <span class="n">x</span> <span class="o">=</span> <span class="nc">Instant</span><span class="o">.</span><span class="na">now</span><span class="o">();</span>
    <span class="o">}</span>
    
    <span class="k">return</span> <span class="n">x</span><span class="o">.</span><span class="na">plus</span><span class="o">(</span><span class="nc">Duration</span><span class="o">.</span><span class="na">ofDays</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="java" /><summary type="html"><![CDATA[这个系列以练习为主，可能不会有多少讲述（当然本篇例外），可以作为初学者的自学验收之用。 Java 中有非受限的空值，并且不知哪时会引发 NPE（即 NullPointerException），解决这个问题对于 Android 开发来说很简单——用 Kotlin 就好了。 其实不仅限于 Android，对于服务端开发来说终极方案也应该是迁移到 Kotlin。 因为只要用 Java，空值问题就没办法彻底解决（之前在《现代编程语言系列2：安全表达可选值》中也提到过这点），而 JVM 平台主流工业级语言中只有 Kotlin 很好地解决了这一问题。 但是对于服务端开发来说，常有各种非技术原因不能在项目中以 Kotlin 取代 Java，对于这些项目来说显然没办法彻底解决空值问题。 那么有没有一些方法与工具可以让空值问题处理起来尽可能规范、简易些呢？这里有几点经验分享。]]></summary></entry><entry><title type="html">追随 Kotlin/Scala，看 Java 12-15 的现代语言特性</title><link href="https://hltj.me/java/2020/06/14/java-12-15-lang-features.html" rel="alternate" type="text/html" title="追随 Kotlin/Scala，看 Java 12-15 的现代语言特性" /><published>2020-06-14T14:40:03+00:00</published><updated>2020-06-14T14:40:03+00:00</updated><id>https://hltj.me/java/2020/06/14/java-12-15-lang-features</id><content type="html" xml:base="https://hltj.me/java/2020/06/14/java-12-15-lang-features.html"><![CDATA[<p>Java 14 发布已经过去了三个月，Java 15 目前也已经到了“Rampdown Phase One ”阶段，其新特性均已敲定。
由于 12-15 都是短期版本，无需考虑也不应该将其用于生产环境。但可以提前了解新特性，以免在下一个 LTS（Java17）正式发布时毫无心理准备。
Java 12-15 引入了一系列改进，本文只讨论语言层面的新特性，它们看起来似曾相识——没错，这些特性让人感觉 Java 在沿 Kotlin/Scala 走过的路线前行。
<!--more--></p>

<p>虽然不能说 Java 就是借鉴的它们（毕竟这些特性既非它们独有也非它们首创），但可以说是 Java 官方对 Kotlin/Scala 这些特性的充分肯定。而这些新特性也让 Java 向现代语言的方向又迈进了一些，我们逐个来看。</p>

<h2 id="switch-表达式">switch 表达式</h2>

<blockquote>
  <p><a href="https://openjdk.java.net/jeps/325">Java 12 预览</a>、<a href="https://openjdk.java.net/jeps/354">Java 13 二次预览</a>、<a href="https://openjdk.java.net/jeps/361">Java 14 正式</a>。</p>
</blockquote>

<p>相当于只支持值匹配的 Kotlin <a href="https://www.kotlincn.net/docs/reference/control-flow.html#when-%E8%A1%A8%E8%BE%BE%E5%BC%8F"><code class="language-plaintext highlighter-rouge">when</code> 表达式</a>/Scala <a href="https://docs.scala-lang.org/overviews/scala-book/match-expressions.html"><code class="language-plaintext highlighter-rouge">match</code> 表达式</a>。</p>

<p>我们看一个不严谨的示例：判断一个非空对象对应哪种  JSON 类型，使用传统的 <code class="language-plaintext highlighter-rouge">switch</code> 语句实现如下：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">switch</span> <span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getSimpleName</span><span class="o">())</span> <span class="o">{</span>
    <span class="k">case</span> <span class="s">"Integer"</span><span class="o">:</span>
    <span class="k">case</span> <span class="s">"Long"</span><span class="o">:</span>
    <span class="k">case</span> <span class="s">"Float"</span><span class="o">:</span>
    <span class="k">case</span> <span class="s">"Double"</span><span class="o">:</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"number"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"List"</span><span class="o">:</span>
    <span class="k">case</span> <span class="s">"Set"</span><span class="o">:</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"array"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"String"</span><span class="o">:</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"string"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"Boolean"</span><span class="o">:</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"boolean"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"Map"</span><span class="o">:</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"object"</span><span class="o">);</span>
        <span class="k">break</span><span class="o">;</span>
    <span class="k">default</span><span class="o">:</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"object/unknown"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>而使用 <code class="language-plaintext highlighter-rouge">switch</code> 表达式可以简化为：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">switch</span> <span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getSimpleName</span><span class="o">())</span> <span class="o">{</span>
    <span class="k">case</span> <span class="s">"Integer"</span><span class="o">,</span> <span class="s">"Long"</span><span class="o">,</span> <span class="s">"Float"</span><span class="o">,</span> <span class="s">"Double"</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"number"</span><span class="o">);</span>
    <span class="k">case</span> <span class="s">"List"</span><span class="o">,</span> <span class="s">"Set"</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"array"</span><span class="o">);</span>
    <span class="k">case</span> <span class="s">"String"</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"string"</span><span class="o">);</span>
    <span class="k">case</span> <span class="s">"Boolean"</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"boolean"</span><span class="o">);</span>
    <span class="k">case</span> <span class="s">"Map"</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"object"</span><span class="o">);</span>
    <span class="k">default</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"object/unknown"</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>是不是简洁了很多？当然如果只能这样用，那还称不上 <code class="language-plaintext highlighter-rouge">switch</code> 表达式，既然是表达式那么一定可以有一个返回值。
我们可以利用 <code class="language-plaintext highlighter-rouge">switch</code> 表达式的返回值来进一步重构上述代码：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">jsonType</span> <span class="o">=</span> <span class="k">switch</span> <span class="o">(</span><span class="n">obj</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getSimpleName</span><span class="o">())</span> <span class="o">{</span>
    <span class="k">case</span> <span class="s">"Integer"</span><span class="o">,</span> <span class="s">"Long"</span><span class="o">,</span> <span class="s">"Float"</span><span class="o">,</span> <span class="s">"Double"</span> <span class="o">-&gt;</span> <span class="s">"number"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"List"</span><span class="o">,</span> <span class="s">"Set"</span> <span class="o">-&gt;</span> <span class="s">"array"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"String"</span> <span class="o">-&gt;</span> <span class="s">"string"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"Boolean"</span> <span class="o">-&gt;</span> <span class="s">"boolean"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"Map"</span> <span class="o">-&gt;</span> <span class="s">"object"</span><span class="o">;</span>
    <span class="k">default</span> <span class="o">-&gt;</span> <span class="s">"object/unknown"</span><span class="o">;</span>
<span class="o">};</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">jsonType</span><span class="o">);</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">switch</code> 表达式中箭头的右侧不仅可以是常规表达式，还可以是一个代码块，在块中通过 <code class="language-plaintext highlighter-rouge">yield</code> 来指定返回值。
例如在上述 <code class="language-plaintext highlighter-rouge">default</code> 分支打一条错误信息，可以这样改：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">javaType</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">jsonType</span> <span class="o">=</span> <span class="k">switch</span> <span class="o">(</span><span class="n">javaType</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">case</span> <span class="s">"Integer"</span><span class="o">,</span> <span class="s">"Long"</span><span class="o">,</span> <span class="s">"Float"</span><span class="o">,</span> <span class="s">"Double"</span> <span class="o">-&gt;</span> <span class="s">"number"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"List"</span><span class="o">,</span> <span class="s">"Set"</span> <span class="o">-&gt;</span> <span class="s">"array"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"String"</span> <span class="o">-&gt;</span> <span class="s">"string"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"Boolean"</span> <span class="o">-&gt;</span> <span class="s">"boolean"</span><span class="o">;</span>
    <span class="k">case</span> <span class="s">"Map"</span> <span class="o">-&gt;</span> <span class="s">"object"</span><span class="o">;</span>
    <span class="k">default</span> <span class="o">-&gt;</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">err</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"unknown type: "</span> <span class="o">+</span> <span class="n">javaType</span><span class="o">);</span>
        <span class="n">yield</span> <span class="s">"object"</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">};</span>
</code></pre></div></div>

<p>是不是有点类似 Kotlin 的 <code class="language-plaintext highlighter-rouge">when</code> 与 Scala 的 <code class="language-plaintext highlighter-rouge">match</code> 了？非常像，只是目前只支持简单的值匹配，还不支持 Kotlin <code class="language-plaintext highlighter-rouge">when</code> 的 <code class="language-plaintext highlighter-rouge">is</code>/<code class="language-plaintext highlighter-rouge">in</code> 以及 Scala <code class="language-plaintext highlighter-rouge">match</code> 的模式匹配。
当然如果综合 Java 12-15 引入的所有语言特性来看，不难预见 <code class="language-plaintext highlighter-rouge">switch</code> 表达式未来会支持模式匹配的。</p>

<p><code class="language-plaintext highlighter-rouge">switch</code> 表达式的优点不仅是简洁且具有返回值，还避免了传统 <code class="language-plaintext highlighter-rouge">switch</code> 语句的一些坑（如忘记写 <code class="language-plaintext highlighter-rouge">break</code> 语句，再如各 <code class="language-plaintext highlighter-rouge">case</code>/<code class="language-plaintext highlighter-rouge">default</code> 子句共享同一个局部作用域）。
因此，在 Java 14 及以上版本中，应该尽量采用新语法、避免使用传统的 <code class="language-plaintext highlighter-rouge">switch</code> 语句。IDEA 甚至会对传统 <code class="language-plaintext highlighter-rouge">switch</code> 语句标记警告，并且提供了自动将传统语法重构为新语法的 quick fix 功能。</p>

<h2 id="文本块">文本块</h2>

<blockquote>
  <p><a href="https://openjdk.java.net/jeps/355">Java 13 预览</a>、<a href="https://openjdk.java.net/jeps/368">Java 14 二次预览</a>、<a href="https://openjdk.java.net/jeps/378">Java 15 正式</a>。</p>
</blockquote>

<p>Java 的文本块（多行字符串）语法与 Kotlin <a href="https://www.kotlincn.net/docs/reference/basic-types.html#%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AD%97%E9%9D%A2%E5%80%BC">原始字符串</a>/Scala 多行字符串类似，都是采用三重双引号括起，不过具体语法、语义不尽相同。
Java 文本块起始的三重双引号后只能跟空白符和换行，因此不能像 Kotlin/Scala 那样写 <code class="language-plaintext highlighter-rouge">"""hello"""</code>，而必须这样写：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">"""
hello"""</span>
</code></pre></div></div>

<p>Java 会自动去掉第一个换行以及每行末尾的空白，因此上述字符串等同于 <code class="language-plaintext highlighter-rouge">"hello"</code>，但是结尾处三重引号前的换行并不会去掉，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 等同于 "hello"</span>
<span class="kt">var</span> <span class="n">s1</span> <span class="o">=</span> <span class="s">"""
        hello"""</span><span class="o">;</span>

<span class="c1">// 等同于 "hello"</span>
<span class="kt">var</span> <span class="n">s2</span> <span class="o">=</span> <span class="s">"""
        hello  """</span><span class="o">;</span>

<span class="c1">// 等同于 "hello\n"</span>
<span class="kt">var</span> <span class="n">s3</span> <span class="o">=</span> <span class="s">"""
        hello
        """</span><span class="o">;</span>
</code></pre></div></div>

<p>文本块中的少于连续三个的双引号都无需转义，这也是文本块的用途之一，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"""                 
        This is a string literal in Java: "</span><span class="nc">Hello</span><span class="s">"."""</span><span class="o">);</span>
</code></pre></div></div>

<p>这段代码会输出 <code class="language-plaintext highlighter-rouge">This is a string literal in Java: "Hello".</code>。
编译过程中会自动去掉缩进用的空白符，如果存在多行，会以前导空白最少的一行作为基准。</p>

<p>文本块的另一个用途是便于书写预排版的文本，例如  ASCII Art 或者竖排文字：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">ci</span> <span class="o">=</span> <span class="s">"┆蝶┆觀┆月┆池┆遊┆　┆獨┆錢┆古┆來┆端┆\n"</span> <span class="o">+</span>
        <span class="s">"┆自┆音┆老┆畔┆人┆　┆賞┆江┆塔┆客┆陽┆\n"</span> <span class="o">+</span>
        <span class="s">"┆舞┆堂┆祠┆問┆醉┆　┆亦┆西┆聽┆尚┆至┆\n"</span> <span class="o">+</span>
        <span class="s">"┆翩┆外┆前┆奇┆　┆　┆悠┆子┆濤┆流┆　┆\n"</span> <span class="o">+</span>
        <span class="s">"┆躚┆雨┆人┆緣┆　┆　┆然┆匯┆於┆連┆　┆\n"</span> <span class="o">+</span>
        <span class="s">"┆　┆連┆絡┆　┆　┆　┆　┆吳┆越┆　┆　┆\n"</span> <span class="o">+</span>
        <span class="s">"┆　┆綿┆繹┆　┆　┆　┆　┆山┆地┆　┆　┆\n"</span><span class="o">;</span>
</code></pre></div></div>

<p>这段竖排文本是我几年前写的<a href="https://hltj.me/poetry/2012/04/05/yijiangnan-hangzhou2.html">《雙調憶江南·庚寅年端午遊杭州》</a>，可以看到自动格式化后第一行没有与后续几行对齐，虽然还有变通办法，但是这本身就已经比较复杂了。
而如果采用文本块就会简单很多：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">String</span> <span class="n">ci</span> <span class="o">=</span> <span class="s">"""
        ┆蝶┆觀┆月┆池┆遊┆　┆獨┆錢┆古┆來┆端┆
        ┆自┆音┆老┆畔┆人┆　┆賞┆江┆塔┆客┆陽┆
        ┆舞┆堂┆祠┆問┆醉┆　┆亦┆西┆聽┆尚┆至┆
        ┆翩┆外┆前┆奇┆　┆　┆悠┆子┆濤┆流┆　┆
        ┆躚┆雨┆人┆緣┆　┆　┆然┆匯┆於┆連┆　┆
        ┆　┆連┆絡┆　┆　┆　┆　┆吳┆越┆　┆　┆
        ┆　┆綿┆繹┆　┆　┆　┆　┆山┆地┆　┆　┆
        """</span><span class="o">;</span>
</code></pre></div></div>

<p>而既有双引号又有预排版的多行文本就更适合使用文本块了，例如 XML 、JSON 或其他配置/代码文本的字面值：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">languages</span> <span class="o">=</span> <span class="s">"[\n"</span> <span class="o">+</span>
        <span class="s">"  {\n"</span> <span class="o">+</span>
        <span class="s">"    \"name\": \"kotlin\",\n"</span> <span class="o">+</span>
        <span class="s">"    \"type\": \"static\"\n"</span> <span class="o">+</span>
        <span class="s">"  },\n"</span> <span class="o">+</span>
        <span class="s">"  {\n"</span> <span class="o">+</span>
        <span class="s">"    \"name\": \"julia\",\n"</span> <span class="o">+</span>
        <span class="s">"    \"type\": \"dynamic\"\n"</span> <span class="o">+</span>
        <span class="s">"  }\n"</span> <span class="o">+</span>
        <span class="s">"]"</span><span class="o">;</span>
</code></pre></div></div>

<p>上述 JSON 字面值看起来很凌乱，而用文本块就会清晰很多：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">languages</span> <span class="o">=</span> <span class="s">"""
        [
          {
            "</span><span class="n">name</span><span class="s">": "</span><span class="n">kotlin</span><span class="s">",
            "</span><span class="n">type</span><span class="s">": "</span><span class="kd">static</span><span class="s">"
          },
          {
            "</span><span class="n">name</span><span class="s">": "</span><span class="n">julia</span><span class="s">",
            "</span><span class="n">type</span><span class="s">": "</span><span class="n">dynamic</span><span class="s">"
          }
        ]"""</span><span class="o">;</span>
</code></pre></div></div>

<h2 id="instanceof-模式匹配"><code class="language-plaintext highlighter-rouge">instanceof</code> 模式匹配</h2>

<blockquote>
  <p><a href="https://openjdk.java.net/jeps/305">Java 14 预览</a>、<a href="https://openjdk.java.net/jeps/375">Java 15 二次预览</a>，预计 Java 16 正式。</p>
</blockquote>

<p>类似于 Kotlin 的<a href="https://www.kotlincn.net/docs/reference/typecasts.html#%E6%99%BA%E8%83%BD%E8%BD%AC%E6%8D%A2">智能转换</a>，但语法不同，在 Scala 中没有直接对应。</p>

<p>传统的 <code class="language-plaintext highlighter-rouge">instanceof</code> 判断成功之后仍然需要强制转换才能按相应类型使用，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(((</span><span class="nc">String</span><span class="o">)</span> <span class="n">obj</span><span class="o">).</span><span class="na">length</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>

<p>而使用模式匹配之后，可以在判断成功时绑定为一个对应类型的变量，之后直接使用该变量即可：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">length</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>

<p>需要注意的是，只有成功匹配的分支才能绑定该变量：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">s</span><span class="o">.</span><span class="na">length</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
    <span class="c1">// 错误：此处 s 不可用</span>
    <span class="c1">// System.out.println(s.length());</span>
<span class="o">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">instanceof</code> 模式匹配不仅能用于 <code class="language-plaintext highlighter-rouge">if</code> 语句中，还可以用于 <code class="language-plaintext highlighter-rouge">while</code> 语句、三目运算符以及 <code class="language-plaintext highlighter-rouge">&amp;&amp;</code>、<code class="language-plaintext highlighter-rouge">||</code> 等能使用布尔逻辑的地方，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span> <span class="n">s</span> <span class="o">?</span> <span class="n">s</span><span class="o">.</span><span class="na">length</span><span class="o">()</span> <span class="o">:</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="kt">var</span> <span class="n">isEmptyString</span> <span class="o">=</span> <span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span> <span class="n">s</span> <span class="o">&amp;&amp;</span> <span class="n">s</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">();</span>
<span class="kt">var</span> <span class="n">isNotEmptyString</span> <span class="o">=</span> <span class="o">!(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">String</span> <span class="n">s</span><span class="o">)</span> <span class="o">||</span> <span class="o">!</span><span class="n">s</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">();</span>
</code></pre></div></div>

<p>目前 Java 中只引入了这一种非常简单的模式匹配形式，未来应该会引入更多模式匹配语法。</p>

<h2 id="记录类型">记录类型</h2>

<blockquote>
  <p><a href="https://openjdk.java.net/jeps/359">Java 14 预览</a>、<a href="https://openjdk.java.net/jeps/384">Java 15 二次预览</a>，预计 Java 16 正式。</p>
</blockquote>

<p>记录类型（record）类似于 Kotlin 的<a href="https://www.kotlincn.net/docs/reference/data-classes.html">数据类（data class）</a>与 Scala 的<a href="https://docs.scala-lang.org/tour/case-classes.html">样例类（case class）</a>，只是更加严格。</p>

<p>在没有记录类型之前，创建一个具有各字段对应 getter、为所有字段初始化的构造函数、基于所有字段的 <code class="language-plaintext highlighter-rouge">equals()</code>/<code class="language-plaintext highlighter-rouge">hashCode()</code>/<code class="language-plaintext highlighter-rouge">toString()</code> 的简单类却需要写一大堆代码，其中大部分都是样板代码。
例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Font</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">Font</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">size</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">size</span> <span class="o">=</span> <span class="n">size</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">name</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">name</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">int</span> <span class="nf">size</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">size</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">o</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(!(</span><span class="n">o</span> <span class="k">instanceof</span> <span class="nc">Font</span> <span class="n">other</span><span class="o">))</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
        <span class="k">return</span> <span class="n">other</span><span class="o">.</span><span class="na">name</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">name</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">other</span><span class="o">.</span><span class="na">size</span> <span class="o">==</span> <span class="n">size</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">Objects</span><span class="o">.</span><span class="na">hash</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">size</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"Font[name=%s, size=%d]"</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">size</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>上述代码中，除了类名、字段类型与字段名之外，其他的全部都是样板代码。而使用记录只需非常简单的一行代码即可：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">record</span> <span class="nf">Font</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">size</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
</code></pre></div></div>

<p>跟一般类相比，记录有以下限制：</p>

<ul>
  <li>总是隐式继承自 <code class="language-plaintext highlighter-rouge">java.lang.Record</code> 而无法显式继承任何任何类</li>
  <li>记录隐含了 final 并且不能声明为抽象</li>
  <li>不能显式声明字段，也不能定义初始化块</li>
  <li>隐式声明的所有字段均为 final</li>
  <li>如果显式声明任何会隐式生成的成员，其类型必须严格匹配</li>
  <li>不能声明 native method（通常译为“本地方法”，按说应该叫“原生方法”）</li>
</ul>

<p>除了这些限制之外，它与普通类一致：</p>

<ul>
  <li>用 <code class="language-plaintext highlighter-rouge">new</code> 实例化</li>
  <li>可以在顶层声明，也可以在类内部、局部作用域中声明</li>
  <li>可以声明静态方法与实例方法</li>
  <li>可以声明静态字段与静态初始化块</li>
  <li>可以实现接口</li>
  <li>可以有其内部类型</li>
  <li>可以标注注解</li>
</ul>

<p>记录类型还可以与接下来提到的密封类/密封接口很好协作，另外记录还适用于未来版本的模式匹配。</p>

<h2 id="密封类与密封接口">密封类与密封接口</h2>

<blockquote>
  <p><a href="https://openjdk.java.net/jeps/360">Java 15 预览</a>，预计 Java 16 二次预览、Java 17 正式。</p>
</blockquote>

<p>Java 15 引入的密封类（sealed class）类似于 Kotlin/Scala 的<a href="https://www.kotlincn.net/docs/reference/sealed-classes.html">密封类</a>、密封接口类似于 Scala 的密封特质（sealed trait）。
不妨将二者统称为密封类型，与普通类/接口不同的是，密封类型限定了哪些类/接口作为其直接子类型。例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sealed</span> <span class="kd">interface</span> <span class="nc">JvmLanguage</span>
    <span class="n">permits</span> <span class="nc">DynamicTypedJvmLanguage</span><span class="o">,</span> <span class="nc">StaticTypedJvmLanguage</span> <span class="o">{}</span>

<span class="n">sealed</span> <span class="kd">interface</span> <span class="nc">StaticTypedJvmLanguage</span> <span class="kd">extends</span> <span class="nc">JvmLanguage</span> <span class="o">{}</span>
<span class="n">sealed</span> <span class="kd">class</span> <span class="nc">DynamicTypedJvmLanguage</span> <span class="kd">implements</span> <span class="nc">JvmLanguage</span>
    <span class="n">permits</span> <span class="nc">Clojure</span><span class="o">,</span> <span class="nc">JvmScriptLanguage</span> <span class="o">{}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Java</span> <span class="kd">implements</span> <span class="nc">StaticTypedJvmLanguage</span> <span class="o">{}</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Scala</span> <span class="kd">implements</span> <span class="nc">StaticTypedJvmLanguage</span> <span class="o">{}</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Kotlin</span> <span class="kd">implements</span> <span class="nc">StaticTypedJvmLanguage</span> <span class="o">{}</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="nc">Clojure</span> <span class="kd">extends</span> <span class="nc">DynamicTypedJvmLanguage</span> <span class="o">{}</span>
<span class="n">non</span><span class="o">-</span><span class="n">sealed</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">JvmScriptLanguage</span> <span class="kd">extends</span> <span class="nc">DynamicTypedJvmLanguage</span> <span class="o">{}</span>

<span class="kd">class</span> <span class="nc">Groovy</span> <span class="kd">extends</span> <span class="nc">JvmScriptLanguage</span> <span class="o">{}</span>
</code></pre></div></div>

<p>在密封类型的声明中可以通过 <code class="language-plaintext highlighter-rouge">permits</code> 显式声明其直接子类型列表，也可以省略——编译器会根据当前文件中的直接子类型的声明推断出来。
一个密封类型的直接子类型必须标注 <code class="language-plaintext highlighter-rouge">sealed</code>、<code class="language-plaintext highlighter-rouge">non-sealed</code>、<code class="language-plaintext highlighter-rouge">final</code> 三者之一。</p>

<p>密封类型与记录是相互独立的功能，但是二者能够很好协作，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sealed</span> <span class="kd">interface</span> <span class="nc">Json</span> <span class="o">{}</span>
<span class="n">record</span> <span class="nf">NullVal</span><span class="o">()</span> <span class="kd">implements</span> <span class="nc">Json</span> <span class="o">{}</span>
<span class="n">record</span> <span class="nf">BooleanVal</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">val</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Json</span> <span class="o">{}</span>
<span class="n">record</span> <span class="nf">NumberVal</span><span class="o">(</span><span class="kt">double</span> <span class="n">val</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Json</span> <span class="o">{}</span>
<span class="n">record</span> <span class="nf">StringVal</span><span class="o">(</span><span class="nc">String</span> <span class="n">val</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Json</span> <span class="o">{}</span>
<span class="n">record</span> <span class="nf">ArrayVal</span><span class="o">(</span><span class="nc">Object</span><span class="o">...</span><span class="na">vals</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Json</span> <span class="o">{}</span>
<span class="n">record</span> <span class="nf">ObjectVal</span><span class="o">(</span><span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">kvs</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">Json</span> <span class="o">{}</span>
</code></pre></div></div>

<p>此外，还可以用记录与密封类型来实现<a href="https://en.wikipedia.org/wiki/Algebraic_data_type">代数数据类型（ADT）</a>：记录为积类型、密封类型为和类型。
与记录类似，密封类型也将适用于未来版本的模式匹配。</p>

<h2 id="小结">小结</h2>

<p>Java 12-15 引入了 <code class="language-plaintext highlighter-rouge">switch</code> 表达式、文本块、<code class="language-plaintext highlighter-rouge">instanceof</code> 模式匹配、记录、密封类型这几个语言新特性，这些特性在 Kotlin/Scala 中基本上都有对应，如同 Java 在追随 Kotlin/Scala 的步伐。</p>

<p>另外，不知大家有没有注意到这一点：除了文本块外，其他几个特性都直接或间接指向了同一个关键词——<strong>模式匹配</strong>。
这些特性除了自身价值之外，也都在为未来版本的模式匹配做铺垫。因此不妨做个大胆预测：在未来的几个版本中，Java 会引入更完善的模式匹配机制。</p>

<h2 id="些许遗憾">些许遗憾</h2>

<p>Java 12-15 中引入语言层面的新特性并不很多，很多令人期待新特性都没有包含在内。
当然语言需要渐进式演化，这也是情理之中的事。唯有两点我觉得有些遗憾：</p>

<h3 id="空安全">空安全</h3>

<p><a href="https://hltj.me/lang/2019/07/08/modern-lang-optional-value.html">安全表达可选值是现代语言的一个特征</a>，隔壁 C# 8 引入空安全的经验告诉我们：
<strong>即便语言当初做了错误的设计，倘若迷途知返，仍然能够回到正轨</strong>。
Java会不会也有这么一天？也许会，不过 Java 12-15 显然没有，在接下来的几个版本中这么做可能性也很渺茫，也许还会在“迷途”中继续前行很久。</p>

<p>当然关于空安全这块也不是什么长进都没有，Java 14 就有一处：<a href="https://openjdk.java.net/jeps/358">JEP 358: Helpful NullPointerExceptions</a> 改善了 NullPointerException 所携带的信息，以便定位是哪个变量出现空值导致的。</p>

<h3 id="协程">协程</h3>

<p>协程即将成为现代工业级语言的标配，不仅 Python、JavaScript、Rust 等语言纷纷引入了协程支持（<code class="language-plaintext highlighter-rouge">async</code>-<code class="language-plaintext highlighter-rouge">await</code> 尤为便利），就连隔壁 C++ 也都已将协程纳入了今年的新标准（C++ 20）中。
再对比灵活、强大的 <a href="https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html">Kotlin 协程</a>在异步程序设计中所带来的便利，还在坚守 Java 的开发者对协程的期待就不难理解了。
遗憾的是，Project Loom 未能合入 Java 15，那么按照其他特性的演进周期来看，在下一个 LTS（即 Java 17）发布时就算包含进来也不会是稳定特性。</p>

<p>当然，目前 JVM 平台的 5 门主流工业级语言（Java、Kotlin、Scala、Groovy、Clojure）中，只有 Kotlin 具备上述两个特性，欢迎来 <a href="https://www.kotlincn.net/docs/reference/">Kotlin 中文站</a>学习。</p>]]></content><author><name></name></author><category term="java" /><summary type="html"><![CDATA[Java 14 发布已经过去了三个月，Java 15 目前也已经到了“Rampdown Phase One ”阶段，其新特性均已敲定。 由于 12-15 都是短期版本，无需考虑也不应该将其用于生产环境。但可以提前了解新特性，以免在下一个 LTS（Java17）正式发布时毫无心理准备。 Java 12-15 引入了一系列改进，本文只讨论语言层面的新特性，它们看起来似曾相识——没错，这些特性让人感觉 Java 在沿 Kotlin/Scala 走过的路线前行。]]></summary></entry><entry><title type="html">在 Kotlin 中“实现”trait/类型类</title><link href="https://hltj.me/kotlin/2020/01/11/kotlin-trait-typeclass.html" rel="alternate" type="text/html" title="在 Kotlin 中“实现”trait/类型类" /><published>2020-01-11T11:31:21+00:00</published><updated>2020-01-11T11:31:21+00:00</updated><id>https://hltj.me/kotlin/2020/01/11/kotlin-trait-typeclass</id><content type="html" xml:base="https://hltj.me/kotlin/2020/01/11/kotlin-trait-typeclass.html"><![CDATA[<h2 id="trait-与类型类都是什么">trait 与类型类都是什么</h2>
<p><strong>trait</strong> 与<strong>类型类（type class）</strong>分别是 Rust 与 Haskell 语言中的概念，用于<a href="https://zh.wikipedia.org/wiki/%E7%89%B9%E8%AE%BE%E5%A4%9A%E6%80%81">特设多态（ad-hoc polymorphism）</a>、<a href="https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B">函数式编程</a>等方面。</p>

<!--more-->

<p>值得一提的是虽然英文都是“trait”， Scala 的特质跟 Rust 的 trait <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> 却并不相同。
Scala 的特质相当于 Kotlin 与 Java 8+ 的接口，能实现<strong>子类型多态</strong>；而 Rust 的 trait 更类似于 Swift 的协议与 Haskell 的类型类，能实现<strong>特设多态</strong>。简单来说，<strong>trait</strong> 应同时具备以下三项能力<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>：</p>
<ol>
  <li>定义“接口”并可提供默认实现</li>
  <li>用作泛型约束</li>
  <li>给既有类型增加功能</li>
</ol>

<p>Haskell 的<strong>类型类</strong>不仅同时具备这三项能力，还能定义函数式编程中非常重要的 <a href="https://hltj.me/kotlin/2017/08/25/kotlin-functor-applicative-monad-cn.html">Functor、Applicative、Monad 等</a>。
当然这是废话，因为它们在 Haskell 中本来就是类型类😂。
实际上这也不是 trait 与类型类的差异，能否支持 Functor 等的关键在于语言的泛型参数能否支持<a href="https://en.wikipedia.org/wiki/Kind_(type_theory)">类型构造器</a>（或者说语言能否支持高阶类型）。</p>

<h2 id="在-kotlin-中寻求对应">在 Kotlin 中寻求对应</h2>
<p>在 Kotlin 中并没有同时具备这三项能力的对应，只有分别提供三项能力的特性。
其中 Kotlin 的<a href="https://www.kotlincn.net/docs/reference/interfaces.html">接口</a>同时具备前两项能力。</p>

<h3 id="定义接口并可提供默认实现">定义“接口”并可提供默认实现</h3>
<p>例如，定义一个带有默认实现的接口：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">WithDescription</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">description</span><span class="p">:</span> <span class="nc">String</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="s">"The description of $this"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Kotlin 的接口中可以定义属性与方法，二者都可以有默认实现，简便起见，示例中用了具有默认实现的属性。它可以这么用：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Foo</span><span class="p">:</span> <span class="nc">WithDescription</span> <span class="p">{</span>
    <span class="c1">// Foo 类为 description 属性提供了自己的实现</span>
    <span class="k">override</span> <span class="kd">val</span> <span class="py">description</span> <span class="p">=</span> <span class="s">"This is a Foo object"</span>
<span class="p">}</span>

<span class="c1">// 对象 Bar 的 description 属性采用默认实现</span>
<span class="kd">object</span> <span class="nc">Bar</span><span class="p">:</span> <span class="nc">WithDescription</span>

<span class="nf">println</span><span class="p">(</span><span class="nc">Foo</span><span class="p">().</span><span class="n">description</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="nc">Bar</span><span class="p">.</span><span class="n">description</span><span class="p">)</span>
</code></pre></div></div>

<p>在 <a href="https://hltj.me/kotlin/2017/08/31/2tips-for-kotlin-repl.html">Kotlin REPL</a> 中执行会得到类似这样的输出：</p>

<pre><code class="language-txt">This is a Foo object
The description of Line_7$Bar@5bf4764d
</code></pre>

<h3 id="用作泛型约束">用作泛型约束</h3>
<p>接下来还可以将之前定义的 <code class="language-plaintext highlighter-rouge">WithDescription</code> 接口用在泛型函数、泛型类或者其他泛型接口中作为<a href="https://www.kotlincn.net/docs/reference/generics.html#%E6%B3%9B%E5%9E%8B%E7%BA%A6%E6%9D%9F">泛型约束</a>，例如：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span> <span class="p">:</span> <span class="nc">WithDescription</span><span class="p">&gt;</span> <span class="nc">T</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">()</span> <span class="p">=</span> <span class="nf">println</span><span class="p">(</span><span class="n">description</span><span class="p">)</span>
</code></pre></div></div>

<p>在 REPL 中执行：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;&gt;&gt;</span> <span class="nc">Bar</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">()</span>
<span class="nc">The</span> <span class="n">description</span> <span class="n">of</span> <span class="nc">Line_7</span><span class="err">$</span><span class="nc">Bar</span><span class="err">@</span><span class="mi">5</span><span class="n">bf4764d</span>
</code></pre></div></div>

<p>遗憾的是，在 Kotlin 中不能给既有类型（类或接口）实现新的接口，比如不能为 <code class="language-plaintext highlighter-rouge">Boolean</code> 或者 <code class="language-plaintext highlighter-rouge">Iterable</code> 实现 <code class="language-plaintext highlighter-rouge">WithDescription</code>。
即接口不具备第三项能力，因此它不是 trait/类型类。</p>

<h3 id="给既有类型增加功能">给既有类型增加功能</h3>
<p>在 Kotlin 中给既有类型增加功能的方式是<a href="https://www.kotlincn.net/docs/reference/extensions.html">扩展</a>，可以给任何既有类型声明扩展函数与扩展属性。例如可以分别给 <code class="language-plaintext highlighter-rouge">Int</code> 与 <code class="language-plaintext highlighter-rouge">String</code> 实现二者间的乘法操作符函数：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">operator</span> <span class="k">fun</span> <span class="nc">Int</span><span class="p">.</span><span class="nf">times</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">repeat</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>

<span class="k">operator</span> <span class="k">fun</span> <span class="nc">String</span><span class="p">.</span><span class="nf">times</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span> <span class="nf">repeat</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
</code></pre></div></div>

<p>于是就可以像 Python/Ruby 那样使用了：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;&gt;&gt;</span> <span class="s">"Hello"</span> <span class="p">*</span> <span class="mi">3</span>
<span class="n">res11</span><span class="p">:</span> <span class="n">kotlin</span><span class="p">.</span><span class="nc">String</span> <span class="p">=</span> <span class="nc">HelloHelloHello</span>
<span class="p">&gt;&gt;&gt;</span> <span class="mi">5</span> <span class="p">*</span> <span class="s">"汉字"</span>
<span class="n">res12</span><span class="p">:</span> <span class="n">kotlin</span><span class="p">.</span><span class="nc">String</span> <span class="p">=</span> <span class="err">汉字汉字汉字汉字汉字</span>
</code></pre></div></div>

<h2 id="在-kotlin-中实现trait类型类">在 Kotlin 中“实现”trait/类型类</h2>
<p>如上文所述，Kotlin 分别用接口与扩展两个不同特性提供了 trait/类型类的三项能力，因此在 Kotlin 中没有其直接对应。
那么如果把两个特性以某种方式结合起来，是不是就可以“实现”trait/类型类了？——还别说，真就可以！
<a href="https://arrow-kt.io/docs/typeclasses/intro/">Arrow 中的类型类</a>就是这么实现的。</p>

<p>我们继续以 <code class="language-plaintext highlighter-rouge">WithDescription</code> 为例，不同的是，这回要这么声明：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">T</span><span class="p">.</span><span class="n">description</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="s">"The description of $this"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里利用了<a href="https://www.kotlincn.net/docs/reference/extensions.html#%E6%89%A9%E5%B1%95%E5%A3%B0%E6%98%8E%E4%B8%BA%E6%88%90%E5%91%98">分发接收者可以子类化、扩展接收者静态解析</a>的特性，可以为任何既有类型添加实现。
例如分别为 <code class="language-plaintext highlighter-rouge">Char</code>、<code class="language-plaintext highlighter-rouge">String</code> 实现如下：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">object</span> <span class="nc">CharWithDescription</span> <span class="p">:</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">Char</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="kd">val</span> <span class="py">Char</span><span class="p">.</span><span class="n">description</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="s">"${this.category} $this"</span>
<span class="p">}</span>

<span class="c1">// 采用默认实现</span>
<span class="kd">object</span> <span class="nc">StringWithDescription</span><span class="p">:</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>不过使用时会麻烦一点，需要借助 <code class="language-plaintext highlighter-rouge">run()</code> 或者 <code class="language-plaintext highlighter-rouge">with()</code> 这样的<a href="https://www.kotlincn.net/docs/reference/scope-functions.html">作用域函数</a>在相应上下文中执行：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">println</span><span class="p">(</span><span class="nc">StringWithDescription</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span> <span class="s">"hello"</span><span class="p">.</span><span class="n">description</span> <span class="p">})</span>

<span class="nf">with</span><span class="p">(</span><span class="nc">CharWithDescription</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">println</span><span class="p">(</span><span class="sc">'a'</span><span class="p">.</span><span class="n">description</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在 REPL 中执行的输出如下：</p>

<pre><code class="language-txt">The description of hello
LOWERCASE_LETTER a
</code></pre>

<p>用作泛型约束也不成问题：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">,</span> <span class="nc">Ctx</span> <span class="p">:</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;&gt;</span> <span class="nc">Ctx</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">(</span><span class="n">t</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">=</span> <span class="nf">println</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">description</span><span class="p">)</span>

<span class="nc">StringWithDescription</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
    <span class="nc">CharWithDescription</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
        <span class="nf">printDescription</span><span class="p">(</span><span class="s">"Kotlin"</span><span class="p">)</span>
        <span class="nf">printDescription</span><span class="p">(</span><span class="sc">'①'</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里实现的 <code class="language-plaintext highlighter-rouge">printDescription()</code> 与上文的函数签名不同，因为接收者类型用于实现基于作用域上下文的泛型约束了，这也是利用接口、扩展、子类型多态以及作用域函数这些特性来“实现”trait/类型类的关键所在。
当然，如果仍然希望目标类型（如例中的 <code class="language-plaintext highlighter-rouge">Char</code>、<code class="language-plaintext highlighter-rouge">String</code>）作为 <code class="language-plaintext highlighter-rouge">printDescription</code> 的接收者，只要将其接收者与参数互换即可：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">,</span> <span class="nc">Ctx</span> <span class="p">:</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;&gt;</span> <span class="nc">T</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="nc">Ctx</span><span class="p">)</span> <span class="p">=</span> <span class="n">ctx</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
    <span class="nf">println</span><span class="p">(</span><span class="n">description</span><span class="p">)</span>
<span class="p">}</span>

<span class="s">"hltj.me"</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">(</span><span class="nc">StringWithDescription</span><span class="p">)</span>
</code></pre></div></div>

<p>上述两种方式中提供泛型约束的上下文要么占用了函数的扩展接收者、要么占用了函数参数。实际上还有一种方式——占用分发接收者，显然只要在 <code class="language-plaintext highlighter-rouge">WithDescription</code> 内声明 <code class="language-plaintext highlighter-rouge">printDescription()</code> 就可以了。
不过我们这里要假设 <code class="language-plaintext highlighter-rouge">printDescription()</code> 是自己定义的函数，而 <code class="language-plaintext highlighter-rouge">WithDescription</code> 是无法修改的既有类型，那么还能做到吗？——当然不成问题！只要用一个新接口继承  <code class="language-plaintext highlighter-rouge">WithDescription</code> 就可以了：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">WithDescriptionAndItsPrinter</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;:</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nc">T</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">()</span> <span class="p">=</span> <span class="nf">println</span><span class="p">(</span><span class="n">description</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">object</span> <span class="nc">StringWithDescriptionAndItsPrinter</span><span class="p">:</span> <span class="nc">WithDescriptionAndItsPrinter</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span>

<span class="kd">object</span> <span class="nc">CharWithDescriptionAndItsPrinter</span><span class="p">:</span>
    <span class="nc">WithDescriptionAndItsPrinter</span><span class="p">&lt;</span><span class="nc">Char</span><span class="p">&gt;,</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">Char</span><span class="p">&gt;</span> <span class="k">by</span> <span class="nc">CharWithDescription</span>

<span class="nc">StringWithDescriptionAndItsPrinter</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
   <span class="nc">CharWithDescriptionAndItsPrinter</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
        <span class="s">"hltj.me"</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">()</span>
        <span class="sc">'★'</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>将三种方式放一起对比会更直观：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 方式 1</span>
<span class="nc">StringWithDescription</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
    <span class="nf">printDescription</span><span class="p">(</span><span class="s">"hltj.me"</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// 方式 2</span>
<span class="s">"hltj.me"</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">(</span><span class="nc">StringWithDescription</span><span class="p">)</span>

<span class="c1">// 方式 3</span>
<span class="kd">interface</span> <span class="nc">WithDescriptionAndItsPrinter</span> <span class="p">{</span> <span class="cm">/*……*/</span> <span class="p">}</span>
<span class="kd">object</span> <span class="nc">StringWithDescriptionAndItsPrinter</span><span class="p">:</span> <span class="nc">WithDescriptionAndItsPrinter</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span>
<span class="nc">StringWithDescriptionAndItsPrinter</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
    <span class="s">"hltj.me"</span><span class="p">.</span><span class="nf">printDescription</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>第三种方式的优点是提供泛型约束的上下文既不占用扩展接收者也不占用参数，但其代价是需要为每个用到的目标类型（如例中的 <code class="language-plaintext highlighter-rouge">Char</code>、<code class="language-plaintext highlighter-rouge">String</code>）提供新接口（如例中的 <code class="language-plaintext highlighter-rouge">WithDescriptionAndItsPrinter&lt;T&gt;</code>）的相应实现，并且依然需要借助作用域函数 <code class="language-plaintext highlighter-rouge">run()</code> 或 <code class="language-plaintext highlighter-rouge">with()</code>。
因此通常采用前两种方式即可，但是如果要自定义操作符函数或者中缀函数时就只能采用第三种方式了，例如：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">DescriptionMultiplier</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;:</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">infix</span> <span class="k">fun</span> <span class="nc">T</span><span class="p">.</span><span class="nf">rep</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="n">n</span><span class="p">).</span><span class="nf">joinToString</span> <span class="p">{</span> <span class="n">description</span> <span class="p">}</span>

    <span class="k">operator</span> <span class="k">fun</span> <span class="nc">T</span><span class="p">.</span><span class="nf">times</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span> <span class="k">this</span> <span class="n">rep</span> <span class="n">n</span>
<span class="p">}</span>

<span class="kd">object</span> <span class="nc">CharDescriptionMultiplier</span><span class="p">:</span>
    <span class="nc">DescriptionMultiplier</span><span class="p">&lt;</span><span class="nc">Char</span><span class="p">&gt;,</span> <span class="nc">WithDescription</span><span class="p">&lt;</span><span class="nc">Char</span><span class="p">&gt;</span> <span class="k">by</span> <span class="nc">CharWithDescription</span>

<span class="nf">println</span><span class="p">(</span><span class="kd">object</span> <span class="err">: </span><span class="nc">DescriptionMultiplier</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span> <span class="p">{}.</span><span class="nf">run</span> <span class="p">{</span> <span class="s">"hltj.me"</span> <span class="n">rep</span> <span class="mi">2</span> <span class="p">})</span>

<span class="nf">println</span><span class="p">(</span><span class="nc">CharDescriptionMultiplier</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span> <span class="sc">'A'</span> <span class="p">*</span> <span class="mi">3</span> <span class="p">})</span>
</code></pre></div></div>

<p>在 REPL 中执行的输出为：</p>

<pre><code class="language-txt">The description of hltj.me, The description of hltj.me
UPPERCASE_LETTER A, UPPERCASE_LETTER A, UPPERCASE_LETTER A
</code></pre>

<h3 id="扩展与成员的优先级">扩展与成员的优先级</h3>
<p>我们知道，在 Kotlin 中扩展与成员冲突时<a href="https://www.kotlincn.net/docs/reference/extensions.html#%E6%89%A9%E5%B1%95%E6%98%AF%E9%9D%99%E6%80%81%E8%A7%A3%E6%9E%90%E7%9A%84">总是取成员</a>。
但是在使用基于作用域上下文的泛型约束时却并非如此，例如：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">WithLength</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">T</span><span class="p">.</span><span class="n">length</span><span class="p">:</span> <span class="nc">Int</span>
<span class="p">}</span>

<span class="kd">object</span> <span class="nc">StringWithFakeLength</span><span class="p">:</span> <span class="nc">WithLength</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="kd">val</span> <span class="py">String</span><span class="p">.</span><span class="n">length</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">128</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">,</span> <span class="nc">U</span><span class="p">:</span> <span class="nc">WithLength</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;&gt;</span> <span class="nc">U</span><span class="p">.</span><span class="nf">printLength</span><span class="p">(</span><span class="n">t</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">=</span> <span class="nf">println</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">length</span><span class="p">)</span>

<span class="nc">StringWithFakeLength</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
    <span class="nf">printLength</span><span class="p">(</span><span class="s">"hltj.me"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>在 REPL 中运行输出是 <code class="language-plaintext highlighter-rouge">128</code>，表明 <code class="language-plaintext highlighter-rouge">printLenth()</code> 取到的 <code class="language-plaintext highlighter-rouge">length</code> 是 <code class="language-plaintext highlighter-rouge">StringWithFakeLength</code> 中定义的扩展属性而不是 <code class="language-plaintext highlighter-rouge">String</code> 自身的属性。因此使用时需要特别注意。
唯有 Any 的三个成员 <code class="language-plaintext highlighter-rouge">toString()</code>、<code class="language-plaintext highlighter-rouge">hashCode()</code>、<code class="language-plaintext highlighter-rouge">equals()</code> 会始终调用成员函数，即便在泛型约束上下文中声明了具有相同签名的扩展函数也是一样。</p>

<h3 id="实现functor-等">“实现”Functor 等</h3>
<p>按照上文介绍的方式，我们可以轻松实现 <code class="language-plaintext highlighter-rouge">Show</code>、<code class="language-plaintext highlighter-rouge">Eq</code>、<code class="language-plaintext highlighter-rouge">Ord</code> 等简单类型类，无需赘述。
但是如果要实现 <code class="language-plaintext highlighter-rouge">Functor</code>、<code class="language-plaintext highlighter-rouge">Applicative</code>、<code class="language-plaintext highlighter-rouge">Monad</code> 等却会遇到问题。
以 <code class="language-plaintext highlighter-rouge">Functor</code> 为例，按说要这么定义：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Functor</span><span class="p">&lt;</span><span class="nc">C</span><span class="p">&lt;</span><span class="err">*</span><span class="p">&gt;&gt;</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">,</span> <span class="nc">R</span><span class="p">&gt;</span> <span class="nf">C</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.</span><span class="nf">fmap</span><span class="p">(</span><span class="n">f</span><span class="p">:</span> <span class="p">(</span><span class="nc">T</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">R</span><span class="p">):</span> <span class="nc">C</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>但遗憾的是上述代码无法通过编译，因为 Kotlin 目前不支持<a href="https://en.wikipedia.org/wiki/Kind_(type_theory)">高阶类型</a>，在泛型参数中用 <code class="language-plaintext highlighter-rouge">C&lt;*&gt;</code> 表示类型构造器只是<strong>假想</strong>的语法 。
因此，需要有一种方式来变通。按 <a href="https://arrow-kt.io/docs/patterns/glossary/#higher-kinds">Arrow 的方式</a>引入 <code class="language-plaintext highlighter-rouge">Kind</code> 接口来表示：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Kind</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">F</span><span class="p">,</span> <span class="k">out</span> <span class="nc">A</span><span class="p">&gt;</span>

<span class="kd">interface</span> <span class="nc">Functor</span><span class="p">&lt;</span><span class="nc">F</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">,</span> <span class="nc">R</span><span class="p">&gt;</span> <span class="nf">Kind</span><span class="p">&lt;</span><span class="nc">F</span><span class="p">,</span> <span class="nc">T</span><span class="p">&gt;.</span><span class="nf">fmap</span><span class="p">(</span><span class="n">f</span><span class="p">:</span> <span class="p">(</span><span class="nc">T</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">R</span><span class="p">):</span> <span class="nc">Kind</span><span class="p">&lt;</span><span class="nc">F</span><span class="p">,</span> <span class="nc">R</span><span class="p">&gt;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>然后写一个标记类，让具体类型作为 <code class="language-plaintext highlighter-rouge">Kind&lt;标记类, T&gt;</code> 的实现类。再定义一个由 <code class="language-plaintext highlighter-rouge">Kind&lt;标记类, T&gt;</code> 向具体类型转换的扩展函数 <code class="language-plaintext highlighter-rouge">fix()</code>，以便在具体实现中使用。
例如：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ForMaybe</span> <span class="k">private</span> <span class="k">constructor</span><span class="p">()</span>

<span class="k">sealed</span> <span class="kd">class</span> <span class="nc">Maybe</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">Kind</span><span class="p">&lt;</span><span class="nc">ForMaybe</span><span class="p">,</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">object</span> <span class="nc">`Nothing</span><span class="err">#`</span> <span class="p">:</span> <span class="nc">Maybe</span><span class="p">&lt;</span><span class="nc">Nothing</span><span class="p">&gt;()</span> <span class="p">{</span>
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">toString</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"Nothing#"</span>
    <span class="p">}</span>
    <span class="kd">data class</span> <span class="nc">Just</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">T</span><span class="p">&gt;(</span><span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Maybe</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;()</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">Kind</span><span class="p">&lt;</span><span class="nc">ForMaybe</span><span class="p">,</span> <span class="nc">T</span><span class="p">&gt;.</span><span class="nf">fix</span><span class="p">():</span> <span class="nc">Maybe</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">=</span> <span class="k">this</span> <span class="k">as</span> <span class="nc">Maybe</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>这样就可以为 <code class="language-plaintext highlighter-rouge">Maybe</code> 实现 <code class="language-plaintext highlighter-rouge">Functor&lt;ForMaybe&gt;</code> 了：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">object</span> <span class="nc">MaybeFunctor</span> <span class="p">:</span> <span class="nc">Functor</span><span class="p">&lt;</span><span class="nc">ForMaybe</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">,</span> <span class="nc">R</span><span class="p">&gt;</span> <span class="nf">Kind</span><span class="p">&lt;</span><span class="nc">ForMaybe</span><span class="p">,</span> <span class="nc">T</span><span class="p">&gt;.</span><span class="nf">fmap</span><span class="p">(</span><span class="n">f</span><span class="p">:</span> <span class="p">(</span><span class="nc">T</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">R</span><span class="p">):</span> <span class="nc">Maybe</span><span class="p">&lt;</span><span class="nc">R</span><span class="p">&gt;</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="kd">val</span> <span class="py">maybe</span> <span class="p">=</span> <span class="nf">fix</span><span class="p">())</span> <span class="p">{</span>
        <span class="k">is</span> <span class="nc">Maybe</span><span class="p">.</span><span class="nc">Just</span> <span class="p">-&gt;</span> <span class="nc">Maybe</span><span class="p">.</span><span class="nc">Just</span><span class="p">(</span><span class="nf">f</span><span class="p">(</span><span class="n">maybe</span><span class="p">.</span><span class="n">value</span><span class="p">))</span>
        <span class="k">else</span> <span class="p">-&gt;</span> <span class="nc">Maybe</span><span class="p">.</span><span class="nc">`Nothing</span><span class="err">#`</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="nf">with</span><span class="p">(</span><span class="nc">MaybeFunctor</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">println</span><span class="p">(</span><span class="nc">Maybe</span><span class="p">.</span><span class="nc">Just</span><span class="p">(</span><span class="mi">5</span><span class="p">).</span><span class="nf">fmap</span> <span class="p">{</span> <span class="n">it</span> <span class="p">+</span> <span class="mi">1</span> <span class="p">})</span>
    <span class="nf">println</span><span class="p">(</span><span class="nc">Maybe</span><span class="p">.</span><span class="nc">`Nothing</span><span class="err">#`</span><span class="p">.</span><span class="nf">fmap</span> <span class="p">{</span> <span class="n">x</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">-&gt;</span> <span class="n">x</span> <span class="p">+</span> <span class="mi">1</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<p>可以看出这种实现方式会有明显的局限性：只能为 Arrow 中定义的类型或者按照 Arrow 方式实现的既有类型实现 <code class="language-plaintext highlighter-rouge">Functor</code>、<code class="language-plaintext highlighter-rouge">Applicative</code>、<code class="language-plaintext highlighter-rouge">Monad</code> 等接受类型构造器作为泛型参数的“类型类”。
好在 Arrow 已经自带了大量有用的类型，很多场景都够用。</p>

<blockquote>
  <p>需要注意的是这段代码无法在当前版本（1.3.61）的 Kotlin REPL 中运行，需要放在普通的 Kotlin 文件中编译运行。</p>
</blockquote>

<h2 id="arrow">Arrow</h2>
<p><a href="https://arrow-kt.io/">Arrow</a>（按其官网写作 Λrrow）是 Kotlin 标准库的函数式“伴侣”。目前主要以下四套件：</p>

<ul>
  <li><a href="https://arrow-kt.io/docs/core/">Arrow Core</a> 提供了核心的数据类型与类型类。</li>
  <li><a href="https://arrow-kt.io/docs/fx/">Arrow FX</a> 是函数式副作用库，提供了
<a href="https://github.com/MnO2/learnyouahaskell-zh/blob/master/zh-cn/ch12/a-fistful-of-monads.md#do-%E8%A1%A8%E7%A4%BA%E6%B3%95"><code class="language-plaintext highlighter-rouge">do</code>-表示法</a>/<a href="https://arrow-kt.io/docs/patterns/monad_comprehensions/#comprehensions-over-coroutines">Monad
推导</a>风格的 DSL。</li>
  <li><a href="https://arrow-kt.io/docs/optics/dsl/">Arrow Optics</a> 用于在 Kotlin 中处理不可变数据模型。</li>
  <li><a href="https://meta.arrow-kt.io/">Arrow Meta</a> 是 Kotlin 编译器与 IDE 的函数式“伴侣”。</li>
</ul>

<p>此外还有若干套件/特性还在孵化中。
关于 Arrow 整体与模式的介绍也在 Arrow Core 的文档中，其中 <a href="https://arrow-kt.io/docs/patterns/glossary/">Functional Programming Glossary</a> 提供了一些使用 Arrow 进行函数式编程的背景知识可供参考。</p>

<h2 id="一点意外">一点意外</h2>
<p>在尝试写这些示例时意外发现了一个会导致当前版本的 Kotlin JVM 编译器抛异常的 bug，最小重现代码如下：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">WithIntId</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">T</span><span class="p">.</span><span class="n">intId</span> <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="mi">1</span>
<span class="p">}</span>

<span class="kd">object</span> <span class="nc">BooleanWithIntId</span> <span class="p">:</span> <span class="nc">WithIntId</span><span class="p">&lt;</span><span class="nc">Boolean</span><span class="p">&gt;</span>

<span class="kd">val</span> <span class="py">x</span> <span class="p">=</span> <span class="nc">BooleanWithIntId</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span>
    <span class="k">true</span><span class="p">.</span><span class="n">intId</span>
<span class="p">}</span>
</code></pre></div></div>

<p>只影响 Kotlin JVM 编译器，Kotlin JS 与 Kotlin Native 都不存在这个问题。
查了下  YouTrack，看起来是个<a href="https://youtrack.jetbrains.com/issue/KT-29331#focus=streamItem-27-3894798.0-0">已知 bug</a>。
不过文中的其他示例代码都能正常编译运行，尽可放心。</p>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>trait：Scala 中文社区倾向于译为“特质”，Rust 中文社区倾向于不译。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>按说“接口”不支持默认实现也可实现 Rust/Haskell 式的特设多态，但其易用性与表现力都要大打折扣。 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="kotlin" /><summary type="html"><![CDATA[trait 与类型类都是什么 trait 与类型类（type class）分别是 Rust 与 Haskell 语言中的概念，用于特设多态（ad-hoc polymorphism）、函数式编程等方面。]]></summary></entry><entry><title type="html">现代编程语言系列2：安全表达可选值</title><link href="https://hltj.me/lang/2019/07/08/modern-lang-optional-value.html" rel="alternate" type="text/html" title="现代编程语言系列2：安全表达可选值" /><published>2019-07-08T05:15:16+00:00</published><updated>2019-07-08T05:15:16+00:00</updated><id>https://hltj.me/lang/2019/07/08/modern-lang-optional-value</id><content type="html" xml:base="https://hltj.me/lang/2019/07/08/modern-lang-optional-value.html"><![CDATA[<p>这里的可选值（optional value）是指可能无值也可能有一个值的情况，在一些编程语言中称为可空值（nullable value）。</p>

<h2 id="问题与解决方案">问题与解决方案</h2>
<p>传统编程语言中往往使用空值（<code class="language-plaintext highlighter-rouge">null</code> 或者 <code class="language-plaintext highlighter-rouge">None</code>、<code class="language-plaintext highlighter-rouge">nil</code> 等）来表达可选值，可谓简单粗暴。
<!--more-->
因为这样一来，就需要在每一处使用的地方判断相应的值是否为空，一旦疏忽大意就可能导致程序出错甚至崩溃。
不仅如此，正如著名的<a href="https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare">《十亿美元的错误》</a>与<a href="https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/">《计算机科学中的最严重错误》</a>所说，传统空值还引入了一系列其他问题：破坏了类型系统、易与空容器混为一谈、表意模凌两可、难以调试、不便同语言的其他特性结合使用等。</p>

<p>因此，现代编程语言基本都会避免使用传统空值，而采用更安全的方式来表达可选值，具体方式主要有两种：</p>
<ol>
  <li>受限的空值：Kotlin、Swift、Hack、C# 8+ 等。</li>
  <li>可选值类型：Haskell、Rust、Julia、OCaml、Swift、<em style="color:gray">F#、Scala、Java 8+、C++17+</em> 等。</li>
</ol>

<p>你没看错，Swift 在两边都站队了。这倒并不是它采用了两种不同的机制，相反，它在一致的底层机制基础上，同时兼容两种上层语法。</p>

<p>另外 F#、Scala、Java 8+、C++ 17+ 实际上处于灰色地带，它们虽然推荐使用可选值类型，却也支持传统空值。</p>

<blockquote>
  <p>值得一提的是，无论采用哪种方式，其安全性都是由类型系统来保障的。
虽然这并不仅限于静态类型语言（Hack 与 Julia 都是动态类型语言），不过确实需要一定程度的静态类型支持，这也从侧面反映了<a href="https://hltj.me/lang/2017/08/01/morden-lang-static-type.html">现代编程语言的静态类型趋势</a>。</p>
</blockquote>

<h2 id="历史包袱">历史包袱</h2>
<p>采用受限空值的编程语言与处于灰色地带的编程语言一般都存在历史包袱。</p>

<p>采用受限空值的语言可能都与历史包袱有关：Kotlin 中有 <code class="language-plaintext highlighter-rouge">null</code> 因为 Java 中有，Hack 中有 <code class="language-plaintext highlighter-rouge">null</code> 因为 PHP 中有，Swift 中有 <code class="language-plaintext highlighter-rouge">nil</code> 因为 Objective-C 中有，C# 8+ 中有 <code class="language-plaintext highlighter-rouge">null</code> 是因为 C# 的历史版本中有且需要与 .Net 平台其他语言互操作。</p>

<p>处于灰色地带的 F#、Scala、Java 8+、C++ 17+ 同样有历史包袱，因为需要与 .Net/JVM 平台的其他语言互操作或者要兼容本语言的旧版本。</p>

<p>但是两队语言做了不同的抉择：一队采用受限的空值取代了传统空值；另一队引入了可选值类型的同时，却还保留传统空值。
于是后面这队语言虽有安全的方式，却也无法摆脱传统空值的纠缠。
Java 8 与 C++ 17 为了兼容历史版本或是无奈之选，但是如果历史重新给 F# 与 Scala 选择机会的话，会不会采用 Swift 的方案更好一些？</p>

<p>这里特别值得一提的是，<a href="https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/upgrade-to-nullable-references">C# 8 的选择</a>给了大家一个启示：
<strong>即便曾经做了错误的设计，仍然可以重回正轨</strong>，就看语言的后续版本有没有壮士断腕的勇气。</p>

<h2 id="受限的空值">受限的空值</h2>
<p>在采用受限的空值来表达可选值的编程语言中，对空值的使用有以下限制：</p>
<ol>
  <li>语言中严格区分可空类型与非空类型，不能直接将可空值用于只接受非空值的地方。</li>
  <li>语言中通过特定语法访问可空类型对象的成员，也需要特定语法由可空值得到非空值。</li>
</ol>

<h3 id="区分可空与非空类型">区分可空与非空类型</h3>
<p>Kotlin、Swift、Hack、C# 8+<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> 都严格区分可空类型与非空类型，并且类型都默认非空，对于可空类型也都采用加注 <code class="language-plaintext highlighter-rouge">?</code> 的方式来表达（只是 Hack 放在类型名前，Kotlin、Swift 与 C# 放在类型名后）。我们看些具体的示例：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Kotlin</span>
<span class="kd">val</span> <span class="py">a</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">1</span> <span class="c1">// OK</span>
<span class="kd">val</span> <span class="py">b</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="k">null</span> <span class="c1">// 错误，b 不接受空值</span>
<span class="kd">val</span> <span class="py">c</span><span class="p">:</span> <span class="nc">Int</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span> <span class="c1">// OK，c 是可空类型</span>

<span class="kd">val</span> <span class="py">d</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="n">a</span> <span class="c1">// OK</span>
<span class="kd">val</span> <span class="py">e</span><span class="p">:</span> <span class="nc">Int</span><span class="p">?</span> <span class="p">=</span> <span class="n">a</span> <span class="c1">// OK，非空表达式可以赋给可空变量</span>
<span class="kd">val</span> <span class="py">f</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="n">c</span> <span class="c1">// 错误，f 不接受可空表达式赋值</span>
<span class="kd">val</span> <span class="py">g</span><span class="p">:</span> <span class="nc">Int</span><span class="p">?</span> <span class="p">=</span> <span class="n">c</span> <span class="c1">// OK</span>
<span class="kd">val</span> <span class="py">h</span> <span class="p">=</span> <span class="n">c</span> <span class="c1">// OK，h 的类型会推断为 Int?</span>
</code></pre></div></div>
<p>Swift 版与之非常类似，只需将 <code class="language-plaintext highlighter-rouge">val</code> 替换为 <code class="language-plaintext highlighter-rouge">let</code>、<code class="language-plaintext highlighter-rouge">null</code> 替换为 <code class="language-plaintext highlighter-rouge">nil</code> 即可。
Hack 语法与它们差异大些、并且需要采用函数形式来表达上述示例，但其相似性还是很明显的：</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Hack</span>
<span class="k">function</span> <span class="n">a</span><span class="p">():</span> <span class="kt">int</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span>
<span class="k">function</span> <span class="n">b</span><span class="p">():</span> <span class="kt">int</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// 错误</span>
<span class="k">function</span> <span class="n">c</span><span class="p">():</span> <span class="kt">?int</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">null</span><span class="p">;</span> <span class="p">}</span>
<span class="k">function</span> <span class="n">d</span><span class="p">():</span> <span class="kt">int</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">a</span><span class="p">();</span> <span class="p">}</span>
<span class="k">function</span> <span class="n">e</span><span class="p">():</span> <span class="kt">?int</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">a</span><span class="p">();</span> <span class="p">}</span>
<span class="k">function</span> <span class="n">f</span><span class="p">():</span> <span class="kt">int</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">c</span><span class="p">();</span> <span class="p">}</span> <span class="c1">// 错误</span>
<span class="k">function</span> <span class="n">g</span><span class="p">():</span> <span class="kt">?int</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">c</span><span class="p">();</span> <span class="p">}</span>
<span class="k">function</span> <span class="n">h</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">c</span><span class="p">();</span> <span class="p">}</span>
</code></pre></div></div>

<p>除了变量声明与赋值、函数返回值之外，这几门语言对函数传参、数学运算等各种表达式都会严格区分可空与非空类型。</p>

<h3 id="可空性传播">可空性传播</h3>
<p>在采用受限空值的编程语言中，无法直接访问可空类型对象的成员，需要使用特殊语法。在 Kotlin、Swift 与 C# 8+ 中使用 <code class="language-plaintext highlighter-rouge">?.</code> 语法，在 Hack 中使用 <code class="language-plaintext highlighter-rouge">?-&gt;</code> 语法。例如输出一个可空字符串的长度：</p>

<p>Kotlin 代码，输出是 <code class="language-plaintext highlighter-rouge">12</code>：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">hello</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="s">"Hello, World"</span>
<span class="nf">println</span><span class="p">(</span><span class="n">hello</span><span class="o">?.</span><span class="n">length</span><span class="p">)</span>
<span class="n">hello</span><span class="o">?.</span><span class="n">length</span><span class="p">.</span><span class="nf">plus</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="c1">// 错误，可空值不能直接调用方法</span>
</code></pre></div></div>

<p>Swift 代码，输出是 <code class="language-plaintext highlighter-rouge">Optional(12)</code>：</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">hello</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="s">"Hello, World"</span>
<span class="nf">print</span><span class="p">(</span><span class="n">hello</span><span class="p">?</span><span class="o">.</span><span class="n">count</span><span class="p">)</span>
<span class="n">hello</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="o">+</span> <span class="mi">10</span> <span class="c1">// 错误，可空值不能用于算术运算</span>
</code></pre></div></div>

<p>Hack 代码有些复杂，因为内置字符串值不是对象，所以需要模拟出一个对象，其输出是 <code class="language-plaintext highlighter-rouge">12</code>:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">String0</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kt">string</span> <span class="nv">$s</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="kt">string</span> <span class="nv">$s</span><span class="p">)</span> <span class="p">{</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">s</span> <span class="o">=</span> <span class="nv">$s</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">length</span><span class="p">():</span> <span class="kt">int</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nb">strlen</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="n">s</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">function</span> <span class="n">hello</span><span class="p">():</span> <span class="kt">?String0</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nc">String0</span><span class="p">(</span><span class="s2">"Hello, World"</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">function</span> <span class="n">printLength</span><span class="p">(</span><span class="kt">?String0</span> <span class="nv">$s</span><span class="p">)</span> <span class="p">{</span>
   <span class="k">echo</span> <span class="nv">$s</span><span class="o">?-&gt;</span><span class="nf">length</span><span class="p">(),</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>

<span class="nf">printLength</span><span class="p">(</span><span class="k">new</span> <span class="nc">String0</span><span class="p">(</span><span class="s2">"Hello, World"</span><span class="p">));</span>

<span class="k">function</span> <span class="n">foo</span><span class="p">():</span> <span class="kt">int</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">hello</span><span class="p">()</span><span class="o">?-&gt;</span><span class="nf">length</span><span class="p">();</span> <span class="p">}</span> <span class="c1">// 错误，函数签名要求非空返回值</span>
</code></pre></div></div>

<p>Swift 输出的是 <code class="language-plaintext highlighter-rouge">Optional(12)</code>，它明确表明这是一个 <code class="language-plaintext highlighter-rouge">Int?</code> 值。Kotlin 与 Hack 虽然直接输出了数字，但其值同样是可空整型，不能用于只接受非空整数的地方。<code class="language-plaintext highlighter-rouge">?.</code>/<code class="language-plaintext highlighter-rouge">?-&gt;</code>的求值逻辑为：</p>
<ol>
  <li>如果对象非空，那么访问相应成员。</li>
  <li>如果对象为空，返回空。</li>
  <li>返回类型为可空类型。</li>
</ol>

<p>以 Kotlin 为例，虽然 <code class="language-plaintext highlighter-rouge">String</code> 的 <code class="language-plaintext highlighter-rouge">length</code> 属性是非空成员，但因为 <code class="language-plaintext highlighter-rouge">hello</code> 是可空的，进而导致 <code class="language-plaintext highlighter-rouge">hello?.length</code> 也是可空的，因此如需继续调用 <code class="language-plaintext highlighter-rouge">plus</code> 也要使用 <code class="language-plaintext highlighter-rouge">?.</code> 语法：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;&gt;&gt;</span> <span class="n">hello</span><span class="o">?.</span><span class="n">length</span><span class="o">?.</span><span class="nf">plus</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
<span class="mi">22</span>
</code></pre></div></div>

<p>并且这一表达式依然是可空的，因此如果还有后续成员访问，就还需使用 <code class="language-plaintext highlighter-rouge">?.</code> 语法：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;&gt;&gt;</span> <span class="n">hello</span><span class="o">?.</span><span class="n">length</span><span class="o">?.</span><span class="nf">plus</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="o">?.</span><span class="nf">times</span><span class="p">(</span><span class="mf">1.2</span><span class="p">)</span><span class="o">?.</span><span class="nf">toLong</span><span class="p">()</span>
<span class="mi">26</span>
</code></pre></div></div>

<p>可空性就像病毒一样感染了整个调用链条，并且会继续传播下去。在 C# 8+ 中也是如此；在 Hack 中与此类似，只是使用 <code class="language-plaintext highlighter-rouge">?-&gt;</code> 语法。不过 Swift 的语法与它们不同，对于上述情况，后续链条中用 <code class="language-plaintext highlighter-rouge">.</code> 即可，但是最终结果仍然是可空值：</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">3</span><span class="o">&gt;</span> <span class="k">let</span> <span class="nv">a</span> <span class="o">=</span> <span class="n">hello</span><span class="p">?</span><span class="o">.</span><span class="n">utf8</span><span class="o">.</span><span class="n">count</span><span class="o">.</span><span class="nf">advanced</span><span class="p">(</span><span class="nv">by</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span><span class="o">.</span><span class="nf">distance</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="mi">100</span><span class="p">)</span>
<span class="nv">a</span><span class="p">:</span> <span class="kt">Int</span><span class="p">?</span> <span class="o">=</span> <span class="mi">78</span>
</code></pre></div></div>

<p>Swift 中只有后续成员的返回值本身也是可空类型时才需要再次使用 <code class="language-plaintext highlighter-rouge">?.</code>，参见其<a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/optionalchaining/#Linking-Multiple-Levels-of-Chaining">官网介绍</a>：</p>

<blockquote>
  <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="k">let</span> <span class="nv">johnsStreet</span> <span class="o">=</span> <span class="n">john</span><span class="o">.</span><span class="n">residence</span><span class="p">?</span><span class="o">.</span><span class="n">address</span><span class="p">?</span><span class="o">.</span><span class="n">street</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"John's street name is </span><span class="se">\(</span><span class="n">johnsStreet</span><span class="se">)</span><span class="s">."</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nf">print</span><span class="p">(</span><span class="s">"Unable to retrieve the address."</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>  </div>
</blockquote>

<p>当然，在 Kotlin 中也可以通过高阶函数 <code class="language-plaintext highlighter-rouge">let</code> 来简化多级 <code class="language-plaintext highlighter-rouge">?.</code> 的语法，进而达到接近 Swift 的效果：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;&gt;&gt;</span> <span class="n">hello</span><span class="o">?.</span><span class="nf">let</span><span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="n">length</span><span class="p">.</span><span class="nf">plus</span><span class="p">(</span><span class="mi">10</span><span class="p">).</span><span class="nf">times</span><span class="p">(</span><span class="mf">1.2</span><span class="p">).</span><span class="nf">toLong</span><span class="p">()</span> <span class="p">}</span>
<span class="mi">26</span>
</code></pre></div></div>

<p>此外 Swift 与 C# 还支持可空对象的索引访问（<code class="language-plaintext highlighter-rouge">?[]</code>）语法，C# 8 还引入了空接合赋值操作符（<code style="white-space: nowrap;" class="language-plaintext highlighter-rouge">??=</code>）。</p>

<h3 id="可空值用于常规函数">可空值用于常规函数</h3>
<p>如果不是访问成员，而是用于普通函数，例如将上述链式调用的结果传给 <code class="language-plaintext highlighter-rouge">sin</code> 函数并输出其结果，该如何实现呢？
这在 Kotlin 与 Swift 中分别有不同的语法：</p>

<p>Kotlin 代码：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;&gt;&gt;</span> <span class="n">hello</span><span class="o">?.</span><span class="n">length</span><span class="o">?.</span><span class="nf">plus</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="o">?.</span><span class="nf">times</span><span class="p">(</span><span class="mf">1.2</span><span class="p">)</span><span class="o">?.</span><span class="nf">toLong</span><span class="p">()</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span>
<span class="o">..</span><span class="p">.</span>     <span class="nf">println</span><span class="p">(</span><span class="nc">Math</span><span class="p">.</span><span class="nf">sin</span><span class="p">(</span><span class="n">it</span> <span class="p">*</span> <span class="mf">1.0</span><span class="p">))</span>
<span class="o">..</span><span class="p">.</span> <span class="p">}</span>
<span class="mf">0.7625584504796028</span>
</code></pre></div></div>

<p>Swift 代码：</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">4</span><span class="o">&gt;</span> <span class="kd">import</span> <span class="kt">Foundation</span>
<span class="mi">5</span><span class="o">&gt;</span> <span class="k">if</span> <span class="k">let</span> <span class="nv">a</span> <span class="o">=</span> <span class="n">hello</span><span class="p">?</span><span class="o">.</span><span class="n">utf8</span><span class="o">.</span><span class="n">count</span><span class="o">.</span><span class="nf">advanced</span><span class="p">(</span><span class="nv">by</span><span class="p">:</span> <span class="mi">10</span><span class="p">)</span><span class="o">.</span><span class="nf">distance</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="mi">100</span><span class="p">)</span> <span class="p">{</span>
<span class="mi">6</span><span class="o">.</span>     <span class="nf">print</span><span class="p">(</span><span class="nf">sin</span><span class="p">(</span><span class="kt">Double</span><span class="p">(</span><span class="n">a</span><span class="p">)))</span>
<span class="mi">7</span><span class="o">.</span> <span class="p">}</span>
<span class="mf">0.513978455987535</span>
</code></pre></div></div>

<p>目前在 Hack 中没有类似语法，可以通过更通用的由可空表达式获得非空值的方式实现。</p>

<h3 id="由可空表达式获得非空值">由可空表达式获得非空值</h3>
<p>这里只讨论安全获得非空值的方式。由可空表达式安全地获得非空值还需要提供一个默认值，这样就一定能够取得非空值：当表达式求值结果非空时取求值结果，否则取默认值。这在  Kotlin 中通过 Elvis 操作符（<code class="language-plaintext highlighter-rouge">?:</code>）来实现，在 Swift、Hack 与 C# 中通过空接合操作符（<code class="language-plaintext highlighter-rouge">??</code>）来实现。</p>

<blockquote>
  <p>现在有没有觉得受限空值与问号（<code class="language-plaintext highlighter-rouge">?</code>）结下了不解之缘 :P</p>
</blockquote>

<p>Kotlin 示例：</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;&gt;&gt;</span> <span class="kd">val</span> <span class="py">hello</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="s">"Hello, World"</span>
<span class="p">&gt;&gt;&gt;</span> <span class="kd">val</span> <span class="py">len</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="n">hello</span><span class="o">?.</span><span class="n">length</span> <span class="o">?:</span> <span class="mi">0</span>
<span class="p">&gt;&gt;&gt;</span> <span class="p">((</span><span class="n">hello</span><span class="o">?.</span><span class="n">length</span> <span class="o">?:</span> <span class="mi">0</span><span class="p">)</span> <span class="p">+</span> <span class="mi">10</span><span class="p">)</span> <span class="p">*</span> <span class="mf">1.2</span>
<span class="mf">26.4</span>
<span class="p">&gt;&gt;&gt;</span> <span class="kd">val</span> <span class="py">hello</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="p">&gt;&gt;&gt;</span> <span class="p">((</span><span class="n">hello</span><span class="o">?.</span><span class="n">length</span> <span class="o">?:</span> <span class="mi">0</span><span class="p">)</span> <span class="p">+</span> <span class="mi">10</span><span class="p">)</span> <span class="p">*</span> <span class="mf">1.2</span>
<span class="mf">12.0</span>
</code></pre></div></div>

<p>Swift 示例：</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="mi">1</span><span class="o">&gt;</span> <span class="k">let</span> <span class="nv">hello</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="s">"Hello, World"</span> 
<span class="nv">hello</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="s">"Hello, World"</span>
  <span class="mi">2</span><span class="o">&gt;</span> <span class="k">let</span> <span class="nv">len</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="n">hello</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="p">??</span> <span class="mi">0</span> 
<span class="nv">len</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">12</span>
  <span class="mi">3</span><span class="o">&gt;</span> <span class="mi">100</span> <span class="o">-</span> <span class="p">((</span><span class="n">hello</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="p">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">10</span><span class="p">)</span>
<span class="err">$</span><span class="kt">R0</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">78</span>
  <span class="mi">4</span><span class="o">&gt;</span> <span class="k">let</span> <span class="nv">hello</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="nv">hello</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span>
  <span class="mi">5</span><span class="o">&gt;</span> <span class="mi">100</span> <span class="o">-</span> <span class="p">((</span><span class="n">hello</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="p">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">10</span><span class="p">)</span> 
<span class="err">$</span><span class="kt">R1</span><span class="p">:</span> <span class="kt">Int</span> <span class="o">=</span> <span class="mi">90</span>
</code></pre></div></div>

<h2 id="可选值类型">可选值类型</h2>
<p>更多的现代编程语言都是采用可选值类型的方式。在这些语言中，都是通过一种专门的包装类型来表达可选值。
下表列举了一些语言中可选字符串的类型以及有无值的字面值表示法：</p>

<table>
  <thead>
    <tr>
      <th>语言</th>
      <th>可选值类型</th>
      <th>无值</th>
      <th>有值</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Haskell</td>
      <td><code class="language-plaintext highlighter-rouge">Maybe String</code></td>
      <td><code class="language-plaintext highlighter-rouge">Nothing</code></td>
      <td><code class="language-plaintext highlighter-rouge">Just "Hello"</code></td>
    </tr>
    <tr>
      <td>Rust</td>
      <td><code class="language-plaintext highlighter-rouge">Option&lt;&amp;str&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">None</code></td>
      <td><code class="language-plaintext highlighter-rouge">Some("Hello")</code></td>
    </tr>
    <tr>
      <td>Julia</td>
      <td><code class="language-plaintext highlighter-rouge">Union{Some{T}, Nothing}</code></td>
      <td><code class="language-plaintext highlighter-rouge">nothing</code></td>
      <td><code class="language-plaintext highlighter-rouge">Some("Hello")</code></td>
    </tr>
    <tr>
      <td>OCaml/F#</td>
      <td><code class="language-plaintext highlighter-rouge">string option</code></td>
      <td><code class="language-plaintext highlighter-rouge">None</code></td>
      <td><code class="language-plaintext highlighter-rouge">Some "Hello"</code></td>
    </tr>
    <tr>
      <td>Swift</td>
      <td><code class="language-plaintext highlighter-rouge">Optional&lt;String&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">Optional.none</code></td>
      <td><code class="language-plaintext highlighter-rouge">Optional.some("Hello")</code></td>
    </tr>
    <tr>
      <td>Scala</td>
      <td><code class="language-plaintext highlighter-rouge">Option[String]</code></td>
      <td><code class="language-plaintext highlighter-rouge">None</code></td>
      <td><code class="language-plaintext highlighter-rouge">Some("Hello")</code></td>
    </tr>
    <tr>
      <td>Java 8+</td>
      <td><code class="language-plaintext highlighter-rouge">Optional&lt;String&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">Optional.empty()</code></td>
      <td><code class="language-plaintext highlighter-rouge">Optional.of("Hello")</code></td>
    </tr>
    <tr>
      <td>C++ 17+</td>
      <td><code class="language-plaintext highlighter-rouge">optional&lt;string&gt;</code></td>
      <td><code class="language-plaintext highlighter-rouge">nullopt</code></td>
      <td><code class="language-plaintext highlighter-rouge">optional{"Hello"}</code></td>
    </tr>
  </tbody>
</table>

<p>包装后的类型与原类型明显不同，因此无法当作原类型来用。那么应该如何使用呢？</p>

<h3 id="显式判断与模式匹配">显式判断与模式匹配</h3>
<p>最简单的使用方式是显式判断，例如求一个可选字符串的长度（Julia）：</p>

<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">julia</span><span class="o">&gt;</span> <span class="n">lengthOfOptionalString</span><span class="x">(</span><span class="n">us</span><span class="o">::</span><span class="kt">Union</span><span class="x">{</span><span class="kt">Some</span><span class="x">{</span><span class="kt">String</span><span class="x">},</span> <span class="kt">Nothing</span><span class="x">})</span> <span class="o">=</span>
           <span class="k">if</span> <span class="n">us</span> <span class="o">===</span> <span class="nb">nothing</span>
               <span class="mi">0</span>
           <span class="k">else</span>
               <span class="n">length</span><span class="x">(</span><span class="n">us</span><span class="o">.</span><span class="n">value</span><span class="x">)</span>
           <span class="k">end</span>
<span class="n">lengthOfOptionalString</span> <span class="x">(</span><span class="n">generic</span> <span class="k">function</span><span class="nf"> with</span> <span class="mi">1</span> <span class="n">method</span><span class="x">)</span>

<span class="n">julia</span><span class="o">&gt;</span> <span class="n">lengthOfOptionalString</span><span class="x">(</span><span class="nb">nothing</span><span class="x">)</span>
<span class="mi">0</span>

<span class="n">julia</span><span class="o">&gt;</span> <span class="n">lengthOfOptionalString</span><span class="x">(</span><span class="kt">Some</span><span class="x">(</span><span class="s">"Hello"</span><span class="x">))</span>
<span class="mi">5</span>
</code></pre></div></div>

<p>如果用 C++ 或者 Java 实现会与之非常相似。而对于上文所列的其他使用可选值类型的语言，都可以采用模式匹配的方式实现类似功能。例如（Haskell）：</p>

<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ghci</span> <span class="o">&gt;</span> <span class="o">:</span><span class="p">{</span>
<span class="n">ghci</span> <span class="o">|</span> <span class="n">lengthOfMaybeString</span><span class="o">::</span> <span class="kt">Maybe</span> <span class="kt">String</span> <span class="o">-&gt;</span> <span class="kt">Int</span>
<span class="n">ghci</span> <span class="o">|</span> <span class="n">lengthOfMaybeString</span> <span class="kt">Nothing</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">ghci</span> <span class="o">|</span> <span class="n">lengthOfMaybeString</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">s</span><span class="p">)</span> <span class="o">=</span> <span class="n">length</span> <span class="n">s</span>
<span class="n">ghci</span> <span class="o">|</span> <span class="o">:</span><span class="p">}</span>
<span class="n">ghci</span> <span class="o">&gt;</span> <span class="n">lengthOfMaybeString</span> <span class="kt">Nothing</span>
<span class="mi">0</span>
<span class="n">ghci</span> <span class="o">&gt;</span> <span class="n">lengthOfMaybeString</span> <span class="p">(</span><span class="kt">Just</span> <span class="s">"Hello"</span><span class="p">)</span>
<span class="mi">5</span>
</code></pre></div></div>

<p>在 Haskell 中只需在声明函数时对 <code class="language-plaintext highlighter-rouge">Maybe String</code> 类型参数的不同模式 <code class="language-plaintext highlighter-rouge">Nothing</code> 与 <code class="language-plaintext highlighter-rouge">Just s</code> 分别编写实现即可。函数调用时 Haskell 会根据实参类型自动匹配到相应实现。</p>

<blockquote>
  <p>实际上，Julia 虽然没有在语言级支持完整的模式匹配，但是在 Julia 中可以通过泛型函数实现与上述 Haskell 代码类似的写法：</p>

  <div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">julia</span><span class="o">&gt;</span> <span class="n">lengthOfOptionalString</span><span class="x">(</span><span class="nb">nothing</span><span class="x">)</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">lengthOfOptionalString</span> <span class="x">(</span><span class="n">generic</span> <span class="k">function</span><span class="nf"> with</span> <span class="mi">1</span> <span class="n">method</span><span class="x">)</span>

<span class="n">julia</span><span class="o">&gt;</span> <span class="n">lengthOfOptionalString</span><span class="x">(</span><span class="n">ss</span><span class="o">::</span><span class="kt">Some</span><span class="x">{</span><span class="kt">String</span><span class="x">})</span> <span class="o">=</span> <span class="n">length</span><span class="x">(</span><span class="n">ss</span><span class="o">.</span><span class="n">value</span><span class="x">)</span>
<span class="n">lengthOfOptionalString</span> <span class="x">(</span><span class="n">generic</span> <span class="k">function</span><span class="nf"> with</span> <span class="mi">2</span> <span class="n">methods</span><span class="x">)</span>

<span class="n">julia</span><span class="o">&gt;</span> <span class="n">lengthOfOptionalString</span><span class="x">(</span><span class="nb">nothing</span><span class="x">)</span>
<span class="mi">0</span>

<span class="n">julia</span><span class="o">&gt;</span> <span class="n">lengthOfOptionalString</span><span class="x">(</span><span class="kt">Some</span><span class="x">(</span><span class="s">"Hello"</span><span class="x">))</span>
<span class="mi">5</span>
</code></pre></div>  </div>
</blockquote>

<p>我们再看一下 Rust 的写法：</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irust</span><span class="o">&gt;</span> <span class="k">let</span> <span class="n">a</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;&amp;</span><span class="nb">str</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nb">None</span><span class="p">;</span>
<span class="p">()</span>
<span class="n">irust</span><span class="o">&gt;</span> <span class="k">let</span> <span class="n">b</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;&amp;</span><span class="nb">str</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">);</span>
<span class="p">()</span>
<span class="n">irust</span><span class="o">&gt;</span> <span class="k">match</span> <span class="n">a</span> <span class="p">{</span> <span class="nb">None</span> <span class="k">=&gt;</span> <span class="mi">0</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="k">ref</span> <span class="n">s</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">s</span><span class="nf">.len</span><span class="p">()</span> <span class="p">}</span>
<span class="mi">0</span>
<span class="n">irust</span><span class="o">&gt;</span> <span class="k">match</span> <span class="n">b</span> <span class="p">{</span> <span class="nb">None</span> <span class="k">=&gt;</span> <span class="mi">0</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="k">ref</span> <span class="n">s</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="n">s</span><span class="nf">.len</span><span class="p">()</span> <span class="p">}</span>
<span class="mi">5</span>
</code></pre></div></div>

<p>这段代码乍一看跟传统语言的 switch-case 很类似，但实际上要强大的多。
上述代码中的 <code class="language-plaintext highlighter-rouge">Some(ref s) =&gt; 含 s 的表达式</code> 就是传统 switch-case 无法支持的。对于匹配到模式 <code class="language-plaintext highlighter-rouge">Some(ref s)</code> 的 <code class="language-plaintext highlighter-rouge">Option</code>，Rust 能够自动提取模式中对应的 <code class="language-plaintext highlighter-rouge">s</code>，并用于后续处理。</p>

<p>我们可以通过显式判断或模式匹配来处理可选值类型，但通常并不这么做，因为还有更便捷的方式。</p>

<h3 id="函数式方式">函数式方式</h3>
<p>以函数式方式实现求一个可选字符串的长度的代码，可以这样写（Java）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="kt">int</span> <span class="nf">lengthOfOptionalString</span><span class="o">(</span><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">opStr</span><span class="o">)</span> <span class="o">{</span>
   <span class="o">...&gt;</span>     <span class="k">return</span> <span class="n">opStr</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">String:</span><span class="o">:</span><span class="n">length</span><span class="o">).</span><span class="na">orElse</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
   <span class="o">...&gt;</span> <span class="o">}</span>
   <span class="o">...&gt;</span>
<span class="o">|</span> <span class="n">已创建</span> <span class="n">方法</span> <span class="n">lengthOfOptionalString</span><span class="o">(</span><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;)</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nf">lengthOfOptionalString</span><span class="o">(</span><span class="nc">Optional</span><span class="o">.</span><span class="na">empty</span><span class="o">())</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="mi">0</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nf">lengthOfOptionalString</span><span class="o">(</span><span class="nc">Optional</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"Hello"</span><span class="o">))</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="mi">5</span>
</code></pre></div></div>

<p>这里用到了 <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;</code> 的两个方法：<code class="language-plaintext highlighter-rouge">map()</code> 与 <code class="language-plaintext highlighter-rouge">orElse()</code>。</p>

<p>其中 <code class="language-plaintext highlighter-rouge">Optional&lt;T&gt;.map()</code> 接受一个函数式接口参数 <code class="language-plaintext highlighter-rouge">mapper</code>（可以传入 lambda 表达式或者方法引用），如果可选值无值，那么直接返回 <code class="language-plaintext highlighter-rouge">Optional.empty()</code>；而如果有值，那么返回对其值调用 <code class="language-plaintext highlighter-rouge">mapper</code> 所得结果的 <code class="language-plaintext highlighter-rouge">Optional&lt;U&gt;</code> 包装。</p>

<p><code class="language-plaintext highlighter-rouge">Optional&lt;U&gt;.orElse()</code> 接受一个 <code class="language-plaintext highlighter-rouge">U</code> 类型的参数 <code class="language-plaintext highlighter-rouge">default</code>，如果可选值有值则返回其值，如果无值返回 <code class="language-plaintext highlighter-rouge">default</code>，因此通过 <code class="language-plaintext highlighter-rouge">Optional&lt;U&gt;.orElse()</code> 总能得到一个 <code class="language-plaintext highlighter-rouge">U</code> 类型的值。</p>

<p>实际上，可选值类型是 <a href="https://hltj.me/kotlin/2017/08/25/kotlin-functor-applicative-monad-cn.html">Functor、Applicative、Monad</a>，上述 <code class="language-plaintext highlighter-rouge">Optional.map()</code> 相当于 Haskell 中 <code class="language-plaintext highlighter-rouge">Functor</code> 的 <code class="language-plaintext highlighter-rouge">fmap</code>/<code class="language-plaintext highlighter-rouge">&lt;$&gt;</code>。此外，常用的还有相当于 <code class="language-plaintext highlighter-rouge">Monad</code> 的 <code class="language-plaintext highlighter-rouge">&gt;&gt;=</code> 的函数，如 Java 的 <code class="language-plaintext highlighter-rouge">Optional.flatMap()</code>、Rust 的 <code class="language-plaintext highlighter-rouge">Option::and_then()</code>、OCaml 4.08+/F# 的 <code class="language-plaintext highlighter-rouge">Option.bind</code> 等。我们看一个 F# 的示例——对一个整数可选值求余：</p>

<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&gt;</span> <span class="k">let</span> <span class="o">(%?)</span> <span class="n">a</span> <span class="n">b</span> <span class="p">=</span>
<span class="p">-</span>     <span class="k">match</span> <span class="n">b</span> <span class="k">with</span>
<span class="p">-</span>     <span class="p">|</span> <span class="mi">0</span> <span class="p">-&gt;</span> <span class="nc">None</span>
<span class="p">-</span>     <span class="p">|</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="nc">Some</span> <span class="p">(</span><span class="n">a</span> <span class="o">%</span> <span class="n">b</span><span class="o">);;</span>
<span class="k">val</span> <span class="p">(</span> <span class="o">%?</span> <span class="p">)</span> <span class="p">:</span> <span class="n">a</span><span class="p">:</span><span class="kt">int</span> <span class="p">-&gt;</span> <span class="n">b</span><span class="p">:</span><span class="kt">int</span> <span class="p">-&gt;</span> <span class="kt">int</span> <span class="n">option</span>

<span class="p">&gt;</span> <span class="mi">18</span> <span class="o">%?</span> <span class="mi">5</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">Some</span> <span class="mi">3</span>

<span class="p">&gt;</span> <span class="mi">18</span> <span class="o">%?</span> <span class="mi">0</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">None</span>

<span class="p">&gt;</span> <span class="nn">Option</span><span class="p">.</span><span class="n">map</span> <span class="o">((%?)</span> <span class="mi">18</span><span class="p">)</span> <span class="p">(</span><span class="nc">Some</span> <span class="mi">5</span><span class="o">);;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">Some</span> <span class="p">(</span><span class="nc">Some</span> <span class="mi">3</span><span class="p">)</span>

<span class="p">&gt;</span> <span class="nn">Option</span><span class="p">.</span><span class="n">map</span> <span class="o">((%?)</span> <span class="mi">18</span><span class="p">)</span> <span class="p">(</span><span class="nc">Some</span> <span class="mi">0</span><span class="o">);;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">Some</span> <span class="nc">None</span>

<span class="p">&gt;</span> <span class="nn">Option</span><span class="p">.</span><span class="n">map</span> <span class="o">((%?)</span> <span class="mi">18</span><span class="p">)</span> <span class="nc">None</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">None</span>

<span class="p">&gt;</span> <span class="nn">Option</span><span class="p">.</span><span class="n">bind</span> <span class="o">((%?)</span> <span class="mi">18</span><span class="p">)</span> <span class="p">(</span><span class="nc">Some</span> <span class="mi">5</span><span class="o">);;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">Some</span> <span class="mi">3</span>

<span class="p">&gt;</span> <span class="nn">Option</span><span class="p">.</span><span class="n">bind</span> <span class="o">((%?)</span> <span class="mi">18</span><span class="p">)</span> <span class="p">(</span><span class="nc">Some</span> <span class="mi">0</span><span class="o">);;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">None</span>

<span class="p">&gt;</span> <span class="nn">Option</span><span class="p">.</span><span class="n">bind</span> <span class="o">((%?)</span> <span class="mi">18</span><span class="p">)</span> <span class="nc">None</span><span class="p">;;</span>
<span class="k">val</span> <span class="n">it</span> <span class="p">:</span> <span class="kt">int</span> <span class="n">option</span> <span class="p">=</span> <span class="nc">None</span>
</code></pre></div></div>

<blockquote>
  <p>上述代码只需将 <code class="language-plaintext highlighter-rouge">%</code> 运算符替换为 <code class="language-plaintext highlighter-rouge">mod</code> 并交换 <code class="language-plaintext highlighter-rouge">Option.bind</code> 两个参数的位置即可用于 OCaml 4.08+。</p>
</blockquote>

<p>示例中首先定义了一个安全求余运算符 <code class="language-plaintext highlighter-rouge">%?</code>，当除数为 <code class="language-plaintext highlighter-rouge">0</code> 时它返回 <code class="language-plaintext highlighter-rouge">None</code>，否则返回 <code class="language-plaintext highlighter-rouge">Some 余数</code>。
<code class="language-plaintext highlighter-rouge">%?</code> 只接受整数作除数，如需将其应用到 <code class="language-plaintext highlighter-rouge">int option</code> 可以借助 <code class="language-plaintext highlighter-rouge">Option.map</code>，但是这样得到的结果是嵌套的 <code class="language-plaintext highlighter-rouge">option</code>（即 <code class="language-plaintext highlighter-rouge">int option option</code>）。 有没有可能直接得到单层的 <code class="language-plaintext highlighter-rouge">int option</code> 呢？——这就需要 <code class="language-plaintext highlighter-rouge">Option.bind</code> 大显身手了，如例中所示。</p>

<blockquote>
  <p>注：上文所列的其他使用可选值类型的语言的标准库没有为可选值类型实现类似 Haskell 中 <code class="language-plaintext highlighter-rouge">Applicative</code> 的 <code class="language-plaintext highlighter-rouge">liftA2</code>/<code class="language-plaintext highlighter-rouge">&lt;*&gt;</code> 的函数，可选用第三方实现或者参考 <a href="http://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Applicative.html#t:Applicative">Applicative 文档</a>或 <a href="https://hltj.me/kotlin/2017/08/25/kotlin-functor-applicative-monad-cn.html">Kotlin 版图解 Functor、Applicative 与 Monad</a> 自行实现。</p>
</blockquote>

<h2 id="综合示例">综合示例</h2>
<p>我们看一个 <a href="https://play.kotlinlang.org/koans/Introduction/Nullable%20types/Task.kt">Kotlin 心印中的示例</a>：实现一个给客户发消息的函数，其中客户、消息、客户的个人信息字段、个人信息的邮箱地址字段都可能无值。
用传统 Java 代码实现如下所示：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">sendMessageToClient</span><span class="o">(</span>
    <span class="nd">@Nullable</span> <span class="nc">Client</span> <span class="n">client</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">message</span><span class="o">,</span> <span class="nd">@NotNull</span> <span class="nc">Mailer</span> <span class="n">mailer</span>
<span class="o">)</span> <span class="o">{</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">client</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">message</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>

    <span class="nc">PersonalInfo</span> <span class="n">personalInfo</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="na">getPersonalInfo</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">personalInfo</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>

    <span class="nc">String</span> <span class="n">email</span> <span class="o">=</span> <span class="n">personalInfo</span><span class="o">.</span><span class="na">getEmail</span><span class="o">();</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">email</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>

    <span class="n">mailer</span><span class="o">.</span><span class="na">sendMessage</span><span class="o">(</span><span class="n">email</span><span class="o">,</span> <span class="n">message</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>上述代码使用了卫语句，可以说已经是质量很高的传统 Java 代码了。
但由于对其中每处可空值都需要判空，有意义的代码与判空代码各有三行，可以说一半的代码都浪费在了毫无业务价值而又不得不做的事情上了。
同时该代码中有 4 个分支、4 个出口，代码虽不多，流程却已略显复杂。
而如果使用 Java 8 的 <code class="language-plaintext highlighter-rouge">Optional</code>，就可以流畅很多：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">sendMessageToClient</span><span class="o">(</span>
    <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Client</span><span class="o">&gt;</span> <span class="n">client</span><span class="o">,</span> <span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">message</span><span class="o">,</span> <span class="nd">@NotNull</span> <span class="nc">Mailer</span> <span class="n">mailer</span>
<span class="o">)</span> <span class="o">{</span>
    <span class="n">message</span><span class="o">.</span><span class="na">ifPresent</span><span class="o">(</span>
            <span class="n">message1</span> <span class="o">-&gt;</span> <span class="n">client</span>
                    <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="nl">Client:</span><span class="o">:</span><span class="n">getPersonalInfo</span><span class="o">)</span>
                    <span class="o">.</span><span class="na">flatMap</span><span class="o">(</span><span class="nl">PersonalInfo:</span><span class="o">:</span><span class="n">getEmail</span><span class="o">)</span>
                    <span class="o">.</span><span class="na">ifPresent</span><span class="o">(</span>
                        <span class="n">email</span> <span class="o">-&gt;</span> <span class="n">mailer</span><span class="o">.</span><span class="na">sendMessage</span><span class="o">(</span><span class="n">email</span><span class="o">,</span> <span class="n">message1</span><span class="o">)</span>
                    <span class="o">)</span>
    <span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在 Swift 语言中，可以综合使用受限空值与可选值类型的语法，代码会更简洁一些：</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">sendMessageToClient</span><span class="p">(</span><span class="nv">client</span><span class="p">:</span> <span class="kt">Client</span><span class="p">?,</span> <span class="nv">message</span><span class="p">:</span> <span class="kt">String</span><span class="p">?,</span> <span class="nv">mailer</span><span class="p">:</span> <span class="kt">Mailer</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">message</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span>
        <span class="n">message1</span> <span class="k">in</span> <span class="n">client</span><span class="p">?</span><span class="o">.</span><span class="n">personalInfo</span><span class="p">?</span><span class="o">.</span><span class="n">email</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span>
            <span class="n">email</span> <span class="k">in</span> <span class="n">mailer</span><span class="o">.</span><span class="nf">sendMessage</span><span class="p">(</span><span class="n">email</span><span class="p">,</span> <span class="n">message1</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在 Kotlin 语言中，可以综合使用受限空值与 <code class="language-plaintext highlighter-rouge">return</code> 表达式，代码会非常简洁：</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">sendMessageToClient</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="nc">Client</span><span class="p">?,</span> <span class="n">message</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span> <span class="n">mailer</span><span class="p">:</span> <span class="nc">Mailer</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">mailer</span><span class="p">.</span><span class="nf">sendMessage</span><span class="p">(</span><span class="n">client</span><span class="o">?.</span><span class="n">personalInfo</span><span class="o">?.</span><span class="n">email</span> <span class="o">?:</span> <span class="k">return</span><span class="p">,</span> <span class="n">message</span> <span class="o">?:</span> <span class="k">return</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="综述">综述</h2>
<p>传统空值会带来一系列问题，为避免这些问题，现代编程语言通常采用<strong>受限的空值</strong>或者<strong>可选值类型</strong>来表达可选值。
这些现代编程语言不仅通过类型系统确保了可选值的安全性，还提供了各种相对便利的使用方式来提升可选值的易用性。</p>

<blockquote>
  <h3 id="修订记录">修订记录</h3>
  <p>2020-03-27：</p>
  <ul>
    <li>补充 OCaml 4.08+ 的 Option.bind 相关内容</li>
    <li>增加部分 C# 8+ 相关内容</li>
  </ul>

  <p>2025-10-19：</p>
  <ul>
    <li>修正手误</li>
    <li>更新 Swift 多层 Optional 链式调用文档链接</li>
  </ul>
</blockquote>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>C# 对于引用类型的严格可空性区分是 C# 8 引入的，而 C# 值类型自 C# 2.0 引入可空支持起一直就区分。 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="lang" /><summary type="html"><![CDATA[这里的可选值（optional value）是指可能无值也可能有一个值的情况，在一些编程语言中称为可空值（nullable value）。 问题与解决方案 传统编程语言中往往使用空值（null 或者 None、nil 等）来表达可选值，可谓简单粗暴。]]></summary></entry><entry><title type="html">【怀旧】在 64 位 Windows 中运行 16 位 Windows/DOS 程序</title><link href="https://hltj.me/nostalgic/2019/05/30/dos-win16-on-win64.html" rel="alternate" type="text/html" title="【怀旧】在 64 位 Windows 中运行 16 位 Windows/DOS 程序" /><published>2019-05-30T12:12:05+00:00</published><updated>2019-05-30T12:12:05+00:00</updated><id>https://hltj.me/nostalgic/2019/05/30/dos-win16-on-win64</id><content type="html" xml:base="https://hltj.me/nostalgic/2019/05/30/dos-win16-on-win64.html"><![CDATA[<p>相信好多人都知道，在基于 NT 的 32 位 Windows 中，可以通过 <a href="https://en.wikipedia.org/wiki/Virtual_DOS_machine#Windows_NTVDM">NTVDM</a> 运行 DOS 程序，进而还可以通过 <a href="https://zh.wikipedia.org/wiki/Windows_on_Windows">WOW</a> 运行 16 位 Windows（Windows 1.x～3.x）程序。</p>

<p>但是在 64 位 Windows 中没有 NTVDM 与 WOW。
<!--more--></p>

<p>于是有人（<a href="https://github.com/leecher1337">leecher1337</a>）开发了 <a href="https://github.com/leecher1337/ntvdmx64">NTVDMx64</a>——可以在 64 位 Windows 环境中运行的 NTVDM。</p>

<p><a href="https://github.com/leecher1337/ntvdmx64">https://github.com/leecher1337/ntvdmx64</a></p>

<p>不过它可能会对系统有些影响，在该项目的 README 中提到了可能会导致一些 DPMI 程序崩溃。README 中还解释了为什么已经有 DOSBox 了还要移植 NTVDM。</p>

<p>而 <a href="https://github.com/otya128">otya</a> 的 <a href="https://github.com/otya128/winevdm">WineVDM</a> 则更进一步，不仅可以在 64 位的 Windows 中运行 DOS 程序，还可以运行 16 位的 Windows 程序。</p>

<p><a href="https://github.com/otya128/winevdm">https://github.com/otya128/winevdm</a></p>

<p>下图是使用 WineVDM 在 64 位 Windows 10 中运行 Windows 3.2 的计算器与时钟的截图：</p>

<p><img src="/assets/nostalgic/exe16_win10x64.png" alt="Windows 10 中运行 Windows 3.2 的计算器与时钟" /></p>

<p>这个 WineVDM 是 <a href="https://source.winehq.org/git/wine.git/tree/HEAD:/programs/winevdm">Wine 项目中同名程序</a>的增强版（在其 README 中也提到了），还可以运行 DOS 程序。</p>

<p>值得一提的是 WineVDM 本身是 32 位程序，不过能在 64 位 Windows 中正常运行。
既然是 32 位程序，那当然可以在 32 位环境运行了，只是对于 32 位 Microsoft Windows 来说并不需要，因为已经有 NTVDM 与 WOW 可以运行 DOS 程序及 16 位 Windows 程序。
但是在 <a href="https://www.reactos.org/">ReactOS</a> 中就能<a href="https://twitter.com/x86corez/status/1129394199273590784">派上用场了</a>：</p>

<p><img src="/assets/nostalgic/exe16_reactos.jpg" alt="ReactOS 中运行 16 Windows 程序" /></p>]]></content><author><name></name></author><category term="nostalgic" /><summary type="html"><![CDATA[相信好多人都知道，在基于 NT 的 32 位 Windows 中，可以通过 NTVDM 运行 DOS 程序，进而还可以通过 WOW 运行 16 位 Windows（Windows 1.x～3.x）程序。 但是在 64 位 Windows 中没有 NTVDM 与 WOW。]]></summary></entry><entry><title type="html">【译】【图文】标准化中的 WASI：在 web 之外运行 WebAssembly 的系统接口</title><link href="https://hltj.me/wasm/2019/04/04/standardizing-wasi.html" rel="alternate" type="text/html" title="【译】【图文】标准化中的 WASI：在 web 之外运行 WebAssembly 的系统接口" /><published>2019-04-04T04:52:27+00:00</published><updated>2019-04-04T04:52:27+00:00</updated><id>https://hltj.me/wasm/2019/04/04/standardizing-wasi</id><content type="html" xml:base="https://hltj.me/wasm/2019/04/04/standardizing-wasi.html"><![CDATA[<blockquote>
  <p>本文已获得翻译授权，译自 <a href="https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/">Standardizing WASI: A system interface to run WebAssembly outside the web</a>，由作者 <a href="https://twitter.com/linclark">Lin Clark</a> 于当地时间 2019-03-27 发布。</p>
</blockquote>

<p>今天（当地时间 2019-03-27），我们宣布开始进行一项新的标准化工作——WASI，WebAssembly 系统接口（WebAssembly System Interface）。</p>

<!--more-->

<p><strong>起因（Why）：</strong> 开发人员开始将 WebAssembly 向浏览器之外推进，因为它提供了一种快速、可扩展、安全的方式来在所有计算机上运行相同的代码。</p>

<p>但是我们尚未建立一个坚实的基础。
浏览器之外的代码需要一种与系统交互的方式——系统接口。
而 WebAssembly 平台还没有系统接口。</p>

<p><strong>事物（What）：</strong> WebAssembly 是概念机的汇编语言，而不是物理机的汇编语言。
这就是它可以在各种不同计算机体系结构上运行的原因。</p>

<p>正因为 WebAssembly 是概念机的汇编语言，所以 WebAssembly 需要一个概念操作系统的系统接口，而不是任何单一操作系统的系统接口。
这样，它就可以在所有不同操作系统中运行。</p>

<p>这就是 WASI——WebAssembly 平台的系统接口。</p>

<p>我们的目标是创建一个系统接口，它会成为 WebAssembly 的真正伴侣，并经受起时间的考验。
这意味着坚持 WebAssembly 的关键原则——可移植性与安全性。</p>

<p><strong>人物（Who）：</strong> 我们正在组建一个 WebAssembly 的一个子工作组，专注于 <a href="https://wasi.dev/">WASI</a> 标准化。
我们已经聚集了一些志同道合的合作伙伴，并且还在寻找更多伙伴加入。</p>

<p>以下是我们、我们的合作伙伴与支持者认为这很重要的一些原因：</p>

<h4 id="sean-whitemozilla-的首席研发官">Sean White，Mozilla 的首席研发官</h4>
<p>“WebAssembly 已经在改变 web 给人们带来新的引人入胜的内容的方式，并使开发者与创作者能在 web 上尽显身手。
到目前为止，已经通过浏览器实现了，不过有了 WASI，我们可以将 WebAssembly 与 web 的益处传递给更多用户、更多场合、更多设备，以及作为更多体验的一部分。”</p>

<h4 id="tyler-mcmullenfastly-的-cto">Tyler McMullen，Fastly 的 CTO</h4>
<p>“我们将 WebAssembly 带到浏览器之外，在我们的边缘云中作为快速、安全地执行代码的平台。
虽然我们的边缘与浏览器之间存在环境差异，但是 WASI 意味着 WebAssembly 开发者无需将代码移植到每个不同的平台。”</p>

<h4 id="myles-borinsnode-技术指导委员会主任">Myles Borins，Node 技术指导委员会主任</h4>
<p>“WebAssembly 可以解决 Node 中最大的问题之一——如何获得接近原生速度，并像使用原生模块一样复用其他语言（如 C 与 C++）编写的代码，同时仍保持可移植性与安全性。
标准化这个系统接口是实现这一目标的第一步。”</p>

<h4 id="laurie-vossnpm-的联合创始人">Laurie Voss，npm 的联合创始人</h4>
<p>“npm 极为感兴趣的是，WebAssembly 潜在具有扩展 npm 生态系统、并同时极大地简化在服务端 JavaScript 应用程序中运行原生代码过程的能力。
我们期待这个过程的结果。”</p>

<p>所以说这是个大新闻！🎉</p>

<p>WASI 目前有三份实现:</p>

<ul>
  <li><a href="https://github.com/CraneStation/wasmtime">wasmtime</a>，Mozilla 的 WebAssembly 运行时</li>
  <li><a href="https://www.fastly.com/blog/announcing-lucet-fastly-native-webassembly-compiler-runtime">Lucet</a>，Fastly 的 WebAssembly 运行时</li>
  <li><a href="https://wasi.dev/polyfill/">浏览器垫片（polyfill）</a></li>
</ul>

<p>（如果你能访问 YouTube）可以在这个视频中看个 WASI 实战：</p>

<iframe width="560" height="315" src="https://www.youtube.com/embed/ggtEJC0Jv8A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>如果你希望了解关于这个系统接口应该如何工作的提案的更多信息，请继续阅读。</p>

<h3 id="系统接口是什么">系统接口是什么？</h3>
<p>很多人说像 C 这样的语言可以直接访问系统资源。
但事实并非如此。</p>

<p>这些语言无法直接在大多数系统上进行打开或创建文件等操作。
为什么不能呢？</p>

<p>因为这些系统资源（诸如文件、内存以及网络连接）对于稳定性与安全性来说太重要了。</p>

<p>如果一个程序无意中搅乱了另外一个程序的资源，那么它可能会使另一个程序崩溃。
更糟的是，如果一个程序（或用户）故意干扰另一个程序的资源，那么它可能会窃取敏感数据。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/01-01_crash-data-leak-1.png" alt="表示崩溃的皱着眉头的终端窗口，以及表示数据泄露的带有损坏的锁的文件" /></p>

<p>因此，我们需要一种方式来控制哪些程序与用户可以访问哪些资源。
人们很早就发现了这一点，并想出了一个提供这种控制的方式：保护环安全。</p>

<p>有了保护环安全，操作系统基本上在系统资源外围设置了保护屏障。
这就是内核。
内核是唯一可以进行创建新文件、打开文件或者打开网络连接等操作的地方。</p>

<p>用户的程序在称之为用户模式的内核以外运行。
如果程序想做打开文件这样的事，它必须请求内核为它打开。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/01-02-protection-ring-sec-1.png" alt="左侧是一个文件目录结构，中间有一个包含操作系统内核的保护屏障，右侧是一个敲门访问的应用程序" /></p>

<p>这就是系统调用的概念所在。
当程序需要让内核执行这其中某一项操作时，它会使用系统调用来请求。
这让内核有机会找出是哪个用户在请求。
然后就可以在打开文件之前分辨该用户是否有权访问该文件。</p>

<p>在大多数设备上，这是代码可以访问系统资源的唯一方式——通过系统调用。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/01-03-syscall-1.png" alt="请求操作系统将数据放入已打开文件的应用程序" /></p>

<p>操作系统让系统调用可用。
但是如果每个操作系统都有自身的系统调用，那岂不是需要为每个操作系统编写不同版本的代码？
幸运的是，并不用。</p>

<p>这个问题是如何解决的呢？——抽象。</p>

<p>大多数语言都提供了标准库。
在编码时，程序员并不需要知道他们所面向的系统。
他们只是使用相应接口。</p>

<p>然后在编译时，工具链会根据所面向的目标系统来选择使用该接口的哪个实现。
这个实现会使用操作系统 API 中的函数，因此它是平台相关的。</p>

<p>这就是系统接口的用武之地。
例如，为 Windows 计算机编译的 <code class="language-plaintext highlighter-rouge">printf</code> 可以使用 Windows API 与该计算机进行交互。
而为 Mac 或者 Linux 编译，则会改用 POSIX。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/02-01-implementations-1.png" alt="putc 的接口会翻译为为两种不同的实现，一种使用 POSIX 实现，另一种使用 Windows API 实现" /></p>

<p>然而这却给 WebAssembly 带来了一个问题。</p>

<p>对于 WebAssembly 来说，即使在编译时也无从知晓所面向的是哪种操作系统。
因此，无法在标准库的 WebAssembly 实现中使用任何单一操作系统的系统接口。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/02-02-implementations-1.png" alt="putc 的空实现" /></p>

<p>我之前说过 WebAssembly 是<a href="https://hacks.mozilla.org/2017/02/creating-and-working-with-webassembly-modules/">一种概念机的汇编语言</a>，而不是真实计算机的汇编语言。
同样，WebAssembly 也需要一套概念操作系统（而不是真实操作系统）的系统接口。</p>

<p>不过已经存在可以在浏览器之外运行 WebAssembly 的运行时了，即便没有这个系统接口。
他们是怎么做到的呢？我们来看一看。</p>

<h3 id="如今-webassembly-是如何在浏览器之外运行的">如今 WebAssembly 是如何在浏览器之外运行的？</h3>
<p>生成 WebAssembly 的第一个工具是 Emscripten。
它在 web 上模拟一个特定操作系统的系统接口 POSIX。
这意味着程序员可以使用 C 标准库（libc）中的函数。</p>

<p>为此，Emscripten 创建了自己的 libc 实现。
这个实现分为两部分——一部分编译成 WebAssembly 模块，另一部分用 JS 胶水代码实现。
这个 JS 胶水层会调用浏览器，进而与操作系统交互。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/03-01-emscripten-1.png" alt="一个 Rube Goldberg 机展示了一个调用如何从 WebAssembly 模块进入到 Emscripten 的 JS 胶水代码中，再进入到浏览器中，再进入到内核中" /></p>

<p>大多数早期的 WebAssembly 代码都是使用 Emscripten 编译的。
因此，当人们开始想在没有浏览器的情况下运行 WebAssembly 代码时，他们会从让 Emscripten 所编译的代码能运行入手。</p>

<p>于是，这些运行时需要为 JS 胶水代码中的所有函数创建自身的实现。</p>

<p>不过，这里有个问题。
这个 JS 胶水代码所提供的接口并没有设计成标准接口，甚至并非面向公众的接口。
这并不是它所解决的问题。</p>

<p>例如，对于一个在设计成公开接口的 API 中名为 <code class="language-plaintext highlighter-rouge">read</code> 的函数，其 JS 胶水代码使用的是 <code class="language-plaintext highlighter-rouge">_system3(which, varargs)</code>。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/03-02-system3-1.png" alt="一个清晰的 read 接口，对比一个令人困惑的 system3" /></p>

<p>第一个参数 <code class="language-plaintext highlighter-rouge">which</code> 是一个整数，它始终与名称中的数字相同（在本例中是 3）。</p>

<p>第二个参数 <code class="language-plaintext highlighter-rouge">varargs</code> 是用到的参数。
它之所以称为 <code class="language-plaintext highlighter-rouge">varargs</code>，是因为可以有可变数量的参数。
但是 WebAssembly 并没有提供将可变数量参数传给函数的方式。
于是，这些参数通过线性内存传递。
这不是类型安全的做法，而且也比使用寄存器传递参数（如果可能的话）慢。</p>

<p>对于在浏览器中运行 Emscripten 来说，这没有问题。
但是现在运行时将其视为事实上的标准，实现了自身版本的 JS 胶水代码。
他们是在模拟 POSIX 仿真层的内部细节。</p>

<p>这意味着他们正在重新实现那些对于 Emscripten 的约束有意义的选择（例如将参数作为堆中值传递），即便这些约束并不适于他们的环境。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/03-03-emulation-1.png" alt="一个更复杂的 Rube Goldberg 机，其中 JS 胶水层与浏览器都是由 WebAssembly 运行时模拟的" /></p>

<p>如果我们要构建一个持续数十年的 WebAssembly 生态系统，我们就需要坚实的基础。
这意味着我们的事实标准不能是仿真的仿真。</p>

<p>不过我们应该采用什么原则呢？</p>

<h3 id="webassembly-系统接口需要坚持什么原则">WebAssembly 系统接口需要坚持什么原则？</h3>
<p>有两项重要的原则已经融入到 WebAssembly 中：</p>

<ul>
  <li>可移植性</li>
  <li>安全性</li>
</ul>

<p>当我们转向浏览器之外的应用场景时，我们需要坚持这些关键原则。</p>

<p>实际上，POSIX 与 Unix 的安全访问控制方式并没有帮我们达到目的。
我们来看下它们的不足之处。</p>

<h4 id="可移植性">可移植性</h4>
<p>POSIX 提供了源代码级的可移植性。
相同的源代码可以与不同版本的 libc 一起编译来面向不同的计算机。</p>

<p><img src="https://hacks.mozilla.org/files/2019/03/04-01-portability-1-768x576.png" alt="一个 C 语言源文件编译成多个二进制文件" /></p>

<p>但是 WebAssembly 需要超越它一步。
我们需要能够编译一次就能在一系列不同的计算机上运行。
我们需要可移植的二进制文件。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/04-02-portability-1.png" alt="一个 C 语言源文件编译成单个二进制文件" /></p>

<p>这种可移植性让用户分发代码更容易。</p>

<p>例如，Node 的原生模块如果是用 WebAssembly 编写的，那么当用户安装带有原生模块的应用时就不需要运行 node-gyp 了，开发人员也无需配置并分发几十个二进制文件了。</p>

<h4 id="安全性">安全性</h4>
<p>当一行代码请求操作系统执行某些输入或输出时，操作系统需要确定该代码所请求的操作是否安全。</p>

<p>操作系统通常使用基于所有权与组的访问控制来处理这个问题。</p>

<p>例如，程序可能会请求操作系统打开一个文件。
一个用户具有他有权访问的特定一组文件。</p>

<p>当该用户启动程序时，该程序代表用户运行。
如果这个用户有权访问某文件（要么因为他是所有者，要么因为他在有权访问的组里），那么该程序也能访问。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/04-03-access-control-1.png" alt="请求打开与其所执行操作相关的文件的应用程序" /></p>

<p>这在用户之间提供了保护。
在早期操作系统开发出来时很有意义。
系统通常是多用户的，而管理员控制安装什么软件。
所以最突出的威胁是其他用户偷看你的文件。</p>

<p>情况已经变了。
现在系统通常是单个用户，但是他们会运行引入了许多未知可信度的其他第三方代码的代码。
现在最大的威胁是你自己运行的代码会对你不利。</p>

<p>例如，假设你在应用程序中使用的库来了一个新的维护者（在开源项目中经常发生）。
该维护者可能会对你的兴趣很上心……也可能是个坏人。
如果这些代码有权在你的系统上做任何事（例如，打开你的任何文件并通过网络发送出去），那么其代码会造成很大的损害。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/04-04-bitcoin-1.png" alt="一个恶意应用程序请求访问用户的比特币钱包及打开网络连接" /></p>

<p>这就是为什么使用可以直接与系统交互的第三方库可能是危险的。</p>

<p>WebAssembly 实现安全性的方式与此不同。
WebAssembly 采用了沙箱。</p>

<p>这意味着代码不能直接与操作系统交互。
那么它是如何利用系统资源的呢？
宿主机（可能是浏览器，也可能是 wasm 运行时）将函数放入代码可以使用的沙箱中。</p>

<p>这意味着宿主机可以逐一限制每个程序可以做什么。
它并不是仅仅让程序代表用户、使用该用户的完整权限调用任何系统调用。</p>

<p>只是拥有沙箱机制并不会使系统本身变安全（宿主机仍然可以将所有能力都放入到沙箱中，若是这种情况则并没有变好），不过它至少让宿主机能够选择创建更安全的系统。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/04-05-sandbox-1.png" alt="将安全函数放入到沙箱中的运行时以及一个应用" /></p>

<p>在我们设计的任何系统接口中，我们都需要坚持这两项原则。
可移植性让开发与分发软件更容易，而为宿主机提供保护自身或用户的工具更是绝对必需。</p>

<h3 id="这个系统接口应该是什么样的">这个系统接口应该是什么样的？</h3>
<p>鉴于这两项关键原则，WebAssembly 系统接口应该设计成什么样的？</p>

<p>这正是我们要通过标准化过程得出的成果。
不过我们确实有个提案要启动：</p>

<ul>
  <li>创建模块化的一组标准接口</li>
  <li>开始标准化最基本的模块 wasi-core</li>
</ul>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/05-01-wasi-1.png" alt="包含在 WASI 标准成果中的多个模块" /></p>

<p>wasi-core 里会有什么？</p>

<p>wasi-core 会包含所有程序都需要的基本接口。
它会覆盖与 POSIX 近乎相同的领域，包括诸如文件、网络连接、时钟以及随机数。</p>

<p>并且其中很多会采用与 POSIX 非常类似的方式。
例如，它会使用 POSIX 的面向文件的方式，其中有诸如 open、close、read 以及 write 这样的系统调用，而所有其他内容基本都是在此之上提供的扩充。</p>

<p>不过 wasi-core 并不会覆盖所有 POSIX 的内容。
例如，进程概念并没有清晰映射到 WebAssembly 上。
更进一步，让每个 WebAssembly 引擎都需要支持像 <code class="language-plaintext highlighter-rouge">fork</code> 这样的进程操作并无意义。
当然我们也希望标准化 <code class="language-plaintext highlighter-rouge">fork</code> 成为可能。</p>

<p>这就模块化方式的用武之地。
通过这种方式，我们可以获得良好的标准化覆盖率，同时仍然让一些平台能够只使用对其有意义的 WASI 部分。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/05-02-wasi-1.png" alt="填充了标准化中可能区域的模块（诸如进程、传感器、3D 图形等）" /></p>

<p>像 Rust 这样的语言会直接在其标准库中使用 wasi-core。
例如，Rust 的 <code class="language-plaintext highlighter-rouge">open</code> 在编译到 WebAssembly 时会通过调用 <code class="language-plaintext highlighter-rouge">__wasi_path_open</code> 来实现。</p>

<p>对于 C 与 C++，我们创建了一个 <a href="https://github.com/CraneStation/wasi-sysroot">wasi-sysroot</a>，它根据 wasi-core 实现了 libc。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/05-03-open-imps-1.png" alt="使用 openat 与 WASI 的 Rust 与 C 语言实现" /></p>

<p>我们期望像 Clang 这样的编译器准备好与 WASI API 交互，并完成像 Rust 编译器与 Emscripten 这样的工具链，将 WASI 作为其系统实现的一部分。</p>

<p>用户代码如何调用这些 WASI 函数？</p>

<p>运行用户代码的运行时会将 wasi-core 函数作为导入传入。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/05-04-imports-1.png" alt="将一个导入对象放入沙箱中的运行时" /></p>

<p>这为我们提供了可移植性，因为每个宿主机都可以有专为其平台（从像 Mozilla 的 wasmtime 与 Fastly 的 Lucet，到 Node 乃至浏览器）编写的自身的 wasi-core 实现。</p>

<p>它还为我们提供了沙箱，因为宿主机可以逐个程序选择哪些 wasi-core 函数可以传入（即允许哪些系统调用）。
这保持了安全性。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/05-05-sec-port-2.png" alt="将其自身的 wasi_fd_open 实现传到沙箱中的三个运行时——wastime、Node 以及浏览器" /></p>

<p>WASI 为我们提供了进一步扩展这种安全性的方式。
它从基于能力的安全性中引入了更多概念。</p>

<p>传统方式中，如果代码需要打开一个文件，那么它会用一个路径名字符串调用 <code class="language-plaintext highlighter-rouge">open</code>。
然后操作系统检验该代码是否有权限（基于启动该程序的用户）。</p>

<p>对于 WASI，调用一个需要访问文件的函数必须传入一个附加了权限的文件描述符。
可以是用于该文件自身的描述符，也可以是用于包含该文件的目录的描述符。</p>

<p>这样，就不会有随机请求打开 <code class="language-plaintext highlighter-rouge">/etc/passwd</code> 的代码。
相反，代码只能对传给它的目录进行操作。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/05-06-openat-path-1.png" alt="沙箱中的两个恶意应用。左边的使用 POSIX 并且成功打开了一个它本不应访问的文件。另一个使用 WASI，而它无法打开该文件。" /></p>

<p>这让为沙箱中的代码安全地提供更多不同系统调用的访问控制成为可能——因为这些系统调用的能力是受限的。</p>

<p>并且这是发生在逐个模块基础上的。
默认情况下，模块没有对任何文件描述符的访问权限。
但是如果一个模块中的代码拥有文件描述符，那么它可以选择将该文件描述符传给其他模块中它所调用的函数。
也可以创建更受限版本的文件描述符来传给其他函数。</p>

<p>因此运行时可以将应用可用的文件描述符传到顶层代码，然后这些文件描述符就可以按需传播到系统的其余部分。</p>

<p><img src="https://hacks.mozilla.org/wp-content/uploads/2019/03/05-07-file-perms-1.png" alt="运行时将一个目录传给应用，而后该应用将一个文件传给一个函数" /></p>

<p>这让 WebAssembly 更接近最小权限原则——一个模块只能访问完成其工作所需的确切资源。</p>

<p>这些概念来自于能力导向系统（capability-oriented systems），例如 CloudABI 与 Capsicum。
能力导向系统的一个问题是通常很难向它们移植代码。
但我们认为这个问题可以解决。</p>

<p>如果代码已经使用文件相对路径调用 <code class="language-plaintext highlighter-rouge">openat</code>，那么只要编译该代码就能用。</p>

<p>如果代码使用的是 <code class="language-plaintext highlighter-rouge">open</code> 并且迁移到 <code class="language-plaintext highlighter-rouge">openat</code> 风格的前期开销太高，WASI 可以提供一个增量解决方案。
使用 <a href="https://github.com/musec/libpreopen">libpreopen</a>，可以为应用程序创建一个合理需要访问的文件路径列表。
然后就可以使用 <code class="language-plaintext highlighter-rouge">open</code> 了，不过只能使用列表中的路径。</p>

<h3 id="下一步呢">下一步呢？</h3>
<p>我们认为 wasi-core 是一个良好的开端。
它保持了 WebAssembly 的可移植性与安全性，为生态系统提供了坚实的基础。</p>

<p>不过，在 wasi-core 完全标准化之后，我们还需要解决一些问题。这些问题包括：</p>

<ul>
  <li>异步 I/O</li>
  <li>文件监视</li>
  <li>文件锁定</li>
</ul>

<p>这只是开始，所以如果你有解决这些问题的想法，就请<a href="https://wasi.dev/">加入我们</a>吧！</p>

<blockquote>
  <h4 id="关于英文原文作者-lin-clark">关于英文原文作者 <a href="https://twitter.com/linclark">Lin Clark</a></h4>
  <p>Lin 在 Mozilla 的高级开发部门工作，专注于 Rust 与 WebAssembly。</p>
  <ul>
    <li><a href="https://twitter.com/linclark">https://twitter.com/linclark</a></li>
    <li><a href="https://twitter.com/linclark">@linclark</a></li>
  </ul>

  <p><a href="https://hacks.mozilla.org/author/lclarkmozilla-com/">Lin Clark 的更多文章……</a></p>
</blockquote>]]></content><author><name></name></author><category term="wasm" /><summary type="html"><![CDATA[本文已获得翻译授权，译自 Standardizing WASI: A system interface to run WebAssembly outside the web，由作者 Lin Clark 于当地时间 2019-03-27 发布。 今天（当地时间 2019-03-27），我们宣布开始进行一项新的标准化工作——WASI，WebAssembly 系统接口（WebAssembly System Interface）。]]></summary></entry><entry><title type="html">庆祝 Ktor 1.0 发布，分享 JetBrains 日讲稿及代码</title><link href="https://hltj.me/kotlin/2018/11/22/ktorcn-update-jetbrains-day-slides.html" rel="alternate" type="text/html" title="庆祝 Ktor 1.0 发布，分享 JetBrains 日讲稿及代码" /><published>2018-11-22T15:58:29+00:00</published><updated>2018-11-22T15:58:29+00:00</updated><id>https://hltj.me/kotlin/2018/11/22/ktorcn-update-jetbrains-day-slides</id><content type="html" xml:base="https://hltj.me/kotlin/2018/11/22/ktorcn-update-jetbrains-day-slides.html"><![CDATA[<p>非常值得庆祝的是，<a href="https://www.kotliner.cn/2018/11/ktor-1-0/">Ktor 1.0 正式发布了</a>，<a href="https://ktor.kotlincn.net/">Ktor 中文站</a>也已更新。</p>

<p><img src="/assets/kotlin/ktorcn.png" alt="" /></p>

<p>Ktor 是 <a href="https://www.jetbrains.com/">JetBrains</a> 官方出品的互联应用框架。
使用该框架非常易于开发异步的服务器与客户端，并且能够充分利用 <a href="https://www.kotlincn.net/">Kotlin</a> 以及<a href="https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html">协程</a>的优势。</p>

<!--more-->

<p><a href="https://ktor.kotlincn.net/">Ktor 中文站</a>是<a href="https://ktor.io/">官方英文站</a>的中文翻译（目前还在翻译中，欢迎组团一起）。
初学者可以从<a href="https://ktor.kotlincn.net/quickstart/index.html">快速入门</a>入手来学习与了解 Ktor，这一章大多数内容均已翻译。</p>

<p>上周六，有幸在 <a href="https://info.jetbrains.com/jetbrains-day-beijing-2018.html">JetBrains 开发者日</a>上分享了《Ktor——Kotlin 多平台异步 Web 框架实践》 ，这两天也把讲稿及相关 demo 整理了下。</p>

<p>讲稿可在这里下载：</p>

<blockquote>
  <p>链接： <a href="https://share.weiyun.com/5UqjtTc">https://share.weiyun.com/5UqjtTc</a><br />
密码： eauq37</p>

  <p>我猜你还想看 Benny 分享的讲稿，传送门在这里：<a href="https://www.bennyhuo.com/2018/11/18/2018-JetBrains-Day/">2018 JetBrains 开发者大会见闻</a></p>
</blockquote>

<p>这份讲稿比当天用的那份要新一些（其中的截图也能看出是 11 月 20 日的），补充了当场提到但没有在讲稿中列出的 Ktor 适用场景：
多平台项目，同时开发客户端与服务端，比如同时开发 WebSocket 或者直接套接字通讯的客户端与服务器。</p>

<p>CallID 与 Call Logging MDC 的 demo 在这里：</p>

<p><a href="https://github.com/hltj/ktor-callid-demo">https://github.com/hltj/ktor-callid-demo</a></p>

<p>接口聚合服务 demo 在这里：</p>

<p><a href="https://github.com/hltj/kaggregator-demo">https://github.com/hltj/kaggregator-demo</a></p>

<p>最后出场的这个是原打算在分享中讲的开源缩略图服务 Kthumbor，终于完成了第一个可用版。服务框架使用 Ktor，100% Kotlin 开发，见下图：</p>

<p><img src="/assets/kotlin/kthumbor-github.png" alt="" /></p>

<p>另外，在 Kthumbor 项目中采用了测试驱动开发的方式（其中测试框架使用的是 <a href="https://github.com/kotlintest/kotlintest">KotlinTest</a>），先写测试用例后写实现。
目前只实现了最简单的生成指定宽高内的缩略图的功能，后续还会实现放大、剪裁等功能，最终会实现一个生产级可用的缩略图服务。</p>

<p>Kthumbor 的源代码在这里：</p>

<p><a href="https://github.com/hltj/kthumbor">https://github.com/hltj/kthumbor</a></p>

<p>欢迎反馈与交流。
需要说明的一点是，我并不想做纯雷锋，该项目采用 <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL-3.0</a> 协议发布，因此可以用于商业目的，但是任何修改都需要以同样协议（AGPL-3.0）开源出来。</p>

<p>关于分享中讲到的点以及 Kthumbor 项目，有机会再展开来看。</p>]]></content><author><name></name></author><category term="kotlin" /><summary type="html"><![CDATA[非常值得庆祝的是，Ktor 1.0 正式发布了，Ktor 中文站也已更新。 Ktor 是 JetBrains 官方出品的互联应用框架。 使用该框架非常易于开发异步的服务器与客户端，并且能够充分利用 Kotlin 以及协程的优势。]]></summary></entry><entry><title type="html">体验 Java 9（2）：更新与非重大改动</title><link href="https://hltj.me/java/2018/04/02/experience-java9-update-minors.html" rel="alternate" type="text/html" title="体验 Java 9（2）：更新与非重大改动" /><published>2018-04-02T10:07:29+00:00</published><updated>2018-04-02T10:07:29+00:00</updated><id>https://hltj.me/java/2018/04/02/experience-java9-update-minors</id><content type="html" xml:base="https://hltj.me/java/2018/04/02/experience-java9-update-minors.html"><![CDATA[<p>本篇介绍 Java 9 更新以及一些非重大改动。</p>

<h2 id="更新">更新</h2>
<p>Java 9 已经正式发布半年多了。这期间不仅 Java 9 发布了更新，就连 Java 10 也已正式发布。<a href="https://hltj.me/java/2017/09/22/experience-java9-lombok.html">上一篇</a>中提到的工具也都有更新。其中 IDEA 新版改进了不少 Java 9 支持，Eclipse 新版内置了 Java 9 支持（不再需要 Beta 版插件，但可能需要重装，不能直接从旧版升级）。最值得一提的是 lombok 1.16.20 发布。
<!--more--></p>
<blockquote>
  <p>2018-12-28 注：Java 9 与 Java 10 都是短期版本，目前的长期支持（LTS）版是 Java 11。
而 Java 10 与 Java 11 自身的改动都不多，可以说 Java 11 相对于上一个 LTS 版（Java 8）的新特性很多都是 Java 9 引入的。因此本文仍有参考价值。
Oracle 已经不提供 JDK 9 下载，一些示例可以直接使用 Java 11 来演练，或者用<a href="https://jdk.java.net/archive/">历史版本的 OpenJDK</a>（不推荐）。</p>
</blockquote>

<h3 id="lombok-兼容性改善">Lombok 兼容性改善</h3>
<p>期待已久的 lombok 1.16.20 终于在 2018-01-09 正式发布。这一版解决了<a href="https://hltj.me/java/2017/09/22/experience-java9-lombok.html">上一篇</a>中提到的大多数兼容问题。具体如下：</p>
<ol>
  <li>可以与 Gradle 4.1 及以上版本配合使用。
  这点很重要，因为 Gradle 4.2 及以上版本才能支持 Java 9.0.1/9.0.4。</li>
  <li>改进 IDE 支持。
  将上一篇中提到的 <a href="https://github.com/hltj/java9demo/tree/master/lombok">Lombok 示例</a> 中的 Lombok 版本升级到 1.16.20 后，在新版 IDEA 中无论创建普通项目、Maven 项目还是 Gradle 项目，都能正常编辑与运行；在新版 Eclipse 中，Maven、Gradle 项目正常，普通项目还是有问题；在新版 NetBeans 开发版中普通项目、Maven 项目均正常，Gradle 项目在 JDK 9 环境正常，JDK 9.0.1/9.0.4 环境有问题。</li>
</ol>

<p>结合一篇的内容，汇总 Lombok 1.16.18 到 1.16.20 兼容情况变化如下表所示：</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Java 9</th>
      <th>Java 9.0.1/9.0.4</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Maven 3.3.9-3.5.2</td>
      <td>✓ ⇨ ✓</td>
      <td>✓ ⇨ ✓</td>
    </tr>
    <tr>
      <td>Gradle 3.5rc-4.0.2</td>
      <td>✓ ⇨ ✓</td>
      <td>✗ ⇨ ✗</td>
    </tr>
    <tr>
      <td>Gradle 4.2+</td>
      <td>✘ ⇨ ✔</td>
      <td>✘ ⇨ ✔</td>
    </tr>
    <tr>
      <td>IDEA 2018.1 [普通/Maven/Gradle]</td>
      <td>✘ ⇨ ✔</td>
      <td>✘ ⇨ ✔</td>
    </tr>
    <tr>
      <td>Eclipse 4.7.3 [Maven/Gradle]</td>
      <td>✘ ⇨ ✔</td>
      <td>✘ ⇨ ✔</td>
    </tr>
    <tr>
      <td>Eclipse 4.7.3 [普通]</td>
      <td>✗ ⇨ ✗</td>
      <td>✗ ⇨ ✗</td>
    </tr>
    <tr>
      <td>NetBeans 开发版 [普通/Maven]</td>
      <td>✘ ⇨ ✔</td>
      <td>✘ ⇨ ✔</td>
    </tr>
    <tr>
      <td>NetBeans 开发版 [Gradle]</td>
      <td>✘ ⇨ ✔</td>
      <td>✗ ⇨ ✗</td>
    </tr>
  </tbody>
</table>

<p>表中箭头（⇨）之前表示 Lombok 1.16.18 对工具的兼容情况，箭头之后表示 Lombok 1.16.20 的兼容情况。</p>

<h3 id="java-9-更新与版本号模式">Java 9 更新与版本号模式</h3>
<p>Java 9 的安全更新版 9.0.1、9.0.4 分别于 2017-10-17、2018-01-16 发布。可以看出这里的 Java 版本号与以往 <code class="language-plaintext highlighter-rouge">1.8.0_162</code> 这种版本号差异很明显。Java 9 的版本号模式（version schema）为：</p>

<pre><code class="language-txt">$MAJOR.$MINOR.$SECURITY
</code></pre>

<p>即“主版本号.次版本号.安全级别”，这样很容易看出 9.0.1 与 9.0.4 都是安全修复版本，应该升级。</p>

<p>Java 9 还增加了用于获取版本号的 API，以前取 Java 版本号主要通过系统属性来取，现在可以直接调用 API 了。<code class="language-plaintext highlighter-rouge">java.lang.Runtime</code> 新添了一个静态方法 <code class="language-plaintext highlighter-rouge">version()</code>，其返回值类型为 <code class="language-plaintext highlighter-rouge">Runtime.Version</code>，通过它可以很方便地获取不同格式的版本号。例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">()</span>
<span class="err">$</span><span class="mi">1</span> <span class="o">==&gt;</span> <span class="mf">9.0</span><span class="o">.</span><span class="mi">4</span><span class="o">+</span><span class="mi">11</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">().</span><span class="na">version</span><span class="o">()</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="o">[</span><span class="mi">9</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">4</span><span class="o">]</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">().</span><span class="na">major</span><span class="o">()</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="mi">9</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">().</span><span class="na">security</span><span class="o">()</span>
<span class="err">$</span><span class="mi">4</span> <span class="o">==&gt;</span> <span class="mi">4</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">().</span><span class="na">build</span><span class="o">()</span>
<span class="err">$</span><span class="mi">5</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">[</span><span class="mi">11</span><span class="o">]</span>
</code></pre></div></div>

<p>值得补充的是，Java 10 再次修改版本号模式为：</p>

<pre><code class="language-txt">$FEATURE.$INTERIM.$UPDATE.$EMERG
</code></pre>

<p>即“特性版本号.中间版本号.更新版本号.紧急修复版本号”。 同时 <code class="language-plaintext highlighter-rouge">Runtime.Version</code> 的相应方法 <code class="language-plaintext highlighter-rouge">major()</code>、<code class="language-plaintext highlighter-rouge">minor()</code>、<code class="language-plaintext highlighter-rouge">security()</code> 也被弃用，建议分别使用新增的 <code class="language-plaintext highlighter-rouge">feature()</code>、<code class="language-plaintext highlighter-rouge">interim()</code>、<code class="language-plaintext highlighter-rouge">update()</code> 方法取代，另外新增 <code class="language-plaintext highlighter-rouge">patch()</code> 方法用于取紧急修复版本号。例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">()</span>
<span class="err">$</span><span class="mi">1</span> <span class="o">==&gt;</span> <span class="mi">10</span><span class="o">+</span><span class="mi">46</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">().</span><span class="na">feature</span><span class="o">()</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="mi">10</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Runtime</span><span class="o">.</span><span class="na">version</span><span class="o">().</span><span class="na">build</span><span class="o">()</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">[</span><span class="mi">46</span><span class="o">]</span>
</code></pre></div></div>

<p>当然 Java 10 与 Java 9 的前三位版本号的含义并不完全相同。特别是自 Java 10 起将严格按照 6 个月的节奏发版，即今年三月份发布 Java 10、九月份发布 Java 11、明年三月份发布 Java 12……以此类推。</p>

<p>关于 Java 9 与 Java 10 版本号模式的更多内容请参见 <a href="http://openjdk.java.net/jeps/223">JEP 223: New Version-String Scheme</a> 以及 <a href="http://openjdk.java.net/jeps/322">JEP 322: Time-Based Release Versioning</a>。</p>

<h2 id="非重大改动">非重大改动</h2>

<p>接下来介绍以下内容：</p>

<ul>
  <li>下划线成为保留字</li>
  <li>try-with-resource 改进</li>
  <li>InputStream 改进</li>
  <li>接口的私有默认方法</li>
  <li>集合的工厂方法</li>
  <li>Optional 改进</li>
  <li>Stream 改进</li>
  <li>进程 API 改进</li>
</ul>

<p>如果已经了解过就不需要再往下看了。</p>

<h3 id="下划线成为保留字">下划线成为保留字</h3>
<p>单独一个下划线（<code class="language-plaintext highlighter-rouge">_</code>）作为标识符在 Java 8 中就已经弃用，但是仍然可以通过编译。例如下述代码：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Demo0</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="kt">int</span> <span class="n">_</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">_</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>在 Java 8 环境中编译会出现以下警告：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>javac Demo0.java 
Demo0.java:3: warning: <span class="s1">'_'</span> used as an identifier
        int _ <span class="o">=</span> 1<span class="p">;</span>
            ^
  <span class="o">(</span>use of <span class="s1">'_'</span> as an identifier might not be supported <span class="k">in </span>releases after Java SE 8<span class="o">)</span>
Demo0.java:4: warning: <span class="s1">'_'</span> used as an identifier
        System.out.println<span class="o">(</span>_<span class="o">)</span><span class="p">;</span>
                           ^
  <span class="o">(</span>use of <span class="s1">'_'</span> as an identifier might not be supported <span class="k">in </span>releases after Java SE 8<span class="o">)</span>
2 warnings
<span class="nv">$ </span>java Demo0
1
</code></pre></div></div>

<p>而在 Java 9 环境中则不能通过编译：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>javac Demo0.java
Demo0.java:3: error: as of release 9, <span class="s1">'_'</span> is a keyword, and may not be used as an identifier
        int _ <span class="o">=</span> 1<span class="p">;</span>
            ^
Demo0.java:4: error: as of release 9, <span class="s1">'_'</span> is a keyword, and may not be used as an identifier
        System.out.println<span class="o">(</span>_<span class="o">)</span><span class="p">;</span>
                           ^
2 errors
</code></pre></div></div>

<p>在 JShell 中使用也会出现同样的报错：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">String</span> <span class="n">_</span> <span class="o">=</span> <span class="s">"foo"</span><span class="o">;</span>
<span class="o">|</span>  <span class="nl">Error:</span>
<span class="o">|</span>  <span class="n">as</span> <span class="n">of</span> <span class="n">release</span> <span class="mi">9</span><span class="o">,</span> <span class="sc">'_'</span> <span class="n">is</span> <span class="n">a</span> <span class="n">keyword</span><span class="o">,</span> <span class="n">and</span> <span class="n">may</span> <span class="n">not</span> <span class="n">be</span> <span class="n">used</span> <span class="n">as</span> <span class="n">an</span> <span class="n">identifier</span>
<span class="o">|</span>  <span class="nc">String</span> <span class="n">_</span> <span class="o">=</span> <span class="s">"foo"</span><span class="o">;</span>
<span class="o">|</span>         <span class="o">^</span>
</code></pre></div></div>

<p>这是因为 <code class="language-plaintext highlighter-rouge">_</code> 已经成为保留字，在未来的 Java 版本中有可能用作占位符，类似 Scala 的模式匹配语法或者 Kotlin 的解构语法中的占位符。</p>

<h3 id="try-with-resource-改进">try-with-resource 改进</h3>

<p>Java 7 引入了 try-with-resource 语法，对于支持自动清理的资源（实现了 <code class="language-plaintext highlighter-rouge">AutoCloseable</code> 接口的类型），将引用创建的语句放在 try 与左花括号之间圆括号中，就可以执行自动清理。例如</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="o">(</span><span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileReader</span><span class="o">(</span><span class="n">path</span><span class="o">)))</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<p>对于既有引用，在 Java 7/8 中需要引入临时变量，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileReader</span><span class="o">(</span><span class="n">path</span><span class="o">));</span>
<span class="k">try</span> <span class="o">(</span><span class="nc">BufferedReader</span> <span class="n">br1</span> <span class="o">=</span> <span class="n">br</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">br1</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<p>而在 Java 9 中，使用既有 final（或相当于 final）的可自动清理资源引用时无需引入临时变量。上述代码可以简化为：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">BufferedReader</span> <span class="n">br</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">BufferedReader</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileReader</span><span class="o">(</span><span class="n">path</span><span class="o">));</span>
<span class="k">try</span> <span class="o">(</span><span class="n">br</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">br</span><span class="o">.</span><span class="na">readLine</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="inputstream-改进"><code class="language-plaintext highlighter-rouge">InputStream</code> 改进</h3>
<p>Java 9 的 <code class="language-plaintext highlighter-rouge">InputStream</code> 新添了一个 <code class="language-plaintext highlighter-rouge">transferTo()</code> 方法，可以从输入流读取并写到输出流。例如，在 JShell 中用一行代码实现终端输入回显（按 Ctrl-C 结束）：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">in</span><span class="o">.</span><span class="na">transferTo</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">)</span>
<span class="nc">Hello</span>
<span class="nc">Hello</span>
<span class="mo">01234567</span><span class="mi">89</span>
<span class="mo">01234567</span><span class="mi">89</span>

<span class="err">$</span><span class="mi">1</span> <span class="o">==&gt;</span>
</code></pre></div></div>

<p>有了这个函数，实现类似 <code class="language-plaintext highlighter-rouge">IOUtils.toString()</code> 的逻辑就很方便了：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">String</span> <span class="nf">inputToString</span><span class="o">(</span><span class="nc">InputStream</span> <span class="n">is</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
   <span class="o">...&gt;</span>    <span class="nc">ByteArrayOutputStream</span> <span class="n">os</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ByteArrayOutputStream</span><span class="o">();</span>
   <span class="o">...&gt;</span>    <span class="n">is</span><span class="o">.</span><span class="na">transferTo</span><span class="o">(</span><span class="n">os</span><span class="o">);</span>
   <span class="o">...&gt;</span>    <span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
   <span class="o">...&gt;</span> <span class="o">}</span>
<span class="o">|</span>  <span class="n">created</span> <span class="n">method</span> <span class="nf">inputToString</span><span class="o">(</span><span class="nc">InputStream</span><span class="o">)</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nf">inputToString</span><span class="o">(</span><span class="k">new</span> <span class="nc">ByteArrayInputStream</span><span class="o">(</span><span class="s">"Hello"</span><span class="o">.</span><span class="na">getBytes</span><span class="o">()))</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="s">"Hello"</span>
</code></pre></div></div>

<h3 id="接口的私有默认方法">接口的私有默认方法</h3>
<p>Java 8 引入了接口的默认方法，可以为接口提供默认实现。例如我们有一个接口 <code class="language-plaintext highlighter-rouge">Person</code> 以及很多实现该接口的类，当我们为 <code class="language-plaintext highlighter-rouge">Person</code> 接口新增 <code class="language-plaintext highlighter-rouge">greeting()</code>、<code class="language-plaintext highlighter-rouge">farewell()</code> 两个方法时，可以为其提供默认实现，而无需每个实现 <code class="language-plaintext highlighter-rouge">Person</code> 接口的类都实现一遍：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Person</span> <span class="o">{</span>
    <span class="c1">// 其他接口 ……</span>

    <span class="nc">String</span> <span class="nf">getName</span><span class="o">();</span>

    <span class="k">default</span> <span class="kt">void</span> <span class="nf">greeting</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">": Hello!"</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="k">default</span> <span class="kt">void</span> <span class="nf">farewell</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">": Goodbye!"</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Java 9 的接口支持私有默认方法，可以让多个默认方法复用代码：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Person</span> <span class="o">{</span>
    <span class="c1">// 其他接口 ……</span>

    <span class="nc">String</span> <span class="nf">getName</span><span class="o">();</span>

    <span class="k">default</span> <span class="kt">void</span> <span class="nf">greeting</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">say</span><span class="o">(</span><span class="s">"Hello!"</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="k">default</span> <span class="kt">void</span> <span class="nf">farewell</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">say</span><span class="o">(</span><span class="s">"Goodbye!"</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">say</span><span class="o">(</span><span class="nc">String</span> <span class="n">words</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">": "</span> <span class="o">+</span> <span class="n">words</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>假如这里的私有默认方法 <code class="language-plaintext highlighter-rouge">say()</code> 并没有调用实例方法 <code class="language-plaintext highlighter-rouge">getName()</code>，那么还可以使用静态私有方法：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Person</span> <span class="o">{</span>
    <span class="c1">// 其他接口 ……</span>

    <span class="k">default</span> <span class="kt">void</span> <span class="nf">greeting</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">say</span><span class="o">(</span><span class="s">"Hello!"</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="k">default</span> <span class="kt">void</span> <span class="nf">farewell</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">say</span><span class="o">(</span><span class="s">"Goodbye!"</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">say</span><span class="o">(</span><span class="nc">String</span> <span class="n">words</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">words</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="集合的工厂方法">集合的工厂方法</h3>
<p>在 Java 9 之前，创建带有初始字面值的只读 List、Set 与 Map 的代码语法风格各异，甚至有些繁琐麻烦。例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// import static java.util.Collections.unmodifiableSet;</span>
<span class="c1">// import static java.util.Collections.unmodifiableMap;</span>
<span class="c1">// import static java.util.stream.Collectors.collectingAndThen;</span>
<span class="c1">// import static java.util.stream.Collectors.toSet</span>
<span class="c1">// import static java.util.stream.Collectors.toMap</span>
<span class="c1">// import static java.util.AbstractMap.SimpleEntry;</span>

<span class="kd">final</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="s">"foo"</span><span class="o">,</span> <span class="s">"bar"</span><span class="o">,</span> <span class="s">"baz"</span><span class="o">);</span>

<span class="kd">final</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">numbers</span> <span class="o">=</span> <span class="n">unmodifiableSet</span><span class="o">(</span><span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;()</span> <span class="o">{</span>
    <span class="o">{</span>
        <span class="n">add</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span> <span class="n">add</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span> <span class="n">add</span><span class="o">(</span><span class="mi">3</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">});</span>

<span class="c1">// 或者</span>
<span class="kd">final</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">numbers1</span> <span class="o">=</span>
    <span class="n">unmodifiableSet</span><span class="o">(</span><span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;(</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)));</span>


<span class="c1">// 或者（Java 8）</span>
<span class="kd">final</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">numbers2</span> <span class="o">=</span>
<span class="nc">Stream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">)</span>
    <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="n">collectingAndThen</span><span class="o">(</span><span class="n">toSet</span><span class="o">(),</span> <span class="nl">Collections:</span><span class="o">:</span><span class="n">unmodifiableSet</span><span class="o">));</span>

<span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">map</span> <span class="o">=</span>
    <span class="n">unmodifiableMap</span><span class="o">(</span><span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;()</span> <span class="o">{</span>
        <span class="o">{</span>
            <span class="n">put</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="s">"foo"</span><span class="o">);</span>
            <span class="n">put</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="s">"bar"</span><span class="o">);</span>
            <span class="n">put</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="s">"baz"</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">});</span>

<span class="c1">// 或者（Java 8）</span>
<span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">map1</span> <span class="o">=</span>
    <span class="n">unmodifiableMap</span><span class="o">(</span><span class="nc">Stream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
        <span class="k">new</span> <span class="nc">SimpleEntry</span><span class="o">&lt;&gt;(</span><span class="mi">1</span><span class="o">,</span> <span class="s">"foo"</span><span class="o">),</span>
        <span class="k">new</span> <span class="nc">SimpleEntry</span><span class="o">&lt;&gt;(</span><span class="mi">2</span><span class="o">,</span> <span class="s">"bar"</span><span class="o">),</span>
        <span class="k">new</span> <span class="nc">SimpleEntry</span><span class="o">&lt;&gt;(</span><span class="mi">3</span><span class="o">,</span> <span class="s">"baz"</span><span class="o">))</span>
            <span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="n">toMap</span><span class="o">((</span><span class="n">e</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">e</span><span class="o">.</span><span class="na">getKey</span><span class="o">(),</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">e</span><span class="o">.</span><span class="na">getValue</span><span class="o">()))</span>
    <span class="o">);</span>
</code></pre></div></div>

<p>而在 Java 9 中可以使用更简洁、更直观、更统一的方式：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// import static java.util.Map.entry</span>

<span class="kd">final</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"foo"</span><span class="o">,</span> <span class="s">"bar"</span><span class="o">,</span> <span class="s">"baz"</span><span class="o">);</span>

<span class="kd">final</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">&gt;</span> <span class="n">numbers</span> <span class="o">=</span> <span class="nc">Set</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">3</span><span class="o">);</span>

<span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">map</span> <span class="o">=</span> <span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="s">"foo"</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="s">"bar"</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="s">"barz"</span><span class="o">);</span>

<span class="c1">// 或者</span>
<span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">map1</span> <span class="o">=</span> <span class="nc">Map</span><span class="o">.</span><span class="na">ofEntries</span><span class="o">(</span>
    <span class="n">entry</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="s">"foo"</span><span class="o">),</span>
    <span class="n">entry</span><span class="o">(</span><span class="mi">2</span><span class="o">,</span> <span class="s">"bar"</span><span class="o">),</span>
    <span class="n">entry</span><span class="o">(</span><span class="mi">3</span><span class="o">,</span> <span class="s">"baz"</span><span class="o">)</span>
<span class="o">);</span>
</code></pre></div></div>

<h3 id="optional-改进">Optional 改进</h3>

<p><strong>新增 <code class="language-plaintext highlighter-rouge">or</code> 方法</strong></p>

<p><code class="language-plaintext highlighter-rouge">Optional</code> 类新增 <code class="language-plaintext highlighter-rouge">or()</code> 方法，它与 <code class="language-plaintext highlighter-rouge">orElseGet()</code> 类似，只是其参数 lambda 的返回值以及自身的返回值都是 <code class="language-plaintext highlighter-rouge">Optional</code>，可以用于从一系列可选值中选取第一个有值的。例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="kn">import</span> <span class="nn">static</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">Optional</span><span class="o">.*;</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nf">empty</span><span class="o">().</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">empty</span><span class="o">()).</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">empty</span><span class="o">())</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">empty</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nf">empty</span><span class="o">().</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">empty</span><span class="o">()).</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">of</span><span class="o">(</span><span class="s">"Hello"</span><span class="o">)).</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">empty</span><span class="o">())</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">[</span><span class="nc">Hello</span><span class="o">]</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nf">empty</span><span class="o">().</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">of</span><span class="o">(</span><span class="s">"Wolrd"</span><span class="o">)).</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">of</span><span class="o">(</span><span class="s">"Hello"</span><span class="o">)).</span><span class="na">or</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="n">empty</span><span class="o">())</span>
<span class="err">$</span><span class="mi">4</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">[</span><span class="nc">Wolrd</span><span class="o">]</span>
</code></pre></div></div>

<blockquote>
  <p>注：只有 <code class="language-plaintext highlighter-rouge">Optional</code> 类新添了这个方法，而 <code class="language-plaintext highlighter-rouge">OptionalInt</code> 等可选类并未添加。</p>
</blockquote>

<p><strong>新增 <code class="language-plaintext highlighter-rouge">ifPresentOrElse</code> 方法</strong></p>

<p>在 Java 8 中可选类都实现了 <code class="language-plaintext highlighter-rouge">ifPresent</code>，可以很容易实现有值时输出其内容：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">OptionalDouble</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">PI</span><span class="o">).</span><span class="na">ifPresent</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">)</span>
<span class="mf">3.141592653589793</span>
</code></pre></div></div>

<p>但如果要同时实现无值时输出默认值，它就无能为力了。这时 Java 9 的 <code class="language-plaintext highlighter-rouge">ifPresentOrElse</code> 就派上用场了：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">OptionalDouble</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">Math</span><span class="o">.</span><span class="na">PI</span><span class="o">).</span><span class="na">ifPresentOrElse</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">,</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"No Value"</span><span class="o">))</span>
<span class="mf">3.141592653589793</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">OptionalDouble</span><span class="o">.</span><span class="na">empty</span><span class="o">().</span><span class="na">ifPresentOrElse</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">,</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"No Value"</span><span class="o">))</span>
<span class="nc">No</span> <span class="nc">Value</span>
</code></pre></div></div>

<p><strong>新增 <code class="language-plaintext highlighter-rouge">stream</code> 方法</strong></p>

<p>在 Java 8 中如果想对 <code class="language-plaintext highlighter-rouge">OptionalInt</code> 等基本类型可选值做流式集合处理会非常麻烦：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">OptionalLong</span> <span class="n">optlong</span> <span class="o">=</span> <span class="nc">OptionalLong</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1L</span><span class="o">)</span>
<span class="n">optlong</span> <span class="o">==&gt;</span> <span class="nc">OptionalLong</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="o">(</span><span class="n">optlong</span><span class="o">.</span><span class="na">isPresent</span><span class="o">()?</span> <span class="nc">LongStream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">optlong</span><span class="o">.</span><span class="na">getAsLong</span><span class="o">()):</span> <span class="nc">LongStream</span><span class="o">.</span><span class="na">empty</span><span class="o">()).</span><span class="na">map</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="o">).</span><span class="na">boxed</span><span class="o">().</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">())</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="o">[</span><span class="mi">2</span><span class="o">]</span>
</code></pre></div></div>

<p>Java 9 添加了 <code class="language-plaintext highlighter-rouge">stream()</code> 方法可使其简化很多：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="n">optlong</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="o">).</span><span class="na">boxed</span><span class="o">().</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">())</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="o">[</span><span class="mi">2</span><span class="o">]</span>
</code></pre></div></div>

<h3 id="stream-类改进">Stream 类改进</h3>

<p><strong>新增 <code class="language-plaintext highlighter-rouge">ofNullable</code> 工厂方法</strong></p>

<p>在 Java 9 中如需对可空对象进行流式集合处理，可以借助 <code class="language-plaintext highlighter-rouge">Optional</code> 来实现：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="s">"hello"</span><span class="o">).</span><span class="na">stream</span><span class="o">().</span><span class="na">count</span><span class="o">()</span>
<span class="err">$</span><span class="mi">1</span> <span class="o">==&gt;</span> <span class="mi">1</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="kc">null</span><span class="o">).</span><span class="na">stream</span><span class="o">().</span><span class="na">count</span><span class="o">()</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="mi">0</span>
</code></pre></div></div>

<p>另外，Java 9 也为 <code class="language-plaintext highlighter-rouge">Stream</code> 类添加了 <code class="language-plaintext highlighter-rouge">ofNullable()</code> 方法，上述代码还可以简化为：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Stream</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="s">"hello"</span><span class="o">).</span><span class="na">count</span><span class="o">()</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="mi">1</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Stream</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="kc">null</span><span class="o">).</span><span class="na">count</span><span class="o">()</span>
<span class="err">$</span><span class="mi">4</span> <span class="o">==&gt;</span> <span class="mi">0</span>
</code></pre></div></div>

<blockquote>
  <p>注：只有 <code class="language-plaintext highlighter-rouge">Stream</code> 类新添了这个方法，而 <code class="language-plaintext highlighter-rouge">IntStream</code> 等并未添加。</p>
</blockquote>

<p><strong>新增 <code class="language-plaintext highlighter-rouge">takeWhile</code> 与 <code class="language-plaintext highlighter-rouge">dropWhile</code> 方法</strong></p>

<p>分别用于“取元素直到指定值出现”、“直到指定值出现开始取元素”，例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="kn">import</span> <span class="nn">static</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">stream</span><span class="o">.</span><span class="na">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">;</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">9</span><span class="o">,</span> <span class="mi">6</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">6</span><span class="o">).</span><span class="na">takeWhile</span><span class="o">(</span><span class="n">n</span> <span class="o">-&gt;</span> <span class="n">n</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">).</span><span class="na">boxed</span><span class="o">().</span><span class="na">collect</span><span class="o">(</span><span class="n">toList</span><span class="o">())</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="o">[</span><span class="mi">9</span><span class="o">,</span> <span class="mi">6</span><span class="o">,</span> <span class="mi">3</span><span class="o">]</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">9</span><span class="o">,</span> <span class="mi">6</span><span class="o">,</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">6</span><span class="o">).</span><span class="na">dropWhile</span><span class="o">(</span><span class="n">n</span> <span class="o">-&gt;</span> <span class="n">n</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">).</span><span class="na">boxed</span><span class="o">().</span><span class="na">collect</span><span class="o">(</span><span class="n">toList</span><span class="o">())</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="o">[</span><span class="mi">0</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="mi">4</span><span class="o">,</span> <span class="mi">6</span><span class="o">]</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">iterate</code> 方法新增重载</strong></p>

<p>使用 Java 8 的 <code class="language-plaintext highlighter-rouge">iterate</code> 以及上述 <code class="language-plaintext highlighter-rouge">takeWhile</code> 实现输出 10 以内的偶数如下：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">iterate</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">i</span> <span class="o">-&gt;</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">2</span><span class="o">).</span><span class="na">takeWhile</span><span class="o">(</span><span class="n">i</span> <span class="o">-&gt;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="mi">10</span><span class="o">).</span><span class="na">forEach</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">)</span>
<span class="mi">0</span>
<span class="mi">2</span>
<span class="mi">4</span>
<span class="mi">6</span>
<span class="mi">8</span>
<span class="mi">10</span>
</code></pre></div></div>

<p>不过 Java 9 还为 <code class="language-plaintext highlighter-rouge">iterate</code> 新添了支持判断迭代条件的重载形式，因此上述代码可改为：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">iterate</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">i</span> <span class="o">-&gt;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="mi">10</span><span class="o">,</span> <span class="n">i</span> <span class="o">-&gt;</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">2</span><span class="o">).</span><span class="na">forEach</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">)</span>
<span class="mi">0</span>
<span class="mi">2</span>
<span class="mi">4</span>
<span class="mi">6</span>
<span class="mi">8</span>
<span class="mi">10</span>
</code></pre></div></div>
<h3 id="进程-api-改进">进程 API 改进</h3>
<p>Java 9 新增了 <code class="language-plaintext highlighter-rouge">ProcessHandle</code> 接口，可以很方便地获得进程的 pid 与进程信息。<code class="language-plaintext highlighter-rouge">ProcessHandle</code> 还提供了两个静态方法，分别用于获取当前进程与指定 pid 的 <code class="language-plaintext highlighter-rouge">ProcessHandle</code> 实例。例如：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">ProcessHandle</span><span class="o">.</span><span class="na">current</span><span class="o">().</span><span class="na">pid</span><span class="o">()</span>
<span class="err">$</span><span class="mi">1</span> <span class="o">==&gt;</span> <span class="mi">27137</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">ProcessHandle</span><span class="o">.</span><span class="na">current</span><span class="o">().</span><span class="na">parent</span><span class="o">()</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">[</span><span class="mi">27112</span><span class="o">]</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">ProcessHandle</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">27112</span><span class="o">).</span><span class="na">get</span><span class="o">().</span><span class="na">children</span><span class="o">().</span><span class="na">count</span><span class="o">()</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="mi">1</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">ProcessHandle</span><span class="o">.</span><span class="na">current</span><span class="o">().</span><span class="na">info</span><span class="o">().</span><span class="na">command</span><span class="o">()</span>
<span class="err">$</span><span class="mi">4</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">[/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">jdk</span><span class="o">-</span><span class="mf">9.0</span><span class="o">.</span><span class="mi">4</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">java</span><span class="o">]</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">ProcessHandle</span><span class="o">.</span><span class="na">current</span><span class="o">().</span><span class="na">info</span><span class="o">().</span><span class="na">totalCpuDuration</span><span class="o">()</span>
<span class="err">$</span><span class="mi">5</span> <span class="o">==&gt;</span> <span class="nc">Optional</span><span class="o">[</span><span class="no">PT0</span><span class="o">.</span><span class="mi">81</span><span class="no">S</span><span class="o">]</span>
</code></pre></div></div>

<p>关于这些新特性的更多细节请参见官方文档。</p>

<p>本系列未完待续。</p>]]></content><author><name></name></author><category term="java" /><summary type="html"><![CDATA[本篇介绍 Java 9 更新以及一些非重大改动。 更新 Java 9 已经正式发布半年多了。这期间不仅 Java 9 发布了更新，就连 Java 10 也已正式发布。上一篇中提到的工具也都有更新。其中 IDEA 新版改进了不少 Java 9 支持，Eclipse 新版内置了 Java 9 支持（不再需要 Beta 版插件，但可能需要重装，不能直接从旧版升级）。最值得一提的是 lombok 1.16.20 发布。]]></summary></entry><entry><title type="html">体验 Java 9（1）：从 Hello World 到 Lombok</title><link href="https://hltj.me/java/2017/09/22/experience-java9-lombok.html" rel="alternate" type="text/html" title="体验 Java 9（1）：从 Hello World 到 Lombok" /><published>2017-09-22T04:43:06+00:00</published><updated>2017-09-22T04:43:06+00:00</updated><id>https://hltj.me/java/2017/09/22/experience-java9-lombok</id><content type="html" xml:base="https://hltj.me/java/2017/09/22/experience-java9-lombok.html"><![CDATA[<p>Java 9 正式版已于当地时间的 9 月 21 日（北京时间大约是今天凌晨）如期发布。可<a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.html">在 Oracle 官网下载</a>。
<!--more--></p>
<blockquote>
  <p>2018-12-28 注：Java 9 是一个短期版本，目前的长期支持（LTS）版是 Java 11。
而 Java 10 与 Java 11 自身的改动都不多，可以说 Java 11 相对于上一个 LTS 版（Java 8）的新特性很多都是 Java 9 引入的。因此本文仍有参考价值。
上述下载链接已失效，一些示例可以直接使用 Java 11 来演练，或者用<a href="https://jdk.java.net/archive/">历史版本的 OpenJDK</a>（不推荐）。</p>
</blockquote>

<p>Java 9 没有像 Java 5/Java 8 那样引入新的编程范式而给语言本身带来革命性的改进，不过 Java 9 的改动还是很大的，尤其是引入模块化对 JDK 与运行时的改动都很大。
现在网上能找到很多介绍 Java 9 新特性的文章，这里不再赘述，只简要列举如下：</p>
<ol>
  <li>模块化（Jigsaw 项目）</li>
  <li>G1 成为默认垃圾收集器</li>
  <li>JDK 自带 REPL 即 JShell</li>
  <li>支持 HTTP/2 与 WebSocket 的新版 HTTP 客户端</li>
  <li>多版本 Jar 包</li>
  <li>语言、JDK、运行时与工具的其他优化与改动</li>
</ol>

<p>如何切换到 Java 9，Oracle 官网上也有详细的<a href="https://docs.oracle.com/javase/9/migrate/toc.htm">迁移文档</a>。</p>

<p>而如何获得最直观的感受，就不妨跟我一起来亲身体验下了。</p>

<h2 id="hello-world">Hello World</h2>
<p>千万不要觉得用体验一门新语言的方式来体验一个新版本是小儿科或者小题大做。
因为 Hello World 能运行至少意味着基本环境没问题，如果不能就说明我们发现了问题。</p>

<h3 id="hello-jshell">Hello JShell</h3>
<p>JShell 是 JDK 9 自带的交互式编程命令行，即 REPL（Read-Eval-Print Loop 的简写，直译为 “读取-求值-输出”循环），非常适合快速实验一些代码片段。
它遵循通用的命令行操作，如 <code class="language-plaintext highlighter-rouge">Tab</code> 自动补全、<code class="language-plaintext highlighter-rouge">Ctrl-D</code> 退出、<code class="language-plaintext highlighter-rouge">Ctrl-R</code> 反向搜索、<code class="language-plaintext highlighter-rouge">Ctrl-S</code> 正向搜索等等。
运行 <code class="language-plaintext highlighter-rouge">jshell --help</code> 可以查看其命令行选项说明，直接运行 <code class="language-plaintext highlighter-rouge">jshell</code> 进入交互式模式后，可以通过 <code class="language-plaintext highlighter-rouge">/help</code> 查看交互式模式帮助。
更详细的用法说明可参见其<a href="https://docs.oracle.com/javase/9/tools/jshell.htm">官方文档</a>。</p>

<h4 id="交互式-hello-world">交互式 Hello World</h4>
<p>运行 <code class="language-plaintext highlighter-rouge">jshell</code> 进入交互式模式：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>jshell
|  欢迎使用 JShell <span class="nt">--</span> 版本 9
|  要大致了解该版本, 请键入: /help intro

jshell&gt; 
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">jshell&gt; </code> 是 JShell 的命令提示符，可在其后直接键入代码。在 JShell 中直接输入可运行的代码就可以，无需定义额外的类与 <code class="language-plaintext highlighter-rouge">main</code> 函数，还可以省略单行语句的分号。
因此运行 Hello World 只需键入 <code class="language-plaintext highlighter-rouge">System.out.println("Hello, World!")</code> 即可，并且在输入过程中还可以通过 Tab 键补全：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Hello, World!"</span><span class="o">)</span>
<span class="nc">Hello</span><span class="o">,</span> <span class="nc">World</span><span class="o">!</span>
</code></pre></div></div>

<p>大功告成。当然，如果只是想在 JShell 中查看一个表达式的求值结果，并不需要调用 <code class="language-plaintext highlighter-rouge">System.out.println()</code> 这么麻烦，而只需直接键入表达式然后回车即可：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="s">"Hello, World!"</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">==&gt;</span> <span class="s">"Hello, World!"</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nc">Math</span><span class="o">.</span><span class="na">PI</span> <span class="o">*</span> <span class="mf">1.5</span> <span class="o">*</span> <span class="mf">1.5</span>
<span class="err">$</span><span class="mi">3</span> <span class="o">==&gt;</span> <span class="mf">7.0685834705770345</span>
</code></pre></div></div>

<p>这实际上是 JShell 的“反馈”，如果希望反馈中包含表达式的类型，可以在启动时加命令行选项 <code class="language-plaintext highlighter-rouge">-v</code> 或者通过交互模式的命令 <code class="language-plaintext highlighter-rouge">/set feedback verbose</code> 切换到详细反馈模式：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">jshell</span><span class="o">&gt;</span> <span class="o">/</span><span class="n">set</span> <span class="n">feedback</span> <span class="n">verbose</span>
<span class="o">|</span>  <span class="nl">反馈模式:</span> <span class="n">verbose</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="s">"Hello, World!"</span>
<span class="err">$</span><span class="mi">4</span> <span class="o">==&gt;</span> <span class="s">"Hello, World!"</span>
<span class="o">|</span>  <span class="n">已创建暂存变量</span> <span class="err">$</span><span class="mi">4</span> <span class="o">:</span> <span class="nc">String</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="kt">double</span> <span class="nf">areaOfCircle</span><span class="o">(</span><span class="kt">double</span> <span class="n">radius</span><span class="o">)</span> <span class="o">{</span>
   <span class="o">...&gt;</span>     <span class="k">return</span> <span class="nc">Math</span><span class="o">.</span><span class="na">PI</span> <span class="o">*</span> <span class="n">radius</span> <span class="o">*</span> <span class="n">radius</span><span class="o">;</span>
   <span class="o">...&gt;</span> <span class="o">}</span>
<span class="o">|</span>  <span class="n">已创建</span> <span class="n">方法</span> <span class="n">areaOfCircle</span><span class="o">(</span><span class="kt">double</span><span class="o">)</span>

<span class="n">jshell</span><span class="o">&gt;</span> <span class="nf">areaOfCircle</span><span class="o">(</span><span class="mf">1.5</span><span class="o">)</span>
<span class="err">$</span><span class="mi">6</span> <span class="o">==&gt;</span> <span class="mf">7.0685834705770345</span>
<span class="o">|</span>  <span class="n">已创建暂存变量</span> <span class="err">$</span><span class="mi">6</span> <span class="o">:</span> <span class="kt">double</span>
</code></pre></div></div>

<p>更多交互式模式的用法可以查阅文档或自行发掘，现在通过快捷键 <code class="language-plaintext highlighter-rouge">Ctrl-D</code> 或者交互式命令 <code class="language-plaintext highlighter-rouge">/exit</code> 退出 JShell，来看下非交互式 JShell 版 Hello World。</p>

<h4 id="hello-jshell-脚本">Hello JShell 脚本</h4>
<p>除了交互式运行，JShell 还可以用作脚本执行。
现在我们将输出 Hello World 的语句写入一个文件：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">echo</span> <span class="s1">'System.out.println("Hello, World!")'</span> <span class="o">&gt;</span> hello.jsh
</code></pre></div></div>

<p>然后用 JShell 运行之：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>jshell hello.jsh
Hello, World!
|  欢迎使用 JShell <span class="nt">--</span> 版本 9
|  要大致了解该版本, 请键入: /help intro

jshell&gt; 
</code></pre></div></div>

<p>在执行完 hello.jsh 中的语句后进入了交互式模式，这并不是预期的结果，我们希望它执行完就退出而不是进入交互式模式。
当然，这只需在文件末尾追加 <code class="language-plaintext highlighter-rouge">/exit</code> 命令即可：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">echo</span> <span class="s1">'/exit'</span> <span class="o">&gt;&gt;</span> hello.jsh
<span class="nv">$ </span>jshell hello.jsh
Hello, World!
</code></pre></div></div>

<p>又大功告成了！（奇怪，我为什么要说又 :P）。比较遗憾的是 JShell 目前还不支持 shbang，不能将 <code class="language-plaintext highlighter-rouge">.jsh</code> 文件直接作为可执行脚本来用。</p>

<h2 id="构建运行-hello-world">构建运行 Hello World</h2>
<p>对于 Hello World 这样简单的代码片段，确实只用 JShell 演练就可以了。只是这并不能验证构建运行也一切正常，因此还要继续来看使用 JDK 9 构建运行 Hello World 程序的情形。</p>

<h3 id="直接编译运行">直接编译运行</h3>
<p>直接编译运行与 JDK 8 及以前版本没有任何区别：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span> <span class="o">&gt;</span> Hello.java <span class="o">&lt;&lt;</span> <span class="no">END</span><span class="sh">
&gt; public class Hello {
&gt;     public static void main(String[] args) {
&gt;         System.out.println("Hello, World!");
&gt;     }
&gt; }
&gt; END
</span><span class="nv">$ </span><span class="sh">javac Hello.java
</span><span class="nv">$ </span><span class="sh">java Hello
Hello, World!
</span></code></pre></div></div>

<p>而我们在实际项目中通常并不能如此简单地编译运行，而需使用 Maven 或者 Gradle 来构建项目。</p>
<blockquote>
  <p><strong>注：</strong>本文中测过的 Maven 版本为 3.3.9、3.5.0；Gradle 版本为 3.5-RC-2 到 4.0.2。</p>
</blockquote>

<h3 id="hello-maven">Hello Maven</h3>
<p>现在我们将 Hello.java 的开头加上包声明 <code class="language-plaintext highlighter-rouge">package me.hltj.java9demo.hello;</code>，并将其放到 <code class="language-plaintext highlighter-rouge">src/main/java/me/hltj/java9demo/hello</code> 目录下，然后创建 pom.xml 如下：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>

    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>

    <span class="nt">&lt;groupId&gt;</span>me.hltj<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>java9demo-hello<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>

<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<p>这样就创建了一个简单的 maven 项目。
如果使用 JDK 8，此时就可以通过 <code class="language-plaintext highlighter-rouge">mvn package</code> 生成 jar 包，并通过以下命令来运行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>java <span class="nt">-cp</span> target/java9demo-hello-1.0-SNAPSHOT.jar me.hltj.java9demo.hello.Hello
</code></pre></div></div>

<p>但是使用 JDK 9 编译会报错：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project java9demo-hello: Compilation failure: Compilation failure:
[ERROR] Source option 1.5 is no longer supported. Use 1.6 or later.
[ERROR] Target option 1.5 is no longer supported. Use 1.6 or later.
[ERROR] -&gt; [Help 1]
</code></pre></div></div>

<p>这是因为 Maven 默认的源代码和目标版本都是 1.5，而 JDK 9 支持的最低版本是 1.6，因此必须在 pom.xml 中显式指定这两个版本号：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>1.9<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>1.9<span class="nt">&lt;/maven.compiler.target&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>
</code></pre></div></div>

<p>之后就可以像使用 JDK 8 一样正常构建和运行了。</p>

<h3 id="hello-gradle">Hello Gradle</h3>
<p>首先为项目创建 gradle wrapper 并基于 maven 项目初始化：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gradle wrapper <span class="nt">--gradle-version</span><span class="o">=</span>4.0.2 <span class="nt">--distribution-type</span><span class="o">=</span>bin
<span class="nv">$ </span>./gradlew init
</code></pre></div></div>

<p>然后将 build.gradle 简化为：</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'java'</span>

<span class="n">group</span> <span class="o">=</span> <span class="s1">'me.hltj'</span>
<span class="n">version</span> <span class="o">=</span> <span class="s1">'1.0-SNAPSHOT'</span>
</code></pre></div></div>

<p>无需指定源代码与目标版本就可以正常构建运行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>./gradlew build
BUILD SUCCESSFUL <span class="k">in </span>1s
<span class="nv">$ </span>java <span class="nt">-cp</span> build/libs/java9demo-hello-1.0-SNAPSHOT.jar me.hltj.java9demo.hello.Hello
Hello, World!
</code></pre></div></div>

<p>除了构建工具外，在实际项目开发中也离不开 IDE，接下来我们就看下在不同 IDE 中使用 Java 9 构建与运行的情况。</p>

<h3 id="hello-idea">Hello IDEA</h3>
<p>最新稳定版的 IDEA（2017.2.4）已支持 Java 9。
在其中使用 Java 9 构建与运行 Hello World 与 Java 8 无异，只需选用 JDK 9 即可：</p>

<p><img src="/assets/java9/idea-jdk9.png" alt="IDEA 配置 JDK 9" /></p>

<p>如果尚未添加点击上图所示的对话框右上方的“New”按钮来添加。
之后可以照常构建与运行 Hello World，无论创建项目时选 Java 项目、Maven 项目还是 Gradle 项目。</p>

<h3 id="hello-eclipse">Hello Eclipse</h3>
<p>最新稳定版的 Eclipse Oxygen（4.7.0）并没有内置 Java 9 支持，需要安装一个 <a href="https://marketplace.eclipse.org/content/java-9-support-beta-oxygen">Beta 版的 Java 9 支持插件</a>才行：</p>

<p><img src="/assets/java9/eclipse-java9.png" alt="Eclipse 安装 Java 9 插件" /></p>

<p>之后就可以添加 JDK 9 了：</p>

<p><img src="/assets/java9/eclipse-jdk9.png" alt="IDEA 配置 Java 9" /></p>

<p>添加完成后就可以通过 JDK 9 照常构建与运行 Hello World，无论创建项目时选 Java 项目、Maven 项目还是 Gradle 项目。</p>

<h3 id="hello-netbeans">Hello NetBeans</h3>
<p>最新稳定版的 NetBeans（8.2）并不支持 Java 9，也没有用于 Java 9 支持的插件。
想要支持 Java 9 只能用每日构建版，可在这里下载： <a href="http://bits.netbeans.org/download/trunk/nightly/latest/">http://bits.netbeans.org/download/trunk/nightly/latest/</a>。</p>

<p>在安装时选 JDK 9。之后可以照常构建与运行 Hello World，无论创建项目时选 Java 项目还是 Maven 项目。</p>

<blockquote>
  <p><strong>注：</strong>NetBeans 虽然有 Gradle 插件，但是在每日构建版中安装之后也基本无法使用。</p>
</blockquote>

<h3 id="hello-world-小结">Hello World 小结</h3>
<p>以任何方式通过 JRE 9 运行 Hello World 都如预期般顺利。使用 Gradle 或者 IDEA 通过 JDK 9 构建 Hello World 示例项目也一如既往地顺利。
而对于 Maven 需要注意显式指定源代码与目标版本，对于 Eclipse 需要安装插件，对于 NetBeans 目前只能用每日构建版。</p>

<blockquote>
  <p><strong>注：</strong>示例代码已放到 Github 上： <a href="https://github.com/hltj/java9demo/tree/master/hello">https://github.com/hltj/java9demo/tree/master/hello</a>。</p>
</blockquote>

<p>Hello World 总体上看还比较顺利，接下来我们看下更具挑战的 Lombok 支持情况。</p>

<h2 id="java-9-中使用-lombok">Java 9 中使用 Lombok</h2>
<p>代码冗长是 Java 语言广为病诟的缺点之一。而其中很多场景都可以使用 Lombok 来简化，这在实际项目中广泛应用。
因此验证 JDK 9 以及使用 JDK 9 的构建工具与 IDE 对 Lombok 的支持情况尤为重要，会关系到很多既有项目能否迁移到 Java 9。</p>

<p>这里以一段简单代码为例，验证 Maven、Gradle、IDEA、Eclipse、NetBeans 在使用 JDK 9 时对 Lombok 的支持情况。代码如下：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">me.hltj.java9demo.lombok</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">lombok.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.extern.slf4j.Slf4j</span><span class="o">;</span>

<span class="nd">@Value</span>
<span class="nd">@ToString</span>
<span class="kd">class</span> <span class="nc">Language</span> <span class="o">{</span>
    <span class="nd">@NonNull</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">version</span><span class="o">;</span>
<span class="o">}</span>

<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LombokDemo</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">val</span> <span class="n">version</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">parseInt</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"java.version"</span><span class="o">));</span>

        <span class="n">val</span> <span class="n">java</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Language</span><span class="o">(</span><span class="s">"Java"</span><span class="o">,</span> <span class="n">version</span><span class="o">);</span>

        <span class="n">log</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Hello Lombok in {}."</span><span class="o">,</span> <span class="n">java</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="使用-maven-构建">使用 Maven 构建</h3>
<p>我们只需创建相应代码目录结构，然后在 pom.xml 中配置以下依赖即可在 Java 8 下通过编译：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;dependencies&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>org.projectlombok<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>lombok<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>1.16.18<span class="nt">&lt;/version&gt;</span>
            <span class="nt">&lt;scope&gt;</span>provided<span class="nt">&lt;/scope&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>ch.qos.logback<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>logback-classic<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>1.2.3<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>
</code></pre></div></div>

<p>当然为了便于运行还引入了 assembly 插件，另外设置源代码与目标级别为 1.8，完整的 pom.xml 参见
<a href="https://github.com/hltj/java9demo/blob/7a704a1b4a60cab74e454ab8d2d7edda30af430f/lombok/pom.xml">https://github.com/hltj/java9demo/blob/7a704a1b4a60cab74e454ab8d2d7edda30af430f/lombok/pom.xml</a>。
此时使用 JDK 8、JDK 9 都能正常编译，但只有在 Java 9 环境中才能正常运行，因为 Java 8 的版本号如“1.8.0_144”不能解析为整数，运行时会抛异常。</p>

<p>但是当我们把 pom.xml 中的源代码与目标的版本号升级为 1.9 时，却编译失败了：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project java9demo-lombok: Compilation failure: Compilation failure: 
[ERROR] /home/hltj/java9demo/lombok/src/main/java/me/hltj/java9demo/lombok/LombokDemo.java:[6,1] 程序包 javax.annotation 不可见
[ERROR]   (程序包 javax.annotation 已在模块 java.xml.ws.annotation 中声明, 但该模块不在模块图中)
</code></pre></div></div>

<p>这是因为 Lombok 默认在每个生成的方法上添加了注解 <code class="language-plaintext highlighter-rouge">@javax.annotation.Generated("lombok")</code>，而在 Java 9 引入模块化系统后不再默认提供这一注解。
解决方案有：</p>
<ol>
  <li>在 lombok.config 中添加以下配置来禁用这一行为：
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lombok.addJavaxGeneratedAnnotation = false
</code></pre></div>    </div>
  </li>
  <li>给 javac 增加参数 <code class="language-plaintext highlighter-rouge">--add-modules=java.xml.ws.annotation</code>。</li>
  <li>采用安卓项目针对此问题的解决方案，添加一个提供该注解的依赖： <code class="language-plaintext highlighter-rouge">org.glassfish:javax.annotation:10.0-b28</code>。</li>
</ol>

<p>例如采用第三种方案，只需在 pom.xml 中追加以下配置：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>org.glassfish<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>javax.annotation<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>10.0-b28<span class="nt">&lt;/version&gt;</span>
            <span class="nt">&lt;scope&gt;</span>provided<span class="nt">&lt;/scope&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>之后就可以使用 Java 9 顺利构建运行了：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>java <span class="nt">-jar</span> target/java9demo-lombok-1.0-SNAPSHOT-jar-with-dependencies.jar
17:24:57.723 <span class="o">[</span>main] INFO me.hltj.java9demo.lombok.LombokDemo - Hello Lombok <span class="k">in </span>Language<span class="o">(</span><span class="nv">name</span><span class="o">=</span>Java, <span class="nv">version</span><span class="o">=</span>9<span class="o">)</span><span class="nb">.</span>
</code></pre></div></div>

<h3 id="使用-gradle-构建">使用 Gradle 构建</h3>
<p>初始化 Gradle 之后将 build.gradle 改成这样即可使用 JDK 8 编译：</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'java'</span>
<span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'application'</span>

<span class="n">group</span> <span class="o">=</span> <span class="s1">'me.hltj'</span>
<span class="n">version</span> <span class="o">=</span> <span class="s1">'1.0-SNAPSHOT'</span>

<span class="n">mainClassName</span> <span class="o">=</span> <span class="s1">'me.hltj.java9demo.lombok.LombokDemo'</span>

<span class="k">repositories</span> <span class="o">{</span>
    <span class="n">mavenCentral</span><span class="o">()</span>
<span class="o">}</span>

<span class="k">dependencies</span> <span class="o">{</span>
    <span class="n">compileOnly</span> <span class="s2">"org.projectlombok:lombok:1.16.18"</span>
    <span class="n">compile</span> <span class="s2">"ch.qos.logback:logback-classic:1.2.3"</span>
<span class="o">}</span>
</code></pre></div></div>

<p>但是使用 JDK 9 编译会遇到与 Maven 构建时相同的问题，这里采用第二种方案，在 build.gradle 中追加以下配置即可：</p>

<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">compileJava</span> <span class="o">{</span>
    <span class="n">options</span><span class="o">.</span><span class="na">compilerArgs</span> <span class="o">+=</span> <span class="o">[</span><span class="s1">'--add-modules'</span><span class="o">,</span> <span class="s1">'java.xml.ws.annotation'</span><span class="o">]</span>
<span class="o">}</span>
</code></pre></div></div>

<blockquote>
  <p><strong>注意：</strong>目前 Gradle 4.1 到 4.2-RC-2 版无法使用 JDK 9 编译这个项目，这也是本文只验证了 Gradle 3.5-RC-2 到 4.0.2 版的原因。</p>
</blockquote>

<h3 id="在-idea-中使用">在 IDEA 中使用</h3>
<p>如果 IDEA 中已安装 Lombok 插件，并且在项目中开启了注解处理，就能够正常解析 Lombok 注解。但是很遗憾的是，无法直接在 IDEA 中使用 JDK 9 构建：</p>

<p><img src="/assets/java9/idea-lombok-err.png" alt="IDEA 中使用 Lombok 时报错" /></p>

<p>在 IDEA 中要使用 JDK 9 编译这个项目，需采用将构建与运行委托给 Gradle 的方式：</p>

<p><img src="/assets/java9/idea-delegate-to-gradle.png" alt="IDEA 将构建与运行委托给 Gradle" /></p>

<p>这样能够通过 Gradle 来构建，而运行还需要再做几步。首先点击第 15/16 行左侧的运行按钮：</p>

<p><img src="/assets/java9/idea-lombok-run.png" alt="IDEA 中的 lombok 示例代码" /></p>

<p>在弹出菜单中选择“Run”，此时“Run”窗口中默认显示的是各个 Gradle 任务的执行情况，需要点击侧栏的按钮将其切换到文本模式才能看到输出：</p>

<p><img src="/assets/java9/idea-lombok-result.png" alt="IDEA 中 lombok 示例运行结果" /></p>

<h3 id="在-eclipse-中使用">在 Eclipse 中使用</h3>
<p>如果已运行过 lombok.jar 为 Eclipse 添加支持，并且在项目配置中启用了注解处理，那么 Eclipse 能够正常解析代码中的大多数 Lombok 注解，只有 <code class="language-plaintext highlighter-rouge">@Value</code>、<code class="language-plaintext highlighter-rouge">@ToString</code> 这两行有问题。</p>

<p>在 Eclipse 中要使用 JDK 9 编译这个项目，需要通过 Maven 方式来构建运行，而且还要去除代码中 <code class="language-plaintext highlighter-rouge">@Value</code> 与  <code class="language-plaintext highlighter-rouge">@ToString</code> 注解并修改相关联代码：</p>

<p><img src="/assets/java9/eclipse-lombok.png" alt="Eclipse 中的 lombok 示例代码" /></p>

<h3 id="在-netbeans-中使用">在 NetBeans 中使用</h3>
<p>在 Netbeans 每日构建版中即便开启注解处理，仍然只能识别出注解本身，而不能进行相应语义处理，可认为无法解析 Lombok 注解。</p>

<p>在 Netbeans 中要使用 JDK 9 编译这个项目，需要通过 Maven 方式来构建运行：</p>

<p><img src="/assets/java9/netbeans-lombok-result.png" alt="NetBeans 中的 lombok 示例代码与运行结果" /></p>

<h3 id="使用-lombok-小结">使用 Lombok 小结</h3>
<p>在 Java 9 环境中使用 Lombok 需要确保编译期有能够提供 <code class="language-plaintext highlighter-rouge">javax.annotation</code> 的模块可用或者在配置中禁用 javax 注解。
Maven/Gradle 项目使用 JDK 9 编译都不成问题，使用 Gradle 4.1 到 4.2-RC-2 版除外。
三款 IDE 中只有 IDEA 能够良好解析 Lombok 注解，Maven 项目只有每日构建版的 NetBeans 能够顺利构建运行，Gradle 项目只有 IDEA 能够构建运行。</p>

<blockquote>
  <p><strong>注：</strong>示例代码已放到 Github 上： <a href="https://github.com/hltj/java9demo/tree/master/lombok">https://github.com/hltj/java9demo/tree/master/lombok</a>。</p>
</blockquote>

<p>如此看来工具支持还有待提升，那么对于既有项目来说，切换到 Java 9 有收益吗？
当然有！Java 9 不仅提供了新功能，也有很多优化的地方。让我们看一个简单、直观的示例。</p>

<h2 id="性能提升的示例">性能提升的示例</h2>
<p>以之前在<a href="https://hltj.me/lang/2016/11/07/10m-letters.html">由“千万字母表问题”看多范式编程语言</a>中的 Java 代码为例，使用 Java 9 运行的效率相对 Java 8 有显著提升。
以下是我分别在不同环境中运行三次取中位数的结果：</p>

<table>
  <thead>
    <tr>
      <th>　</th>
      <th style="text-align: right">Java 8</th>
      <th style="text-align: right">Java 9</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>某云主机</td>
      <td style="text-align: right">6.495s</td>
      <td style="text-align: right">3.706s</td>
    </tr>
    <tr>
      <td>某 Dell 本</td>
      <td style="text-align: right">10.580s</td>
      <td style="text-align: right">6.829s</td>
    </tr>
    <tr>
      <td>某 Thinkpad</td>
      <td style="text-align: right">8.206s</td>
      <td style="text-align: right">5.889s</td>
    </tr>
  </tbody>
</table>

<p>当然这只是说明 Java 9 的 JVM/JRE 在一些场景中相对于 Java 8 有显著的性能提升。而新版 JDK、新的 API、新的工具同样能够带来很多收益，这些会在本系列后续文章中展开。</p>

<h2 id="结论">结论</h2>
<p>JShell 很适合简短代码的演练。
JDK 9 的 IDE 支持还有待改善，短期内并不适合使用 JDK 9 进行项目开发。
但是无论 JRE 9 的性能提升还是 Java 9 引入的新功能都很值得进一步关注。</p>

<p>本系列后续文章会更多关注 Java 9 的新特性，同时如果发现 IDE 支持有改善也会一并提及，以便大家能够尽早用上。</p>]]></content><author><name></name></author><category term="java" /><summary type="html"><![CDATA[Java 9 正式版已于当地时间的 9 月 21 日（北京时间大约是今天凌晨）如期发布。可在 Oracle 官网下载。]]></summary></entry></feed>