Java8学习笔记(1) -- 从函数式接口说起

转载 2017年01月03日 09:55:38

http://blog.csdn.net/zxhoo/article/details/38349011

希望本文能够成为Java8 Lambda表达式的快速入门指南。

函数式接口

理解Functional Interface(函数式接口,以下简称FI)是学习Java8 Lambda表达式的关键所在,所以放在最开始讨论。FI的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那么它就是一个FI。为了让编译器帮助我们确保一个接口满足FI的要求(也就是说有且仅有一个抽象方法),Java8提供了@FunctionalInterface注解。举个简单的例子,Runnable接口就是一个FI,下面是它的源代码:

[java] view plain copy
  1. @FunctionalInterface  
  2. public interface Runnable {  
  3.     public abstract void run();  
  4. }  

Lambda语法糖

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

[java] view plain copy
  1. List<String> words = Arrays.asList("apple""banana""pear");  
  2. words.sort(new Comparator<String>() {  
  3.   
  4.     @Override  
  5.     public int compare(String w1, String w2) {  
  6.         return Integer.compare(w1.length(), w2.length());  
  7.     }  
  8.   
  9. });  

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

[java] view plain copy
  1. words.sort((String w1, String w2) -> {  
  2.     return Integer.compare(w1.length(), w2.length());  
  3. });  
代码变短了好多!仔细观察就会发现,Lambda表达式,很像一个匿名的方法,只是圆括号内的参数列表和花括号内的代码被->分隔开了。垃圾代码写的越少,我们就有越多的时间去写真正的逻辑代码,不是吗?是的!圆括号里的参数类型是可以省略的:

[java] view plain copy
  1. words.sort((w1, w2) -> {  
  2.     return Integer.compare(w1.length(), w2.length());  
  3. });  
如果Lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化:

[java] view plain copy
  1. words.sort(  
  2.     (w1, w2) -> Integer.compare(w1.length(), w2.length())  
  3. );  
注意,表达式后面是没有分号的!如果只有一个参数,那么包围参数的圆括号可以省略:

[java] view plain copy
  1. words.forEach(word -> {  
  2.     System.out.println(word);  
  3. });  
如果表达式不需要参数呢?好吧,那也必须有圆括号,例如:

[java] view plain copy
  1. Executors.newSingleThreadExecutor().execute(  
  2.     () -> {/* do something. */// Runnable  
  3. );  

方法引用

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

[java] view plain copy
  1. List<Integer> ints = Arrays.asList(123);  
  2. ints.sort(Integer::compare);  
第二种引用某个特定对象的实例方法,例如前面那个遍历并打印每一个word的例子可以写成这样:

[java] view plain copy
  1. words.forEach(System.out::println);  
第三种引用某个类的实例方法,例如:

[java] view plain copy
  1. words.stream().map(word -> word.length()); // lambda  
  2. words.stream().map(String::length); // method reference  

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

[java] view plain copy
  1. // lambda  
  2. words.stream().map(word -> {  
  3.     return new StringBuilder(word);  
  4. });  
  5. // constructor reference  
  6. words.stream().map(StringBuilder::new);  

什么时候用Lambda表达式

既然Lambda表达式这么好用,那么,可以在哪些地方使用呢?如果你真正明白了什么是FI(很容易),应该立刻就能给出答案:任何可以接受一个FI实例的地方,都可以用Lambda表达式。比如,虽然上面给出的例子都是把Lambda表达式当作方法参数传递,但实际上你也可以定义变量:

[java] view plain copy
  1. Runnable task = () -> {  
  2.     // do something  
  3. };  
  4.   
  5. Comparator<String> cmp = (s1, s2) -> {  
  6.     return Integer.compare(s1.length(), s2.length());  
  7. };  

预定义函数式接口

Java8除了给Runnable,Comparator等接口打上了@FunctionalInterface注解之外,还预定义了一大批新的FI。这些接口都在java.util.function包里,下面简单介绍其中的几个。

[java] view plain copy
  1. @FunctionalInterface  
  2. public interface Predicate<T> {  
  3.     boolean test(T t);  
  4. }  
Predicate用来判断一个对象是否满足某种条件,比如,单词是否由六个以上字母组成:

[java] view plain copy
  1. words.stream()  
  2.     .filter(word -> word.length() > 6)  
  3.     .count();  

Function表示接收一个参数,并产生一个结果的函数:

[java] view plain copy
  1. @FunctionalInterface  
  2. public interface Function<T, R> {  
  3.     R apply(T t);  
  4. }  
下面的例子将集合里的每一个整数都乘以2:

[java] view plain copy
  1. ints.stream().map(x -> x * 2);  

Consumer表示对单个参数进行的操作,前面例子中的forEach()方法接收的参数就是这种操作:

[java] view plain copy
  1. @FunctionalInterface  
  2. public interface Consumer<T> {  
  3.     void accept(T t);  
  4. }  

对原有API的增强

为了充分发挥Lambda的威力,Java8对很多老的类库进行了增强,给它们配备了Lambda武器。比如前面例子中用到的forEach()方法,实际上是添加到Iterable接口中的。而多次出现的stream()方法,则是添加在了Collection接口里。用过Ruby,Scala,Groovy等语言的Java程序员,可能已经对在这些语言里很好实现的外部迭代器模式垂涎很久了。虽然Google的Guava可以在一定程度上弥补Java的这种缺陷,但是Java8的Lambda才真正让Java朝着函数式编程迈进了一大步。

接口的默认方法

细心的读者可能会发现一个问题,给Iterable和Collection等接口增加方法,岂不是会破坏接口的向后兼容性?是的,为了保证API的向后兼容性,Java8对接口的语法进行了较大的调整,增加了默认方法(Default Methods)。下面是forEach()方法的实现代码:

[java] view plain copy
  1. public interface Iterable<T> {  
  2.     ...  
  3.     default void forEach(Consumer<? super T> action) {  
  4.         Objects.requireNonNull(action);  
  5.         for (T t : this) {  
  6.             action.accept(t);  
  7.         }  
  8.     }  
  9.     ...  
  10. }  

接口的静态方法

除了抽象方法和默认方法,从Java8开始,接口也可以有静态(static)方法了。有了这个语法,我们就可以把和接口相关的帮助方法(Helper Methods)直接定义在接口里了。比如Function接口就定义了一个工厂方法indentity():

[java] view plain copy
  1. public interface Function<T, R> {  
  2.     ...  
  3.     /** 
  4.      * Returns a function that always returns its input argument. 
  5.      * 
  6.      * @param <T> the type of the input and output objects to the function 
  7.      * @return a function that always returns its input argument 
  8.      */  
  9.     static <T> Function<T, T> identity() {  
  10.         return t -> t;  
  11.     }  
  12. ...  
  13. }  

变量捕获

内部类一样,Lambda也可以访问外部(词法作用域)变量,规则基本一样。Java8之前,内部类只能访问final类型的变量,Java8放宽了这种限制,只要变量实际上不可变(effectively final)就可以。换句话说,如果你给变量加上final关键字编译器也不报错,那么去掉final关键字后,它就是effectively final的。看下面的例子:

[java] view plain copy
  1. int a = 100;  
  2. Runnable x = new Runnable() {  
  3.   
  4.     @Override  
  5.     public void run() {  
  6.         System.out.println(a);  
  7.     }  
  8.       
  9. };  
在Java8之前,a必须是final的才能被x看到。下面用Lambda表达式重写上面的例子:

[java] view plain copy
  1. int a = 100;  
  2. Runnable x = () -> {  
  3.     System.out.println(a);  
  4. };  

结论

可以看到,为了支持Lambda表达式,Java8对Java语言做了很大的调整。但Lambda表达式并非只是Java语法糖,而是由编译器和JVM共同配合来实现的,这一点我会在下一篇文章里详细介绍。



相关文章推荐

Java8学习笔记(1) -- 从函数式接口说起

Java8 Lambda表达式的快速入门指南

Java 8 in Action (1) -- 函数式接口

Java8中常用的函数式接口 函数接口 函数描述符 原始类型特化 Predicate T->boolean Int… Consumer T->void Int… ...

java 8 新特性(1.函数式接口 - Functional Interface)

1.函数式接口(Functional Interface): 所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。这种类型的接口也称为SAM接口,即Single Abst...

Java8-函数式接口理解及测试

1.  函数式接口的理解   根据重构的思想,需要把容易变化的模块进行抽象并封装起来,从这个点来看,Java8新引入的函数式接口就是基于这个思想进行设计的。 2. 函数式接口定义  ...

java8 函数式接口(FunctionalInterface) [一]

函数式接口代表的一种契约, 一种对某个特定函数类型的契约。 在它出现的地方,实际期望一个符合契约要求的函数。 Lambda表达式不能脱离上下文而存在,它必须要有一个明确的目标类型,而这个目标类型就是某...
  • Mr_rain
  • Mr_rain
  • 2017年06月25日 17:25
  • 159

java8入门(二) --::和默认函数式接口

在上一篇λ表达式的文章中有提到,一个接口未实现方法,有3种途径可以使用,实际上还有一种途径可以使用,也就是java8推出的::,这个是什么呢,就叫它方法引用吧。来看下是怎么操作的先。比如有一个与接口毫...

java8新特性lambda表达式, 函数式接口以及Steam流和新的日期时间例子代码

java8新特性lambda表达式, 函数式接口以及Steam流和新的日期时间例子代码 package com.zheng; import java.io.IOException; impor...

Java8的四大核心函数式接口

前言在学习一段时间后会不会有这样的疑问—难道我要用Lambda表达式的时候总要自己写函数式接口吗? 当然不是这样了,Java8里面提供了四大核心的函数式供我们使用!四大核心函数式接口测试代码 ...

Java8中的函数式接口及使用

一.简述函数式接口是仅制定一个抽象方法的接口, 也可以包含一个或多个静态或默认方法. 二.示例形式1:package insping;public interface MyInterface { ...

java8 函数式接口(FunctionalInterface) [二]

话接上章,这篇介绍函数式接口的以下两点: 声明异常 泛型及继承关系 声明异常 函数式接口的抽象方法可以声明 可检查异常(checked exception)。 @FunctionalInter...
  • Mr_rain
  • Mr_rain
  • 2017年06月25日 21:21
  • 131
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java8学习笔记(1) -- 从函数式接口说起
举报原因:
原因补充:

(最多只允许输入30个字)