“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。有了Lambda表达式,java将开启函数式编程的大门。

函数式编程:

函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。 [1] 
和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。
和过程化编程相比,函数式编程里函数的计算可随时调用。

lambda 表达式的语法格式如下:

  1. (parameters) -> expression

  2. (parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

Lambda表达式创建一个线程:

  1. new Thread(
  2. () -> System.out.println("Thread run()")
  3. ).start();

不用Lambda表达式创建一个线程:

  1. new Thread(new Runnable(){
  2. @Override
  3. public void run(){
  4. System.out.println("Thread run()");
  5. }
  6. }).start();

Lambda表达式一个常见的用法是取代(某些)匿名内部类,但Lambda表达式的作用不限于此。

Lambda 表达式实例

  1. public static void main(String args[]){
  2. Java8Tester tester = new Java8Tester();
  3.  
  4. // 类型声明
  5. MathOperation addition = (int a, int b) -> a + b;
  6.  
  7. // 不用类型声明
  8. MathOperation subtraction = (a, b) -> a - b;
  9.  
  10. // 大括号中的返回语句
  11. MathOperation multiplication = (int a, int b) -> { return a * b; };
  12.  
  13. // 没有大括号及返回语句
  14. MathOperation division = (int a, int b) -> a / b;
  15.  
  16. System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
  17. System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
  18. System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
  19. System.out.println("10 / 5 = " + tester.operate(10, 5, division));
  20.  
  21. // 不用括号
  22. GreetingService greetService1 = message ->
  23. System.out.println("Hello " + message);
  24.  
  25. // 用括号
  26. GreetingService greetService2 = (message) ->
  27. System.out.println("Hello " + message);
  28.  
  29. greetService1.sayMessage("Runoob");
  30. greetService2.sayMessage("Google");
  31. }
  32.  
  33. interface MathOperation {
  34. int operation(int a, int b);
  35. }
  36.  
  37. interface GreetingService {
  38. void sayMessage(String message);
  39. }
  40.  
  41. private int operate(int a, int b, MathOperation mathOperation){
  42. return mathOperation.operation(a, b);
  43. }

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

变量作用域

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

  1. final static String salutation = "Hello! ";
  2.  
  3. public static void main(String args[]){
  4. GreetingService greetService1 = message ->
  5. System.out.println(salutation + message);
  6. greetService1.sayMessage("Runoob");
  7. }
  8.  
  9. interface GreetingService {
  10. void sayMessage(String message);
  11. }

我们也可以直接在 lambda 表达式中访问外层的局部变量:

  1. public static void main(String args[]) {
  2. final int num = 1;
  3. Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
  4. s.convert(2); // 输出结果为 3
  5. }
  6.  
  7. public interface Converter<T1, T2> {
  8. void convert(int i);
  9. }

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

  1. int num = 1;
  2. Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
  3. s.convert(2);
  4. num = 5;
  5. //报错信息:Local variable num defined in an enclosing scope must be final or effectively
  6. final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

  1. String first = "";
  2. Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错

Lambda表达式的原理

Lambda表达式实际上是通过invokedynamic指令来实现的。下面是Lambda表达式几种可能的书写形式。

  1. Runnable run = () -> System.out.println("Hello World");//
  2. ActionListener listener = event -> System.out.println("button clicked");//
  3. Runnable multiLine = () -> {//
  4. System.out.println("Hello ");
  5. System.out.println("World");
  6. };
  7. BinaryOperator<Long> add = (Long x, Long y) -> x + y;//
  8. BinaryOperator<Long> addImplicit = (x, y) -> x + y;//
  • Lambda表达式是有类型的,赋值操作的左边就是类型。Lambda表达式的类型实际上是对应接口的类型
  • Lambda表达式可以包含多行代码,需要用大括号把代码块括起来,就像写函数体那样。
  • 大多数时候,Lambda表达式的参数表可以省略类型,就像代码2和5那样。这得益于javac的类型推导机制,编译器可以根据上下文推导出类型信息。

表面上看起来每个Lambda表达式都是原来匿名内部类的简写形式,该内部类实现了某个函数接口(Functional Interface),但事实比这稍微复杂一些,这里不再展开。所谓函数接口是指内部只有一个接口函数的接口。Java是强类型语言,无论有没有显式指明,每个变量和对象都必须有明确的类型,没有显式指定的时候编译器会尝试确定类型。Lambda表达式的类型就是对应函数接口的类型

Lambda表达式和Stream

Lambda表达式的另一个重要用法,是和Stream一起使用。Stream is a sequence of elements supporting sequential and parallel aggregate operations。Stream就是一组元素的序列,支持对这些元素进行各种操作,而这些操作是通过Lambda表达式指定的。可以把Stream看作Java Collection的一种视图,就像迭代器是容器的一种视图那样(但Stream不会修改容器中的内容)。下面例子展示了Stream的常见用法。

假设需要从一个字符串列表中选出以数字开头的字符串并输出,Java 7之前需要这样写:

  1. List<String> list = Arrays.asList("1one", "two", "three", "4four");
  2. for(String str : list){
  3. if(Character.isDigit(str.charAt(0))){
  4. System.out.println(str);
  5. }
  6. }

而Java 8就可以这样写:

  1. List<String> list = Arrays.asList("1one", "two", "three", "4four");
  2. list.stream()// 1.得到容器的Steam
  3. .filter(str -> Character.isDigit(str.charAt(0)))// 2.选出以数字开头的字符串
  4. .forEach(str -> System.out.println(str));// 3.输出字符串

上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法过滤出以数字开头的字符串,3. 最后调用forEach()方法输出结果。

使用Stream有两个明显的好处:

  1. 减少了模板代码,只用Lambda表达式指明所需操作,代码语义更加明确、便于阅读。
  2. 将外部迭代改成了Stream的内部迭代,方便了JVM本身对迭代过程做优化(比如可以并行迭代)。

假设需要从一个字符串列表中,选出所有不以数字开头的字符串,将其转换成大写形式,并把结果放到新的集合当中。Java 8书写的代码如下:

  1. List<String> list = Arrays.asList("1one", "two", "three", "4four");
  2. Set<String> newList =
  3. list.stream()// 1.得到容器的Stream
  4. .filter(str -> !Character.isDigit(str.charAt(0)))// 2.选出不以数字开头的字符串
  5. .map(String::toUpperCase)// 3.转换成大写形式
  6. .collect(Collectors.toSet());// 4.生成结果集

上述代码首先1. 调用List.stream()方法得到容器的Stream,2. 然后调用filter()方法选出不以数字开头的字符串,3. 之后调用map()方法将字符串转换成大写形式,4. 最后调用collect()方法将结果转换成Set。这个例子还向我们展示了方法引用method references,代码中标号3处)以及收集器Collector,代码中标号4处)的用法,这里不再展开说明。

通过这个例子我们看到了Stream链式操作,即多个操作可以连成一串。不用担心这会导致对容器的多次迭代,因为不是每个Stream的操作都会立即执行。Stream的操作分成两类,一类是中间操作(intermediate operations),另一类是结束操作(terminal operation),只有结束操作才会导致真正的代码执行,中间操作只会做一些标记,表示需要对Stream进行某种操作。这意味着可以在Stream上通过关联多种操作,但最终只需要一次迭代。如果你熟悉Spark RDD,对此应该并不陌生。

Java8Lambda表达式的更多相关文章

  1. java8---lambda表达式

    语法糖 lambda表达式允许你通过表达式来代替功能接口. lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块).Lambda表 ...

  2. java8lambda表达式初识

    一.函数式接口 只有一个 抽象方法 的 接口 叫函数式接口 /** * @auther hhh * @date 2018/12/24 22:20 * @description 函数式接口:只有 一个 ...

  3. Java基础一篇过(六)Java8--lambda表达式

    一.简介 lambda表达式是Java8的一个重要特性,也可以称为闭包,常用于配合Java8的Stream对集合元素进行操作,使得代码更简介紧凑. 二.代码解析 虽说lambda表达式是一个新的特性, ...

  4. java8(一)Lambda表达式

    其实很久前已经学习过了Lambda表达式,但是学习后没有多少使用的机会,久而久之也就忘记(惭愧).最近新的项目用的jdk8所以准备再学习一次,写下文章也是为了记录,方便以后再忘的时候,不用到处找资料( ...

  5. 【.net 深呼吸】细说CodeDom(2):表达式、语句

    在上一篇文章中,老周厚着脸皮给大伙介绍了代码文档的基本结构,以及一些代码对象与CodeDom类型的对应关系. 在评论中老周看到有朋友提到了 Emit,那老周就顺便提一下.严格上说,Emit并不是针对代 ...

  6. 你知道C#中的Lambda表达式的演化过程吗?

    那得从很久很久以前说起了,记得那个时候... 懵懂的记得从前有个叫委托的东西是那么的高深难懂. 委托的使用 例一: 什么是委托? 个人理解:用来传递方法的类型.(用来传递数字的类型有int.float ...

  7. 再讲IQueryable<T>,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  8. Linq表达式、Lambda表达式你更喜欢哪个?

    什么是Linq表达式?什么是Lambda表达式? 如图: 由此可见Linq表达式和Lambda表达式并没有什么可比性. 那与Lambda表达式相关的整条语句称作什么呢?在微软并没有给出官方的命名,在& ...

  9. 背后的故事之 - 快乐的Lambda表达式(一)

    快乐的Lambda表达式(二) 自从Lambda随.NET Framework3.5出现在.NET开发者眼前以来,它已经给我们带来了太多的欣喜.它优雅,对开发者更友好,能提高开发效率,天啊!它还有可能 ...

随机推荐

  1. IE6下面的css调试工具

    在开发过程中,代码部分实现之后,就要着手于前台展示部分的界面,公司的美工又是新手,无奈,只有自己慢慢调了,但IE6之前的版本都没有好的调试工具,后来在网上搜索了一个 IE Developer Tool ...

  2. IDEA 2017 破解

    一.windows 1.进入hosts文件中:C:\Windows\System32\drivers\etc\hosts 2.将"0.0.0.0 account.jetbrains.com& ...

  3. 20155210潘滢昊 2016-2017-2 《Java程序设计》第5周学习总结

    20155210 2016-2017-2 <Java程序设计>第5周学习总结 教材学习内容总结 try with resources 关闭多个资源时用分号分隔 java.lang.Auto ...

  4. 20155212 2016-2017-2《Java程序设计》课程总结

    每周博客 每周作业链接汇总 预备作业一:专业理解.未来展望.期望的师生关系. 预备作业二:HOMEWORK-2 预备作业三:HOMEWORK-3 第一周作业:学习教材Chapter 1 Java平台概 ...

  5. python初步学习-python模块之 commands

    commands 通过 os.popen() 执行 shell 命令,返回两个对象,一个是 状态码(Int).另一个为命令输出(str) commands.getoutput(cmd) 返回命令执行输 ...

  6. aarch64_n2

    nodejs-is-dotfile-1.0.2-2.fc26.noarch.rpm 2017-02-12 00:27 9.5K fedora Mirroring Project nodejs-is-e ...

  7. Microsoft.AspNet.SignalR使用cookie丢失

    public void SendGroupMessage(string roomId, string message, string status) { // 调用房间内所有客户端的sendMessa ...

  8. 畸形的 dockerfile中的COPY命令-

    dockerfile中的COPY是指COPY 指定目录的“子级目录”下所有的目录和文件,到指定目录中,这个shell中的cp命令大相径庭,使得很多人纳闷,怎么cpy过去的文件不是自己想要的

  9. 转载:Google 官方应用架构的最佳实践指南 赞👍

    官方给的实践指南,很有实际的指导意义,  特别是对一些小公司,小团队,给了很好的参考意义. 原文地址: https://developer.android.com/topic/libraries/ar ...

  10. pip install 升级时候 出现报asciii码错误的问题。

    原因是pip安装python包会加载我的用户目录,我的用户目录恰好是中文的,ascii不能编码.解决办法是: python目录 Python27\Lib\site-packages 建一个文件site ...