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

函数式接口

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

  1. @FunctionalInterface
  2. public interface Runnable {
  3. public abstract void run();
  4. }

Lambda语法糖

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

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

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

  1. words.sort((String w1, String w2) -> {
  2. return Integer.compare(w1.length(), w2.length());
  3. });

代码变短了好多!仔细观察就会发现,Lambda表达式,很像一个匿名的方法,只是圆括号内的参数列表和花括号内的代码被->分隔开了。垃圾代码写的越少,我们就有越多的时间去写真正的逻辑代码,不是吗?是的!圆括号里的参数类型是可以省略的:

  1. words.sort((w1, w2) -> {
  2. return Integer.compare(w1.length(), w2.length());
  3. });

如果Lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化:

  1. words.sort(
  2. (w1, w2) -> Integer.compare(w1.length(), w2.length())
  3. );

注意,表达式后面是没有分号的!如果只有一个参数,那么包围参数的圆括号可以省略:

  1. words.forEach(word -> {
  2. System.out.println(word);
  3. });

如果表达式不需要参数呢?好吧,那也必须有圆括号,例如:

  1. Executors.newSingleThreadExecutor().execute(
  2. () -> {/* do something. */} // Runnable
  3. );

方法引用

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

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

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

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

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

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

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

  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表达式当作方法参数传递,但实际上你也可以定义变量:

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

预定义函数式接口

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

  1. @FunctionalInterface
  2. public interface Predicate<T> {
  3. boolean test(T t);
  4. }

Predicate用来判断一个对象是否满足某种条件,比如,单词是否由六个以上字母组成:

  1. words.stream()
  2. .filter(word -> word.length() > 6)
  3. .count();

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

  1. @FunctionalInterface
  2. public interface Function<T, R> {
  3. R apply(T t);
  4. }

下面的例子将集合里的每一个整数都乘以2:

  1. ints.stream().map(x -> x * 2);

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

  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()方法的实现代码:

  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():

  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的。看下面的例子:

  1. int a = 100;
  2. Runnable x = new Runnable() {
  3. @Override
  4. public void run() {
  5. System.out.println(a);
  6. }
  7. };

在Java8之前,a必须是final的才能被x看到。下面用Lambda表达式重写上面的例子:

  1. int a = 100;
  2. Runnable x = () -> {
  3. System.out.println(a);
  4. };

结论

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

【转】Java8学习笔记(1) -- 从函数式接口说起的更多相关文章

  1. Java8学习笔记----Lambda表达式 (转)

    Java8学习笔记----Lambda表达式 天锦 2014-03-24 16:43:30 发表于:ATA之家       本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘.因为本人 ...

  2. Java8学习笔记目录

    Java8学习笔记(一)--Lambda表达式 Java8学习笔记(二)--三个预定义函数接口 Java8学习笔记(三)--方法引入 Java8学习笔记(四)--接口增强 Java8学习笔记(五)-- ...

  3. Java8学习笔记(八)--方法引入的补充

    在Java8学习笔记(三)--方法引入中,简要总结了方法引入时的使用规则,但不够完善.这里补充下几种情况: 从形参到实例方法的实参 示例 public class Example { static L ...

  4. java8学习之Supplier与函数式接口总结

    Supplier接口: 继续学习一个新的函数式接口--Supplier,它的中文意思为供应商.提供者,下面看一下它的javadoc: 而具体的方法也是相当的简单,就是不接受任何参数,返回一个结果: 对 ...

  5. Java8 学习笔记--函数式接口

    通过之前的函数式接口与lambda表达式的关系那篇文章,大家应该对函数式接口有了一定的了解了,在Java中它是lambda表达式的基础,没有函数式接口就没有办法使用lambda表达式. 函数式接口如此 ...

  6. Java8 学习笔记--函数式接口与lambda表达式的关系

    在java中,lambda表达式与函数式接口是不可分割的,都是结合起来使用的. 对于函数式接口,我们可以理解为只有一个抽象方法的接口,除此之外它和别的接口相比并没有什么特殊的地方.为了确保函数式接口的 ...

  7. Java8学习笔记(一)--Lambda表达式

    两个概念 函数式接口 函数式接口就是只显式声明一个抽象方法的接口.为保证方法数量不多不少,java8提供了一个专用注解@FunctionalInterface,这样,当接口中声明的抽象方法多于或少于一 ...

  8. Java8学习笔记(十一)--并发与非并发流下reduce比较

    BinaryOperator<Integer> operator = (l, r) -> l + r; BiFunction<Integer, Integer, Integer ...

  9. Java8学习笔记(五)--Stream API详解[转]

    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...

随机推荐

  1. 再论c#获取存储过程返回值(包括SqlSugar)

    其实这个问题好多年以前研究过: https://blog.csdn.net/xpnew/article/details/6909902 最近因为需要统计日结月结,给同事写了一套调用存储过程的代码.同时 ...

  2. girdspec实现画布

    对于简单的画布可以很轻松的使用subplot解决,可是对于一些复杂的画布比如下面的这个 就不易使用subplot解决,这是就需要subplot的扩展版gridspec 构造函数GridSpec(nro ...

  3. memcached 扩展安装(windows)

    在脚本之家里下载扩展压缩包 https://www.jb51.net/softs/392873.html 安装扩展前得先安装memcached并启动 下载完扩展压缩包解压并找到对应自己php版本 复制 ...

  4. 51nod1060(反素数&dfs)

    题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1060 题意:中文题诶- 思路: 这里用到了反素数的性质: 对 ...

  5. django 模版 语法与使用

    目录 django 模版语法与使用 django模板语言介绍 (摘自官方文档) 链接 什么是模板? 模板语句的 注释 变量 {{ 变量 }} 点(.)在模板语言中有特殊的含义,用来获取对象的相应属性值 ...

  6. 通过node.js读取html页面及其页面中引入的css样式

    Node 是什么? Node 是一个基于Chrome V8 引擎的JavaScript 运行环境. Node 不是一种独立的语言.Node不是 JavaScript 框架, Node是一个除了浏览器之 ...

  7. katalon studio配置git与git项目创建

    katalon 是一款在2015年诞生的可以安装在windows.macOS.linux操作系统上,基于selenium 和 Appium 测试框架,并集成了这些框架的优点的自动化测试工具.关于这个工 ...

  8. King's Pilots

    题目链接   (双层图, 一层维护工作,一层维护政策) #include <bits/stdc++.h> using namespace std; inline int read() { ...

  9. hdu5726-GCD-ST表+二分

    先用st表处理出所有l-r的GCD值,然后二分求得这些值一共出现了多少次. #include<bits/stdc++.h> #define inf 0x3f3f3f3f ; using n ...

  10. Hive_Hive的数据模型_桶表

    对数据进行HASH运算,放在不同文件中,降低热块,提高查询速度. 例如:根据sname进行hash运算存入5个桶中. create table bucket_table(sid int, sname ...