java8之lambda介绍

Java是一门面向对象编程语言。面向对象编程语言和函数式编程语言中的基本元素(Basic Values)均可以动态封装程序行为:面向对象编程语言使用带有方法的对象封装行为,函数式编程语言使用函数封装行为。但这个相同点并不明显,由于Java的对象每每比较“重量级”:实例化一个类型每每会涉及不一样的类,并须要初始化类里的字段和方法。因而对于有些Java对象,只是对单个函数的封装。 下面这个典型用例:Java API中定义了一个接口(通常被称为回调接口),用户经过提供这个接口的实例来传入指定行为,例如:java

public interface ActionListener {
  void actionPerformed(ActionEvent e);
}

这里并不须要专门定义一个类来实现ActionListener接口,由于它只会在调用处被使用一次。用户通常会使用匿名类型把行为内联(inline):express

button.addActionListener(new ActionListener) {
  public void actionPerformed(ActionEvent e) {
    ui.dazzle(e.getModifiers());
  }
}

显然匿名内部类并非一个好的选择,其语法过于冗余,匿名类中的this和变量名容易令人产生误解,类型载入和实例建立语义不够灵活,没法捕获非final的局部变量,没法对控制流进行抽象等等等等。编程

函数式接口(Functional interfaces)

因而java8引入了一个函数式接口的概念。理解Functional Interface(函数式接口,如下简称FI)是学习Java8 Lambda表达式的关键所在,因此放在最开始讨论。FI的定义其实很简单:任何接口,若是只包含惟一一个抽象方法,那么它就是一个FI。数据结构

Java8提供了@FunctionalInterface注解。举个简单的例子,Runnable接口就是一个FI,下面是它的源代码:app

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

咱们并不须要额外的工做来声明一个接口是函数式接口:编译器会根据接口的结构自行判断(判断过程并不是简单的对接口方法计数:一个接口可能冗余的定义了一个Object已经提供的方法,好比toString(),或者定义了静态方法或默认方法,这些都不属于函数式接口方法的范畴)。不过API做者们能够经过@FunctionalInterface注解来显式指定一个接口是函数式接口(以免无心声明了一个符合函数式标准的接口),加上这个注解以后,编译器就会验证该接口是否知足函数式接口的要求。编程语言

下面是Java SE 7中已经存在的函数式接口:ide

java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.beans.PropertyChangeListener

Java8除了给Runnable,Comparator等接口打上了@FunctionalInterface注解以外,还预约义了一大批新的FI。这些接口都在java.util.function包里,例如:函数式编程

Predicate<T>——接收T对象并返回boolean
Consumer<T>——接收T对象,不返回值
Function<T, R>——接收T对象,返回R对象
Supplier<T>——提供T对象(例如工厂),不接收值
UnaryOperator<T>——接收T对象,返回T对象
BinaryOperator<T>——接收两个T对象,返回T对象

下面简单介绍几个:函数

@FunctionalInterface
//Predicate用来判断一个对象是否知足某种条件,好比,单词是否由六个以上字母组成:
public interface Predicate<T> {
    boolean test(T t);
}
words.stream().filter(word -> word.length() > 6).count();
@FunctionalInterface
//Function表示接收一个参数,并产生一个结果的函数:
public interface Function<T, R> {
    R apply(T t);
}
//下面的例子将集合里的每个整数都乘以2:
ints.stream().map(x -> x * 2);
@FunctionalInterface
//Consumer表示对单个参数进行的操做,前面例子中的forEach()方法接收的参数就是这种操做:
public interface Consumer<T> {
    void accept(T t);
}

Lambda表达式(lambda expressions)

为了可以方便、快捷、幽雅的建立出FI的实例,Java8提供了Lambda表达式这颗语法糖。下面我用一个例子来介绍Lambda语法。假设咱们想对一个List<String>按字符串长度进行排序,那么在Java8以前,能够借助匿名内部类来实现:性能

List<String> words = Arrays.asList("apple", "banana", "pear");
  words.sort(new Comparator<String>() {
    @Override
    public int compare(String w1, String w2) {
        return Integer.compare(w1.length(), w2.length());
    }
  });

上面的匿名内部类简直能够用丑陋来形容,惟一的一行逻辑被五行垃圾代码淹没。根据前面的定义(并查看Java源代码)可知,Comparator是个FI,因此,能够用Lambda表达式来实现:

List<String> words = Arrays.asList("apple", "banana", "pear");
  words.sort((String w1, String w2) -> {
      return Integer.compare(w1.length(), w2.length());
  });

lambda表达式的语法由参数列表、箭头符号->和函数体组成。函数体既能够是一个表达式,也能够是一个语句块:

表达式:表达式会被执行而后返回执行结果。

语句块:语句块中的语句会被依次执行,就像方法中的语句同样——

return语句会把控制权交给匿名方法的调用者

break和continue只能在循环中使用

若是函数体有返回值,那么函数体内部的每一条路径都必须返回值

表达式函数体适合小型lambda表达式,它消除了return关键字,使得语法更加简洁。

下面是一些出如今语句中的lambda表达式:

FileFilter java = (File f) -> f.getName().endsWith("*.java");
String user = doPrivileged(() -> System.getProperty("user.name"));
new Thread(() -> {
  connectToService();
  sendNotification();
}).start();

目标类型(Target typing)

对于给定的lambda表达式,其类型都由上下文推导而出,例如:

ActionListener l = (ActionEvent e) -> ui.dazzle(e.getModifiers());  //ActionListener的实例
Callable<String> c = () -> "done";                  //Callable的实例
PrivilegedAction<String> a = () -> "done";              //PrivilegedAction的实例

lambda表达式对目标类型也是有要求的。编译器会检查lambda表达式的类型和目标类型的方法签名(method signature)是否一致。当且仅当下面全部条件均知足时,lambda表达式才能够被赋给目标类型T:

  • T是一个函数式接口
  • lambda表达式的参数和T的方法参数在数量和类型上一一对应
  • lambda表达式的返回值和T的方法返回值相兼容(Compatible)
  • lambda表达式内所抛出的异常和T的方法throws类型相兼容

而且lambda表达式的参数类型能够从目标类型中得出

Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);

在上面的例子里,编译器能够推导出s1和s2的类型是String。此外,当lambda的参数只有一个并且它的类型能够被推导得知时,该参数列表外面的括号能够被省略:

FileFilter java = f -> f.getName().endsWith(".java");

方法引用(Method References)

有时候Lambda表达式的代码就只是一个简单的方法调用而已,遇到这种状况,Lambda表达式还能够进一步简化为 方法引用(Method References) 。一共有四种形式的方法引用,第一种引用 静态方法 ,例如:

List<Integer> ints = Arrays.asList(1, 2, 3);
ints.sort(Integer::compare);

第二种引用 某个特定对象的实例方法 ,例如前面那个遍历并打印每个word的例子能够写成这样:

words.forEach(System.out::println);

第三种引用 某个类的实例方法,例如:

words.stream().map(word -> word.length()); // lambda
words.stream().map(String::length); // method reference

第四种引用类的 构造函数 ,例如:

// lambda
words.stream().map(word -> {
    return new StringBuilder(word);
});
// constructor reference
words.stream().map(StringBuilder::new);

总结

咱们在设计lambda时的一个重要目标就是新增的语言特性和库特性可以无缝结合(designed to work together)。接下来,咱们经过一个实际例子(按照姓对名字列表进行排序)来演示这一点: 好比说下面的代码:

List<Person> people = ...
Collections.sort(people, new Comparator<Person>() {
  public int compare(Person x, Person y) {
    return x.getLastName().compareTo(y.getLastName());
  }
})

冗余代码实在太多了!有了lambda表达式,咱们能够去掉冗余的匿名类:

Collections.sort(people,
                 (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));

尽管代码简洁了不少,但它的抽象程度依然不好:开发者仍然须要进行实际的比较操做(并且若是比较的值是原始类型那么状况会更糟),因此咱们要借助Comparator里的comparing方法实现比较操做:

Collections.sort(people, Comparator.comparing((Person p) -> p.getLastName()));

在类型推导和静态导入的帮助下,咱们能够进一步简化上面的代码:

Collections.sort(people, comparing(p -> p.getLastName()));

咱们注意到这里的lambda表达式其实是getLastName的代理(forwarder),因而咱们能够用方法引用代替它:

Collections.sort(people, comparing(Person::getLastName));

最后,使用Collections.sort这样的辅助方法并非一个好主意:它不但使代码变的冗余,也没法为实现List接口的数据结构提供特定(specialized)的高效实现,并且因为Collections.sort方法不属于List接口,用户在阅读List接口的文档时不会察觉在另外的Collections类中还有一个针对List接口的排序(sort())方法。 默认方法能够有效的解决这个问题,咱们为List增长默认方法sort(),而后就能够这样调用:

people.sort(comparing(Person::getLastName));;

此外,若是咱们为Comparator接口增长一个默认方法reversed()(产生一个逆序比较器),咱们就能够很是容易的在前面代码的基础上实现降序排序。

people.sort(comparing(Person::getLastName).reversed());;
相关文章
相关标签/搜索