名词王国里的新政-解读Java8之lambda表达式

前几天在reddit上看到Java8 M8 Developer Preview版本已经发布了,难免想要尝鲜一把。Developer Preview版本已经全部Feature都完成了,Java8的特性能够在这里看到http://openjdk.java.net/projects/jdk8/features,下载地址:http://jdk8.java.net/download.html。Java8最值得期待的就是lambda表达式了,本文就将带你体验lambda表达式,并进行比较深刻的解析。html

下载及配置

Intellij IDEA已经完美支持Java8了。首先打开Project Structure,在Project里设置新的JDK路径,并设置Modules=>Source=>Language Level为8.0便可。java

如今咱们可使用Java8编写程序了!可是当咱们开开心心编写完,享受到高级的lambda表达式后,运行程序,会提示:java: Compilation failed: internal java compiler error!这是由于javacc的版本还不对,在Compiler=>Java Compiler里将项目对应的javacc版本选为1.8便可。git

什么?你说你用Eclipse?好像目前尚未稳定版!想尝鲜的,能够看看这个地址http://stackoverflow.com/questions/13295275/programming-java-8-in-eclipse,大体是先checkout Eclipse JDT的beta java8分支,而后在Eclipse里运行这个项目,从而启动一个支持java8的Eclipse…不过应该难不倒做为geek的你吧!github

体验lambda表达式

好了,咱们开始体验Java8的新特性-lambda表达式吧!如今咱们的匿名类能够写成这样子了:编程

<!-- lang: java -->
    new Thread(() -> {
        System.out.println("Foo");
    }).start();

而以前的写法只能是这样子:闭包

<!-- lang: java -->
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Foo");
        }
    }).start();

这样一看,咱们彷佛就是匿名类写起来简单了一点啊?而第二种方法,借助便捷的IDE,好像编写效率也没什么差异?博主开始也是这样认为,仔细学习以后,才知道其中的奥妙所在!app

这里有一个重要的信息,就是**()->{}这里表明一个函数,而非一个对象。**可能这么说比较抽象,咱们仍是代码说话吧:dom

<!-- lang: java -->
public class LambdaTest {

    private static void bar(){
        System.out.println("bar");
    }

    public static void main(String[] args) {
        new Thread(LambdaTest::bar).start();
    }

}

看懂了么?这里LambdaTest::bar表明一个函数(用C++的同窗笑了),而new Thread(Runnable runnable)的参数,能够接受是一个函数做为参数!eclipse

是否是以为很神奇,颠覆了Java思惟?在剖析原理之前,博主暂且卖个关子,咱们先来说讲什么是lambda表达式。编程语言

什么是lambda表达式

lambda表达式的由来

絮叨几句,现代编程语言的lamdba表达式都来自1930年代初,阿隆佐·邱奇(Alonzo Church)提出的λ演算(Lambda calculus)理论。λ演算的核心思想就是“万物皆函数”。一个λ算子即一个函数,其通常形式是λx.x + 2。一个λ算子能够做为另外一个λ算子的输入,从而构建一个高阶的函数。λ演算是函数式编程的鼻祖,大名鼎鼎的编程语言Lisp就是基于λ演算而创建。用过Lisp的应该都清楚,它的语法很简单,可是却有包容万物的能力。

可能搞计算机的对邱奇比较陌生,可是提起和邱奇同时代的另一我的,你们就会以为如雷贯耳了,那就是阿兰·图灵。邱奇成名的时候,图灵仍是个大学生。邱奇和图灵一块儿发表了邱奇-图灵论题,并分别提出了λ演算和图灵机,加上哥德尔提出的递归函数一块儿,在理论上肯定了什么是可计算性。至于什么是可计算性,其实博主也说不清楚,可是现代全部计算机程序语言,均可以认为是从三种之一发展而来,并与之等价的。仅此一点,其影响深远,可想而知。当年教咱们《计算理论》的是一个德高望重的教授,人称宋公,每次讲到那个辉煌的年代,老是要停下来,神情专一的感叹一句:“伟大啊!”想一想确实挺伟大,人家图灵大学时候就奠基了现代计算机的基础,而咱们那会大概还在打DOTA…

附上大神们的照片,你们感觉一下:

turing etc.

现代编程语言中的lambda表达式

好了扯远了,神游过了那个伟大的时代,咱们继续思考如何编代码作需求吧…

现代语言的lambda表达式,大概具有几个特征(博主本身概括的,若有不严谨,欢迎指正):

  1. 函数可做为输入;
  2. 函数可做为输出;
  3. 函数可做用在函数上,造成高阶函数。
  4. 函数支持lambda格式的定义。

其实有了一、2,3也就是顺水推舟的事情,而4其实没有太大的必要性,由于通常语言都有本身的函数定义方式,4仅仅是做为一种补充。固然实现了4的语言,通常都会说:“你看我实现了lambda表达式!”(望向Java8和Python同窗)

在Java8中使用lambda表达式

FunctionalInterface

Java中的lambda没法单独出现,它须要一个接口来盛放。这个接口必须使用@FunctionalInterface做为注解,而且只有一个未实现的方法。等等,什么叫接口中未实现的方法?难道接口中还能够有已实现的方法?恭喜你,猜对了!Java8的接口也能够写实现了!是否是以为Interface和AbstractClass更加傻傻分不清楚了?可是AbstractClass是没法使用@FunctionalInterface注解的,官方的解释是为了防止AbstractClass的构造函数作一些事情,可能会致使一些调用者意料不到的事情发生。

好了,咱们来看一点代码,Runnable接口如今变成了这个样子:

<!-- lang: java -->
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

这里咱们能够将任意无参数的lambda表达式赋值给Runnable:

<!-- lang: java -->
    Runnable runnable = () -> {
        System.out.println("Hello lambda!");
    };
    runnable.run();

lambda表达式本质上是一个函数,因此咱们还能够用更加神奇的赋值:

<!-- lang: java -->
public class HelloLambda {

    private static void hellolambda() {
        System.out.println("Hello lambda!");
    }

    public static void main(String[] args) {
        Runnable runnable = HelloLambda::hellolambda;
        runnable.run();
    }
}

这里看到这里,你们大概明白了,lambda表达式其实只是个幌子,更深层次的含义是:函数在Java里面能够做为一个实体进行表示了。这就意味着,在Java8里,函数既能够做为函数的参数,也能够做为函数的返回值,即具备了lambda演算的全部特性。

Function系列API

看到这里,可能你们会有疑问?什么样的函数和什么样的lambda表达式属于同一类型?答案是参数和返回值的类型共同决定函数的类型。例如Runnable的run方法不接受参数,也没有返回值,那么Runnable接口则能够用任意没有参数且没有返回值的函数来赋值。这样概念上来讲,Runnable表示的含义就从一个对象变成了一个方法

这一点在Java8中的java.util.function包里的代码获得了验证。以最具备表明性的Function接口为例:

<!-- lang: java -->
@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

}

有了Function,咱们能够这样写:

<!-- lang: java -->
    Function<Integer,String> convert = String::valueOf;
    String s = convert.apply(1);

这个东东是否是很像Javascript中的函数对象?

惋惜的是,这里的Function算是个半成品,它只能表示一个有单个参数,并有非void返回值的函数。像System.out.println()这种方法,由于返回值为void,是没法赋值为Function的!

怎么办?java.util.function包提供了一个不那么完美的解决方案:多定义几个FunctionalInterface呗!

因而,在Java8里有了:

  • Supplier: 没有参数,只有返回值的函数
  • Consumer: 一个参数,返回值为void的函数
  • BiFunction: 两个参数,一个返回值的函数
  • BiConsumer: 两个参数,没有返回值的函数
  • ...

对于这些个API,我也没有什么力气吐槽了,反正我也想不出更好的方法…你们趁机,多学几个单词吧,嗯。

总结:名词王国的新政

相信不少同窗都看过这篇著名的文章:名词王国里的死刑。这篇文章吐槽了Java里,动词(方法)在Java里老是要依附于某个名词(对象/类)存在。

如今动词在名词王国终于有了一个身份了。固然这个动词须要先取得一个名词的身份(FunctionInterface),而后才能名正言顺的幸存下来。好在Oracle国王预先为他们留了一些身份(Function、Consumer、Supplier、BiFunction...),因此大多数动词都已经找到了本身的位置。System.out.println(String)如今是Consumer<String>了,String.valueOf(Integer)如今是Function<Integer,String>了,Collection.size()如今是Supplier<Integer>了…。要为一些较长参数的方法获取一个身份,也是挺容易的(定义一个新的FunctionInterface接口)。

我相信这个影响是深远的。例以下面一段代码,能够同一行代码将一个List<Integer>转换成一个List<String>:

<!-- lang: java -->
List<String> strings = intList.stream().map(String::valueOf).collect(Collectors.<String>toList());

固然问题也存在。由于包含了闭包等因素,FunctionInterface的序列化/反序列化会是一个至关复杂的事情。熟悉Java的开发者,也会由于lambda的引入,带来了一些困惑。俗话说活到老学到老,我却是不介意这个新功能,你说呢?

参考文献:

  1. http://blog.sciencenet.cn/blog-414166-628109.html
  2. http://www.global-sci.org/mc/issues/3/no2/freepdf/80s.pdf
  3. http://en.wikipedia.org/wiki/Lambda_calculus

本系列文章还有余下几部分,敬请期待:

lambda表达式与闭包

Java8 lambda表达式原理分析

相关文章
相关标签/搜索