一、什么是lambda表达式?

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java的语言表达能力得到了提升。

匿名内部类的写法:

  1. public void demo1(){
  2.  
  3. Comparator<Integer> comparator = new Comparator<Integer>() {
  4. @Override
  5. public int compare(Integer o1, Integer o2) {
  6. return Integer.compare(o1, o2);
  7. }
  8. };
  9.  
  10. Runnable runnable = new Runnable() {
  11. @Override
  12. public void run() {}
  13. };
  14. }

这样写会发现一个问题,实现的方法是冗余的代码,实际当中并没有什么用处。我们看看Lambda的写法。

Lambda表达式的写法

  1. public void demo2(){
  2.  
  3. Comparator<Integer> comparator = (x,y) -> Integer.compare(x, y);
  4.  
  5. Runnable runnable = () -> System.out.println("lambda表达式");
  6. }

我们会发现Lambda表达式的写法更加的简洁、灵活。它只关心参数和执行的功能(具体需要干什么,比如->后的Integer.compare(x, y))。

二、lambda表达式语法

lambda表达式的一般语法:

  1. (Type1 param1, Type2 param2, ..., TypeN paramN) -> {
  2. statment1;
  3. statment2;
  4. //.............
  5. return statmentM;
  6. }

包含三个部分:参数列表,箭头(->),以及一个表达式或语句块。

1.一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数

2.一个箭头符号:->

3.方法体,可以是表达式和代码块,方法体是函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}。

总体看起来像这样:

  1. (parameters) -> expression 或者 (parameters) -> { statements; }

上面的lambda表达式语法可以认为是最全的版本,写起来还是稍稍有些繁琐。别着急,下面陆续介绍一下lambda表达式的各种简化版:

1. 参数类型省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样lambda表达式就变成了:

  1. (param1,param2, ..., paramN) -> {
  2. statment1;
  3. statment2;
  4. //.............
  5. return statmentM;
  6. }

2. 单参数语法:当lambda表达式的参数个数只有一个,可以省略小括号。lambda表达式简写为:

  1. param1 -> {
  2. statment1;
  3. statment2;
  4. //.............
  5. return statmentM;
  6. }

3. 单语句写法:当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。lambda表达式简化为:

  1. param1 -> statment

下面看几个例子:

demo1:无参,无返回值,Lambda 体只需一条语句

  1. Runnable runnable = () -> System.out.println("lamda表达式");

demo2:Lambda 只需要一个参数

  1. Consumer<String> consumer=(x)->System.out.println(x);

demo3:Lambda 只需要一个参数时,参数的小括号可以省略

  1. Consumer<String> consumer=x->System.out.println(x);

demo4:Lambda 需要两个参数

  1. Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);

demo5:当 Lambda 体只有一条语句时,return 与大括号可以省略

  1. BinaryOperator<Integer> binaryOperator=(x,y)->(x+y);

demo6:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

  1. BinaryOperator<Integer> bo=(x,y)->{
  2. System.out.println("Lambda");
  3. return x+y;};

类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”。

三、lambda表达式的类型

我们都知道,Java是一种强类型语言。所有的方法参数都有类型,那么lambda表达式是一种什么类型呢?

  1. View.OnClickListener listener = new View.OnClickListener() {
  2. @Override
  3. public void onClick(View v) {
  4. //...
  5. }
  6. };
  7.  
  8. button.setOnClickListener(listener);

如上所示,以往我们是通过使用单一方法的接口来代表一个方法并且重用它。

在lambda表达式中,仍使用的和之前一样的形式。我们叫做函数式接口(functional interface)。如我们之前button的点击响应事件使用的View.OnClickListener就是一个函数式接口。

  1. public class View implements Drawable.Callback, KeyEvent.Callback,
  2. AccessibilityEventSource {
  3. ...
  4.  
  5. public interface OnClickListener {
  6. void onClick(View v);
  7. }
  8. ...
  9. }

那究竟什么样的接口是函数式接口呢?

函数式接口是只有一个抽象方法的接口,用作表示lambda表达式的类型。 比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断,但最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。举例如下:

  1. @FunctionalInterface
  2. public interface Runnable { void run(); }
  3.  
  4. public interface Callable<V> { V call() throws Exception; }
  5.  
  6. public interface ActionListener { void actionPerformed(ActionEvent e); }
  7.  
  8. public interface Comparator<T> {
  9. int compare(T o1, T o2);
  10.  
  11. boolean equals(Object obj);
  12. }

注意最后这个Comparator接口。它里面声明了两个方法,貌似不符合函数接口的定义,但它的确是函数接口。这是因为equals方法是Object的,所有的接口都会声明Object的public方法——虽然大多是隐式的。所以,Comparator显式的声明了equals不影响它依然是个函数接口。

Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现。即Lambda表达式不能脱离目标类型存在,这个目标类型就是函数式接口,看下面的例子:

  1. String []datas = new String[] {"peng","zhao","li"};
  2. Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length());
  3. Arrays.sort(datas,comp);
  4. Stream.of(datas).forEach(param -> {System.out.println(param);});

Lambda表达式被赋值给了comp函数接口变量。

你可以用一个lambda表达式为一个函数接口赋值:

  1. Runnable r1 = () -> {System.out.println("Hello Lambda!");};

然后再赋值给一个Object:

  1. Object obj = r1;

但却不能这样干:

  1. Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!

必须显式的转型成一个函数接口才可以:

  1. Object o = (Runnable) () -> { System.out.println("hi"); }; // correct

一个lambda表达式只有在转型成一个函数接口后才能被当做Object使用。所以下面这句也不能编译:

  1. System.out.println( () -> {} ); //错误! 目标类型不明

必须先转型:

  1. System.out.println( (Runnable)() -> {} ); // 正确

假设你自己写了一个函数接口,长的跟Runnable一模一样:

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

那么

  1. Runnable r1 = () -> {System.out.println("Hello Lambda!");};
  2. MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};

都是正确的写法。这说明一个lambda表达式可以有多个目标类型(函数接口),只要函数匹配成功即可。但需注意一个lambda表达式必须至少有一个目标类型。

JDK预定义了很多函数接口以避免用户重复定义。最典型的是Function:

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

这个接口代表一个函数,接受一个T类型的参数,并返回一个R类型的返回值。另一个预定义函数接口叫做Consumer,跟Function的唯一不同是它没有返回值。

  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3. void accept(T t);
  4. }

还有一个Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:

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

综上所述,一个lambda表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。

四、lambda表达式可使用的变量

先举例:

  1. @Test
  2. public void test1(){
  3. //将为列表中的字符串添加前缀字符串
  4. String waibu = "lambda :";
  5. List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
  6. List<String>execStrs = proStrs.stream().map(chuandi -> {
  7. Long zidingyi = System.currentTimeMillis();
  8. return waibu + chuandi + " -----:" + zidingyi;
  9. }).collect(Collectors.toList());
  10.  
  11. execStrs.forEach(System.out::println);
  12. }

输出:

  1. lambda :Ni -----:1498722594781
  2. lambda :Hao -----:1498722594781
  3. lambda :Lambda -----:1498722594781

变量waibu :外部变量

变量chuandi :传递变量

变量zidingyi :内部自定义变量

lambda表达式可以访问给它传递的变量,访问自己内部定义的变量,同时也能访问它外部的变量。不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。

当在表达式内部修改waibu = waibu + " ";时,IDE就会提示你:

  1. Local variable waibu defined in an enclosing scope must be final or effectively final

编译时会报错。因为变量waibu被lambda表达式引用,所以编译器会隐式的把其当成final来处理。

以前Java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。现在java8对这个限制做了优化,可以不用显示使用final修饰,但是编译器隐式当成final来处理。

五、lambda表达式作用域

总体来说,Lambda表达式的变量作用域与内部类非常相似,只是条件相对来说,放宽了些,以前内部类要想引用外部类的变量,必须像下面这样

  1. final String[] datas = new String[] { "peng", "Zhao", "li" };
  2. new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. System.out.println(datas);
  6. }
  7. }).start();

将变量声明为final类型的,现在在Java 8中可以这样写代码

  1. String []datas = new String[] {"peng","Zhao","li"};
  2. new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. System.out.println(datas);
  6. }
  7. }).start();

也可以这样写:

  1. new Thread(() -> System.out.println(datas)).start();

看了上面的两段代码,能够发现一个显著的不同,就是Java 8中内部类或者Lambda表达式对外部类变量的引用条件放松了,不要求强制的加上final关键字了,但是Java 8中要求这个变量是effectively final。What is effectively final?

Effectively final就是有效只读变量,意思是这个变量可以不加final关键字,但是这个变量必须是只读变量,即一旦定义后,在后面就不能再随意修改,如下代码会编译出错

  1. String []datas = new String[] {"peng","Zhao","li"};
  2. datas = null;
  3. new Thread(() -> System.out.println(datas)).start();

Java中内部类以及Lambda表达式中也不允许修改外部类中的变量,这是为了避免多线程情况下的race condition。

六、lambda表达式中的this概念

在lambda中,this不是指向lambda表达式产生的那个对象,而是声明它的外部对象。

例如:

  1. package com.demo;
  2.  
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.stream.Collectors;
  6.  
  7. public class WhatThis {
  8.  
  9. public void whatThis(){
  10. //转全小写
  11. List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
  12. List<String> execStrs = proStrs.stream().map(str -> {
  13. System.out.println(this.getClass().getName());
  14. return str.toLowerCase();
  15. }).collect(Collectors.toList());
  16.  
  17. execStrs.forEach(System.out::println);
  18. }
  19.  
  20. public static void main(String[] args) {
  21. WhatThis wt = new WhatThis();
  22. wt.whatThis();
  23. }
  24.  
  25. }

输出:

  1. com.wzg.test.WhatThis
  2. com.wzg.test.WhatThis
  3. com.wzg.test.WhatThis
  4. ni
  5. hao
  6. lambda

Java8之lambda表达式的更多相关文章

  1. Java8中Lambda表达式的10个例子

    Java8中Lambda表达式的10个例子 例1 用Lambda表达式实现Runnable接口 //Before Java 8: new Thread(new Runnable() { @Overri ...

  2. java8的lambda表达式,将List<DTO> 转为 List<DO>

    将List<PhoneDTO>转为List<PhoneDO>,通过java8的lambda表达式来操作,比传统的for循环精简很多: /** * List<PhoneDT ...

  3. java8的lambda表达式

    关于java8的lambda表达式 lambda表达式一般用于接口,因为lambda表达式是函数式编程. 1.有且仅有一个抽象方法被称为函数式接口,函数式接口可以显示的被@FunctionalInte ...

  4. java8中lambda表达式的应用,以及一些泛型相关

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public clas ...

  5. java8之lambda表达式(1)-基本语法

    lambda表达式,即带有参数的表达式,为更清晰地理解lambda表达式,先看如下例子: (1) class Student{ private String name; private Double ...

  6. java8之lambda表达式入门

    1.基本介绍 lambda表达式,即带有参数的表达式,为了更清晰地理解lambda表达式,先上代码: 1.1 两种方式的对比 1.1.1 方式1-匿名内部类 class Student{ privat ...

  7. JAVA8之lambda表达式具体解释,及stream中的lambda使用

    前言: 本人也是学习lambda不久,可能有些地方描写叙述有误,还请大家谅解及指正! lambda表达式具体解释 一.问题 1.什么是lambda表达式? 2.lambda表达式用来干什么的? 3.l ...

  8. 十分钟学会Java8的lambda表达式和Stream API

    01:前言一直在用JDK8 ,却从未用过Stream,为了对数组或集合进行一些排序.过滤或数据处理,只会写for循环或者foreach,这就是我曾经的一个写照. 刚开始写写是打基础,但写的多了,各种乏 ...

  9. java8中Lambda表达式和Stream API

    一.Lambda表达式 1.语法格式 Lambda是匿名函数,可以传递代码.使用“->”操作符,改操作符将lambda分成两部分: 左侧:指定了 Lambda 表达式需要的所有参数 右侧:指定了 ...

随机推荐

  1. 如何为你的 Vue 项目添加配置 Stylelint

    如何为你的 Vue 项目添加配置 Stylelint 现在已经是 9102 年了,网上许多教程和分享帖都已经过期,照着他们的步骤来会踩一些坑,如 stylelint-processor-html 已经 ...

  2. 关于数据分析的4点心得:维度、指标、KPI

    1.看数据看维度 在对某一项业务或者业务的某个模块进行分析时,可以从大小两个角度去切入分析. 首先站在广阔的视角去看待一些数据.比如对某个产品(消费品),就要分析在大环境下是一个什么样的数据,如市场排 ...

  3. apk公钥私钥用法

    每个密钥都包含两个文件:一个是扩展名为 .x509.pem 的证书,另一个是扩展名为 .pk8 的私钥.私钥需要加以保密,并用于对 apk 包进行签名.密钥本身也可能受密码保护.相比之下,证书只包含公 ...

  4. CsQuery获取IDomObject元素的完整CSS选择器

    一.方法说明 通过IDomObject元素,获取完整的CSS选择器,过滤HTML和BODY元素,自动将class.id添加到选择器上,优先添加class,无class再添加id.如: <html ...

  5. 使用VSTS的Git进行版本控制(三)——评审历史记录

    使用VSTS的Git进行版本控制(三)--评审历史记录 Git使用存储在每个提交中的父引用信息来管理开发的完整历史记录.评审该提交历史记录,能够找出文件更改的时间,并确定代码版本之间的差异. Git使 ...

  6. 商家服务无法上架提示没有授权信息解决FAQ

    1.地址授权:https://openauth.alipay.com/oauth2/appToAppAuth.htm?app_id=2018032002416255&redirect_uri= ...

  7. 如何定位那些SQL产生了大量的redo日志

    在ORACLE数据库的管理.维护过程中,偶尔会遇到归档日志暴增的情况,也就是说一些SQL语句产生了大量的redo log,那么如何跟踪.定位哪些SQL语句生成了大量的redo log日志呢? 下面这篇 ...

  8. DPA从DPA 10.0.352升级到DPA 11.0.373

    1: 解压安装文件SolarWinds-DPA-11.0.373-64bit.tar.gz [root@lnxmonitor tmp]# tar -xzvf SolarWinds-DPA-11.0.3 ...

  9. HTML—标签与表格 、框架

    1.标签 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...

  10. Windows Server 2016-域站点链接及子网调整

    很多情况下我们在判别域控间或者域中各站点同步是否正常往往的操作内容就是查看两台域控间PING或者解析是否正常,或者查看双方防火墙是否关闭,但实际情况下我们需要注意的是,保证站点间Active Dire ...