Java Lambda表达式初探

JulieCremea 7年前
   <h2><strong>前言</strong></h2>    <p>Java 8已经发行两年多,但很多人仍然在使用JDK7。对企业来说,技术上谨慎未必是坏事,但对个人学习而言,不去学习新技术就很可能被技术抛弃。Java 8一个重要的变更是引入 <strong>Lambda表达式</strong> ( lambda expression ),这听起来似乎很牛,有种我虽然不知道Lambda表达式是什么,但我仍然觉得很厉害的感觉。不要怕,具体到语言层面上Lambda表达式不过是一种新的语法而已,有了它,Java将开启函数式编程的大门。</p>    <h2><strong>为什么需要Lambda表达式</strong></h2>    <p>不要纠结什么是 Lambda 表达式、什么是函数式编程。先来看一下Java 8新的语法特性带来的便利之处,相信你会过目不忘的。</p>    <p>在有 Lambda 表达式之前,要新建一个线程,需要这样写:</p>    <pre>  <code class="language-java">new Thread(new Runnable(){      @Override      public void run(){          System.out.println("Thread run()");      }  }).start();</code></pre>    <p>有 <em>Lambda</em> 表达式之后,则可以这样写:</p>    <pre>  <code class="language-java">new Thread(          () -> System.out.println("Thread run()")  ).start();</code></pre>    <p>正如你所见,之前无用的模板代码不见了!如上所示, <strong>Lambda表达式一个常见的用法是取代(某些)匿名内部类</strong> ,但Lambda表达式的作用不限于此。</p>    <h2><strong>Lambda表达式的原理</strong></h2>    <p>刚接触 Lambda 表达式可能觉得它很神奇: 不需要声明类或者方法的名字,就可以直接定义函数 。但其实这只是编译器给我们提供的一个小把戏,背后的原理并不难理解。下面是 <em>Lambda</em> 表达式几种可能的书写形式:</p>    <pre>  <code class="language-java">Runnable run = () -> System.out.println("Hello World");// 1  ActionListener listener = event -> System.out.println("button clicked");// 2  Runnable multiLine = () -> {// 3      System.out.println("Hello ");      System.out.println("World");  };  BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4  BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5</code></pre>    <p>通过上例可以发现:</p>    <ul>     <li> <p>Lambda表达式是有类型的,赋值操作的左边就是类型。Lambda表达式的类型实际上是 <strong>对应接口的类型</strong> 。</p> </li>     <li> <p>Lambda表达式可以包含多行代码,需要用大括号把代码块括起来,就像写函数体那样。</p> </li>     <li> <p>大多数时候,Lambda表达式的参数表可以省略类型,就像代码2和5那样。这得益于javac的 <strong>类型推导</strong> 机制,编译器可以根据上下文推导出类型信息。</p> </li>    </ul>    <p>实际上每个 <strong>Lambda表达式都是原来匿名内部类的简写形式</strong> ,该内部类实现了某个 <strong>函数接口</strong> ( Functional Interface )。 所谓函数接口是指*添加了@FunctionalInterface标注 ,并且内部只有一个接口函数的接口*。Java是强类型语言,无论有没有显式指明,每个变量和对象都必须有明确的类型,没有显式指定的时候编译器会尝试确定类型。 <strong>Lambda表达式的类型就是对应函数接口的类型</strong> 。</p>    <h2><strong>Lambda表达式和Stream</strong></h2>    <p>Lambda表达式的另一个重要用法,是和Stream一起使用。Stream is a sequence of elements supporting sequential and parallel aggregate operations。Stream就是一组元素的序列,支持对这些元素进行各种操作,而这些操作是通过 <em>Lambda</em> 表达式指定的。可以把Stream看作Java Collection的一种视图,就像迭代器是容器的一种视图那样(但 <em>Stream</em> 不会修改容器中的内容)。下面例子展示了 <em>Stream</em> 的常见用法。</p>    <h2><strong>例子1</strong></h2>    <p>假设需要从一个字符串列表中选出以数字开头的字符串并输出,Java 7之前需要这样写:</p>    <pre>  <code class="language-java">List<String> list = Arrays.asList("1one", "two", "three", "4four");  for(String str : list){      if(Character.isDigit(str.charAt(0))){          System.out.println(str);      }  }</code></pre>    <p>而Java 8就可以这样写:</p>    <pre>  <code class="language-java">List<String> list = Arrays.asList("1one", "two", "three", "4four");  list.stream()// 1.得到容器的Steam      .filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串      .forEach(str -> System.out.println(str));// 3.输出字符串</code></pre>    <p>上述代码首先1. 调用 List.stream() 方法得到容器的 <em>Stream</em> ,2. 然后调用 filter() 方法过滤出以数字开头的字符串,3. 最后调用 forEach() 方法输出结果。</p>    <p>使用Stream有两个明显的好处:</p>    <ol>     <li>减少了模板代码,只用Lambda表达式指明所需操作,代码语义更加明确、便于阅读。</li>     <li>将外部迭代改成了Stream的内部迭代,方便了JVM本身对迭代过程做优化(比如可以并行迭代)。</li>    </ol>    <h2><strong>例子2</strong></h2>    <p>假设需要从一个字符串列表中,选出所有不以数字开头的字符串,将其转换成大写形式,并把结果放到新的集合当中。Java 8书写的代码如下:</p>    <pre>  <code class="language-java">List<String> list = Arrays.asList("1one", "two", "three", "4four");  Set<String> newList =          list.stream()// 1.得到容器的Stream          .filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串          .map(String::toUpperCase)// 3.转换成大写形式          .collect(Collectors.toSet());// 4.生成结果集</code></pre>    <p>上述代码首先1. 调用 List.stream() 方法得到容器的 <em>Stream</em> ,2. 然后调用 filter() 方法选出不以数字开头的字符串,3. 之后调用 map() 方法将字符串转换成大写形式,4. 最后调用 collect() 方法将结果转换成 Set 。这个例子还向我们展示了 <strong>方法引用</strong> ( method references ,代码中标号3处)以及 <strong>收集器</strong> ( Collector ,代码中标号4处)的用法,这里不再展开说明。</p>    <p>通过这个例子我们看到了 <em>Stream</em> 链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个 <em>Stream</em> 的操作都会立即执行。 <em>Stream</em> 的操作分成两类,一类是 <strong>中间操作</strong> ( intermediate operations ),另一类是 <strong>结束操作</strong> ( terminal operation ),只有结束操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对 <em>Stream</em> 进行某种操作。这意味着可以在 <em>Stream</em> 上通过关联多种操作,但最终只需要一次迭代。如果你熟悉Spark RDD,对此应该并不陌生。</p>    <h2><strong>结语</strong></h2>    <p>Java 8引入 Lambda 表达式,从此打开了函数式编程的大门。如果你之前不了解函数式编程,不必纠结于这个概念。编程过程中简洁明了的书写形式以及强大的 <em>Stream API</em> 会让你很快熟悉 <em>Lambda</em> 表达式的。</p>    <p>本文只对 Java Lambda 表达式的基本介绍,希望能够激发读者对Java函数式编程的兴趣。如果本文能够让你觉得 Lambda 表达式很好玩,函数式编程很有趣,并产生了进一步学习的欲望,那就再好不过了。文末参考文献中列出了一些有用的资源。</p>    <h2><strong>参考文献</strong></h2>    <p><a href="/misc/goto?guid=4958962222010933771" rel="nofollow,noindex">http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html</a></p>    <p><a href="/misc/goto?guid=4959664587425097235" rel="nofollow,noindex">https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html</a></p>    <p><a href="/misc/goto?guid=4959671977713266591" rel="nofollow,noindex">https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html</a></p>    <p><a href="/misc/goto?guid=4959719771179324917" rel="nofollow,noindex">http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html</a></p>    <p><a href="/misc/goto?guid=4959719771261471185" rel="nofollow,noindex">http://www.slideshare.net/trishagee/refactoring-to-java-8-devoxx-uk</a></p>    <p><a href="/misc/goto?guid=4959719771346149902" rel="nofollow,noindex">《Java 8函数式编程 [英]沃伯顿》</a></p>    <p><a href="/misc/goto?guid=4959719771428087941" rel="nofollow,noindex">https://www.oracle.com/javaone/speakers.html#gee</a></p>    <p> </p>    <p>来自:http://www.cnblogs.com/CarpenterLee/p/5936664.html</p>    <p> </p>