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的局部变量,没法对控制流进行抽象等等等等。编程
因而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
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); }
为了可以方便、快捷、幽雅的建立出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();
对于给定的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:
而且lambda表达式的参数类型能够从目标类型中得出
Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);
在上面的例子里,编译器能够推导出s1和s2的类型是String。此外,当lambda的参数只有一个并且它的类型能够被推导得知时,该参数列表外面的括号能够被省略:
FileFilter java = f -> f.getName().endsWith(".java");
有时候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());;