Java Lambda 表达式
hhkx9382
8年前
<p>根据 JSR 335 , Java 终于在 Java 8 中引入了 Lambda 表达式。也称之为闭包或者匿名函数。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3d34aac0a33f2f8685072a5634b72fd2.png"></p> <h2><strong>JSR 335</strong></h2> <p>所谓的 JSR (Java Specification Requests) 全称叫做 Java 规范提案。简单来说就是向 Java 社区提交新的 API 或 服务 请求的提案。这些提案将作为 Java 社区进行 Java 语言开发的需求,引导着开发的方向。</p> <p>JSR 335 的提案内容摘要如下:</p> <p>This JSR will extend the Java Programming Language Specification and the Java Virtual Machine Specification to support the following features:</p> <ul> <li>Lambda Expressions</li> <li>SAM Conversion</li> <li>Method References</li> <li>Virtual Extension Methods</li> </ul> <p>也就是如下几点:</p> <ol> <li>支持 lambda 表达式。</li> <li>支持 SAM conversion 用来向前兼容。</li> <li>方法引用 Method References</li> <li>Virtual Extension Methods</li> </ol> <p>在 Java 8 中,以上均已经实现, 以上内容下文均有介绍。</p> <h2><strong>为什么需要 Lambda 表达式?</strong></h2> <p>Lambda 表达式,其实就是代码块。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/f66cf9559901a51f8cbca14780a36668.jpg"></p> <h3><strong>原来怎么处理</strong></h3> <p>在具体了解 lambda 之前,我们先往后退一步,看看之前我们是如何处理这些代码块的!</p> <p><strong>例子一</strong></p> <p>当决定在单独的线程运行某程序时,你这样做的</p> <pre> <code class="language-java">classWorkerimplementsRunnable{ publicvoidrun(){ for(inti =0; i <1000; i++) doWork(); } ... } </code></pre> <p>这样执行:</p> <pre> <code class="language-java">Worker w = newWorker(); newThread(w).start(); </code></pre> <p>Worker 中包含了你要执行的代码块。</p> <p><strong>例子二</strong></p> <p>如果你想实现根据字符串长度大小来排序,而不是默认的字母顺序,你可以自己来实现一个 Comparator 用来 Sort。</p> <pre> <code class="language-java">classLengthComparatorimplementsComparator<String>{ publicintcompare(String first, String second){ returnInteger.compare(first.length(), second.length()); } } Arrays.sort(strings, newLengthComparator()); </code></pre> <p><strong>例子三</strong></p> <p>另外一个例子,我选的是 Android 中的点击事件,同样是 Java:</p> <pre> <code class="language-java">button.setOnClickListener(newView.OnClickListener() { @Override publicvoidonClick(View view){ Toast.makeText(MainActivity.this,"Hello World!", Toast.LENGTH_SHORT).show(); } }); </code></pre> <h3><strong>上面代码有什么问题呢?</strong></h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/36ccea16847e4a76db6db4d1a145700e.jpg"></p> <p>它们都太复杂了啊!</p> <p>上述例子都是在某个类中实现某个接口,然后传递到另外一个方法中作为参数,然后用来执行。</p> <p>但是本质上,他们要传递的就是接口中那一个方法的实现而已啊!有必要先创建类,再实例化,再传递给调用的位置吗?</p> <p>因为 Java 是纯面向对象的语言,像其他语言那样随随便便传个方法过来,那可不行,必须要这样。</p> <p>在其他语言中你可能可以,但是,在 Java 中,不可以。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2864d57def1eda74ad1e55dd8f449515.jpg"></p> <p>Java 设计人员为了 Java 的简洁跟连贯性,一直拒绝为 Java 添加这种功能。(这也是我喜欢 Java 而不喜欢 Python 的原因啊!!!)</p> <p>经过多年的努力,开发人员终于找到了符合 Java 编程习惯的 Lambda 表达式!</p> <h2><strong>Lambda 表达式语法(Syntax)</strong></h2> <p>考虑下前面的例子:</p> <pre> <code class="language-java">Integer.compare(first.length(), second.length()) </code></pre> <p>first 和 second 都是 String 类型,Java 是强类型的语言,必须指定类型:</p> <pre> <code class="language-java">(String first, String second) -> Integer.compare(first.length(), second.length()) </code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/7431f839ca95e6a74b074d316ae2427d.jpg"></p> <p>看到没有!第一个 Lambda 表达式诞生了!!输入、输出简洁明了!</p> <p>为什么叫 Lambda 呢,这个很多年以前,有位逻辑学家想要标准化的表示一些可以被计算的数学方程(实际上存在,但是很难被表示出来),他就用 ℷ 来表示。</p> <p>重新介绍一下 Java 中 Lambda 表达式的格式:</p> <p>(参数) -> 表达式</p> <h3><strong>多返回值</strong></h3> <p>如果计算的结果并不由一个单一的表达式返回(换言之,返回值存在多种情况),使用“{}”,然后明确指定返回值。</p> <pre> <code class="language-java">(String first, String second) -> { if(first.length() < second.length())return-1; elseif(first.length() > second.length())return1; elsereturn0; } </code></pre> <h3><strong>无参数</strong></h3> <p>如果没有参数,则 “()”中就空着。</p> <pre> <code class="language-java">() -> { for(inti =0; i <1000; i++) doWork(); } </code></pre> <h3><strong>省略</strong></h3> <p>如果参数的类型可以被推断出,则可以直接省略</p> <pre> <code class="language-java">Comparator<String> comp = (first, second) // Same as (String first, String second) -> Integer.compare(first.length(), second.length()); </code></pre> <p>这里,first 和 second 可以被推断出是 String 类型,因为 是一个 String 类型的 Comparator。</p> <p>如果单个参数可以被推断出,你连括号都可以省略:</p> <pre> <code class="language-java">EventHandler<ActionEvent> listener = event -> System.out.println("Thanks for clicking!"); // Instead of (event) -> or (ActionEvent event) -> </code></pre> <h3><strong>修饰符</strong></h3> <p>你可以像对待其他方法一样,annotation,或者 使用 final 修饰符</p> <pre> <code class="language-java">(finalString name) -> ... (@NonNullString name) -> ... </code></pre> <p>永远不要定义 result 的类型,lambda 表达式总是从上下文中推断出来的:</p> <pre> <code class="language-java">(String first, String second) -> Integer.compare(first.length(), second.length()) </code></pre> <h3><strong>注意</strong></h3> <p>注意,在 lambda 表达式中,某些分支存在返回值,某些不存在返回值这样的情况是不允许的。</p> <p>如 (int x) -> {if (x >= 0) return 1; } 这样是非法的。</p> <h2><strong>函数式接口 (Functional Interfaces/SAM)</strong></h2> <p>要介绍 Java 中 lambda 表达式的实现,需要知道什么是 函数式接口。</p> <p>什么叫作函数式接口呢 (SAM)?</p> <p>函数式接口指的是只定义了唯一的抽象方法的接口(除了隐含的 Object 对象的公共方法), 因此最开始也就做 SAM 类型的接口(Single Abstract Method)。</p> <p>Lambda 表达式 <strong>向前兼容</strong> 这些接口。</p> <h3><strong>Comparable</strong></h3> <p>举个例子 Array.sort:</p> <pre> <code class="language-java">Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length())); </code></pre> <p>Array.sort() 方法收到一个实现了 Comparable 接口的实例。</p> <p>其实可以把 Lambda 表达式想象成一个方法,而非一个对象,一个可以传入一个接口的方法。</p> <h3><strong>OnClickListener</strong></h3> <p>再举个例子</p> <pre> <code class="language-java">button.setOnClickListener(event -> System.out.println("Thanks for clicking!")); </code></pre> <p>你看,是不是更易读了呢?</p> <p>Lambda 表达式能够向前兼容这些 interfaces, 太棒了! 那 Lambda 表达式还能干什么呢?</p> <p>实际上,将函数式接口转变成 lambda 表达式是你在 Java 中 <strong>唯一</strong> 能做的事情。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3db9a0811773f27909c690cebf8c189d.jpg"></p> <p>Why ?!!</p> <p>在其他的语言中,你可以定义一些方便的方法类型,但在 Java 中,你甚至不能将一个 Lambda 表达式赋值给类型为 Object 的变量,因为 Object 变量不是一个 Functional Interface。</p> <p>Java 的设计者们坚持使用熟悉的 interface 概念而不是为其引入新的 方法类型。</p> <p>(这里我还要为设计者点赞!谨慎的设计,一方面降低了初学者的门槛,一方面方便了高级用户的使用。对比 python2 和 python3,升级的不兼容让很多人一直停留在 python2)</p> <h2><strong>Method References</strong></h2> <p>能不能再简洁一点?有的时候我们所要做的事情不过是调用其他类中方法来处理事件。</p> <pre> <code class="language-java">button.setOnClickListener(event -> System.out.println(event)); </code></pre> <p>如果这样呢?</p> <pre> <code class="language-java">button.setOnAction(System.out::println); </code></pre> <p>表达式 System.out::println 属于一个方法引用(method reference), 相当于 lambda 表达式 x -> System.out.println(x)</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/d1bd5b4537ce3394d9941500ce2c2a00.jpg"></p> <p>再举个例子,如果你想对字符串不管大小写进行排序, 就可以这样写!</p> <pre> <code class="language-java">Arrays.sort(strings, String::compareToIgnoreCase) </code></pre> <p>如上所见 :: 操作符将方法名与实例或者类分隔开。总体来说,又如下的规则:</p> <ul> <li>object::instanceMethod</li> <li>Class::staticMethod</li> <li>Class::instanceMethod</li> </ul> <p>值得指出的是, this 和 super 关键字可以在其中使用:</p> <pre> <code class="language-java">classGreeter{ publicvoidgreet(){ System.out.println("Hello, world!"); } } </code></pre> <pre> <code class="language-java">classConcurrentGreeterextendsGreeter{ publicvoidgreet(){ Thread t = newThread(super::greet); t.start(); } } </code></pre> <h2><strong>构造方法引用 Constructor References</strong></h2> <p>跟上一个差不多,毕竟 <strong>构造方法</strong> 也是方法啊!!不过方法名字为 new 。</p> <p>但是!这个构造方法引用有一个牛逼的地方!</p> <p>你知道 Array 是不能使用范型的对吧!你没有办法创建一个类型为 T 的 Array 。 new T[n] 将会被覆盖为 new Object[n]。</p> <p>假设我们想要一个包含 buttons 的 Array。Stream interface 可以返回一个 Object array。</p> <pre> <code class="language-java">Object[] buttons = stream.toArray(); </code></pre> <p>不不不,我们可不想要 Object。Stream 库使用 构造方法引用解决了这个问题:</p> <pre> <code class="language-java">Button[] buttons = stream.toArray(Button[]::new); </code></pre> <p style="text-align:center"><img src="https://simg.open-open.com/show/7bc3bee46cfc446cc139fd74669ccee6.png"></p> <h2>变量作用域</h2> <p>注意到我们在题目中写着 闭包(closure), 实际上,闭包的定义是: 引用了自由变量的函数。</p> <p>在之前,如果需要在匿名类的内部引用外部变量,需要将外部变量定义为 final ,现在有了 lambda 表达式,你不必再这么做了。但同样需要保证外部的自由变量不能在 lambda 表达式中被改变。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/ac4909271d5b4ef11bd47f750e935b24.jpg"></p> <p>这是什么意思呢? 不需要定义为 final,也不能改?</p> <p>其实理解起来很简单,Java 8 中,不需要定义为 final ,但你其实可以直接把他当作 final,不要试图修改它就行了。</p> <p>即便你用内部类,现在也无需定义为 final 了。</p> <h2><strong>Default Methods</strong></h2> <p>由于历史原因,像是类似 Collection 这种接口,如果进行添加接口的话,那将会造成之前的代码出错。</p> <p>Java 想了一个一劳永逸的方法解决这个问题, 使用 default 修饰符来提供默认的实现</p> <p>比如 Collection 接口的源代码:</p> <pre> <code class="language-java">defaultvoidremove(){ thrownewUnsupportedOperationException("remove"); } </code></pre> <p>当没有 override remove 这个方法是,调用的时候返回 UnsupportedOperationException 错误。</p> <h2><strong>Static Methods in Interfaces</strong></h2> <p>Java 8 中,你可以在接口中添加静态方法了。 看起来好像并不符合接口的定义了。</p> <p>一般用来生成一个简单实现该 interface 的实例。</p> <p><strong>参考链接</strong>:</p> <ol> <li><a href="/misc/goto?guid=4959638966714068299" rel="nofollow,noindex">JSR 335: Lambda Expressions for the JavaTM Programming Language</a></li> <li><a href="/misc/goto?guid=4959725654225007566" rel="nofollow,noindex">Java 8 新特性概述</a></li> <li><a href="/misc/goto?guid=4959725654311898167" rel="nofollow,noindex">Lambda Expressions in Java 8</a></li> </ol> <p> </p> <p> </p> <p>来自:http://zhaochunqi.com/2016/11/15/java_lambda_expressions/</p> <p> </p>