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

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

函数式接口

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

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

Lambda语法糖

为了能够方便、快捷、幽雅的创建出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表达式来实现:

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

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

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

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

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

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

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

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

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

方法引用

有时候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表达式

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

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

预定义函数式接口

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

@FunctionalInterface
public interface Predicate<T> {  boolean test(T t);
}  

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

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

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

@FunctionalInterface
public interface Function<T, R> {  R apply(T t);
}  

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

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

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

@FunctionalInterface
public interface Consumer<T> {  void accept(T t);
}  

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

public interface Iterable<T> {  ...  default void forEach(Consumer<? super T> action) {  Objects.requireNonNull(action);  for (T t : this) {  action.accept(t);  }  }  ...
}  

接口的静态方法

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

public interface Function<T, R> {  ...  /** * Returns a function that always returns its input argument. * * @param <T> the type of the input and output objects to the function * @return a function that always returns its input argument */  static <T> Function<T, T> identity() {  return t -> t;  }
...
}  

变量捕获

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

int a = 100;
Runnable x = new Runnable() {  @Override  public void run() {  System.out.println(a);  }  };  

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

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

结论

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

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

  1. Python学习笔记__4章 函数式编程

    # 这是学习廖雪峰老师python教程的学习笔记 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程 ...

  2. Java8学习笔记(七)--Collectors

    本系列文章翻译自@shekhargulati的java8-the-missing-tutorial 你已经学习了Stream API能够让你以声明式的方式帮助你处理集合.我们看到collect是一个将 ...

  3. python函数式编程读取数据-python学习笔记9:函数式编程

    函数式编程(FunctionalProgramming) 基于lambda演算的一种编程方式 程序中只有函数 函数可以作为参数,同样可以作为返回值 纯函数式编程语言: LISP, Haskell Py ...

  4. on java8学习笔记2022.2.19-2022.2.20

    2022.2.19 第十章 接口 如果一个类并不需要包含抽象方法,但同时还想阻止对它的任何实例化,这时将其定义为抽象类就很有用了. 接口和抽象类之间最显著的区别可能是两者的惯用方式.接口通常暗示&qu ...

  5. 《Java8学习笔记》读书笔记(7)

    第7章 接口与多态 学习目标 使用接口定义 了解接口的多态操作 利用接口枚举常数 利用enum枚举常数 7.1 何谓接口 接口是在面向对象思想的发展之中逐渐发展出来的.正如前面所说过的继承的隐患,当类 ...

  6. Java8学习笔记:计算两个时间的间隔及相隔天数、秒数等

    环境 Java11 IDEA:2020.1 前言 如果大家用过Java7的时间,应该都知道,要计算startTime和endTime时间间隔是要写很多代码的: Java8开始对时间类进行过重构优化,并 ...

  7. 学习笔记:Python3 函数式编程

    仅为个人查阅使用,如有错误还请指正. 函数式编程是一种抽象计算的编程模式. 函数式编程的特点 ​ 1.把计算视为函数而非指令. ​ 2.纯粹的函数式编程语言编写的函数没有变量. ​ 3.支持高阶函数, ...

  8. Java8学习笔记(三)--方法引入

    POJO public class Emp {private int empno;private String ename;//其余方法略 } 方法的引入分为以下四种: 引入静态方法 @Functio ...

  9. java变量默为public_《Java8学习笔记》读书笔记(六)

    第5章 对象封装 5.1 何谓封装 定义类并不等于做好了面向对象中封装的概念,那么到底什么才有封装的含义? 5.1.1 封装对象初始流程 假设要写个可以管理储值卡的应用程序,首先得定义储值卡会记录哪些 ...

最新文章

  1. android中volley通信框架简介
  2. 命令行神器 Click 简明笔记
  3. Python编程基础:第三十八节 问答游戏Quiz Game
  4. CTF杂项题型之画图-Miscellaneous-200
  5. 消息发送到topic多个MessageQueue
  6. AndroidStudio怎样导入library项目开源库 - 转
  7. 有道口语大师APP评测:语音识别准确度低
  8. Jquery—JQuery对checkbox的操作(01)
  9. 单片机:DS1302时钟
  10. 你焊接过最小的贴片器件是多大?
  11. python生成的词云没有图案_python词云(词云图生成器)
  12. (10) IFC中的构件与空间结构(IfcRelContainedInSpatialStructure) (Industry Foundation Class)
  13. Hbuilder开发移动新闻客户端(二)
  14. .co域名后缀怎么样?
  15. 计算机考试ppt,计算机考试PPT.ppt
  16. \Qt5\\bin\\d3dcompiler_47.dll
  17. 一种通用强人工智能系统的实现
  18. 语音识别、手写输入 QQ2011正式版全体验
  19. Verilog 实现 UART TX 发送器
  20. Python基础实战项目——学生信息管理系统(内涵完整项目代码)

热门文章

  1. 了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化
  2. [Java基础]自然排序Comparable的使用
  3. 使用单项循环链表实现字典操作(算法导论第十章10.2-5题)
  4. python序列数据类型_Python 数据类型 之 序列类型
  5. 数据结构---模式匹配
  6. 2019 ICPC Asia Nanchang Regional K.Tree 树上启发式合并 + 动态开点线段树
  7. 【BZOJ3451】Normal【期望线性性】【点分治】【NTT卷积】
  8. 数据结构题(莫队算法)
  9. CF1039D-You Are Given a Tree【根号分治,贪心】
  10. CF757F-Team Rocket Rises Again【最短路,DAG支配树】