一、什么是lambda表达式?

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

匿名内部类的写法:

public void demo1(){

    Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
}; Runnable runnable = new Runnable() {
@Override
public void run() {}
};
}

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

Lambda表达式的写法

public void demo2(){

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

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

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

二、lambda表达式语法

lambda表达式的一般语法:

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}

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

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

2.一个箭头符号:->

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

总体看起来像这样:

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

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

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

(param1,param2, ..., paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}

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

param1 -> {
statment1;
statment2;
//.............
return statmentM;
}

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

param1 -> statment

下面看几个例子:

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

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

demo2:Lambda 只需要一个参数

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

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

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

demo4:Lambda 需要两个参数

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

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

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

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

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

类型推断

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

三、lambda表达式的类型

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

View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
//...
}
}; button.setOnClickListener(listener);

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

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

public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
... public interface OnClickListener {
void onClick(View v);
}
...
}

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

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

@FunctionalInterface
public interface Runnable { void run(); } public interface Callable<V> { V call() throws Exception; } public interface ActionListener { void actionPerformed(ActionEvent e); } public interface Comparator<T> {
int compare(T o1, T o2); boolean equals(Object obj);
}

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

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

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

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

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

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

然后再赋值给一个Object:

Object obj = r1;

但却不能这样干:

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

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

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

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

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

必须先转型:

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

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

@FunctionalInterface
public interface MyRunnable {
public void run();
}

那么

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

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

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

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

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

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

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

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

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

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

先举例:

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

输出:

lambda :Ni -----:1498722594781
lambda :Hao -----:1498722594781
lambda :Lambda -----:1498722594781

变量waibu :外部变量

变量chuandi :传递变量

变量zidingyi :内部自定义变量

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

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

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

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

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

五、lambda表达式作用域

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

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

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

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

也可以这样写:

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

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

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

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

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

六、lambda表达式中的this概念

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

例如:

package com.demo;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors; public class WhatThis { public void whatThis(){
//转全小写
List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> execStrs = proStrs.stream().map(str -> {
System.out.println(this.getClass().getName());
return str.toLowerCase();
}).collect(Collectors.toList()); execStrs.forEach(System.out::println);
} public static void main(String[] args) {
WhatThis wt = new WhatThis();
wt.whatThis();
} }

输出:

com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
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. 洛谷P4104 [HEOI2014]平衡(dp 组合数学)

    题意 题目链接 Sol 可以把题目转化为从\([1, 2n + 1]\)中选\(k\)个数,使其和为\((n+1)k\). 再转化一下:把\((n+1)k\)划分为\(k\)个数,满足每个数在范围在\ ...

  2. SAP MM PR中的Fixed ID字段与MD04里PR单据号后的星号

    SAP MM PR中的Fixed ID字段与MD04里PR单据号后的星号 如下图是我手工使用ME51N 创建的一个采购申请单据, ​ MD04去看这个PR, ​ 这个PR号码后面有一个*号,代表它是一 ...

  3. JMeter 中实现发送Java请求

    JMeter 中实现发送Java请求 1.  步骤1 新建JAVA项目 File -> New -> Java Project 如上图,填写Project Name,然后Next,打开以J ...

  4. 小技巧:在线生成按钮Shape的网站

    AndroidButton Make  右侧设置按钮的属性,可以即时看到效果,并即时生成对应的.xml 代码,非常高效(当然熟练的话 自己手写代码更快)

  5. Android udp json+数组 --->bytes发送数据

    Android  json支持五种数据类型 String / int(float)/bool / null  / object 今天说 object  : json = new JSONObject( ...

  6. 我喜欢 Google Flutter

    在 Google I/O ’17 上,Google 向我们介绍了 Flutter —— 一款新的用于创建移动应用的开源库. 正如你所想的那样,Flutter 是能够帮助创建拥有漂亮 UI 界面的跨平台 ...

  7. (后端)swagger

    Swagger 文档提供了一个方法,使我们可以用指定的 JSON 或者 YAML 摘要来描述你的 API,包括了比如 names.order 等 API 信息. 你可以通过一个文本编辑器来编辑 Swa ...

  8. way.js

    (function (root, factory) { if (typeof define === "function" && define.amd) { defi ...

  9. JMeter—总结

    Jmter简单总结 简单的使用篇 jmeter简单的使用 Jmeter中默认语言的显示 jmeter利用自身代理录制脚本 Jmeter运行后出现乱码 http cookie管理中cookie poli ...

  10. SQL WHERE 子句

    WHERE 子句 如需有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句. 语法 SELECT 列名称 FROM 表名称 WHERE 列 运算符 值 释意:选取 [列] 来自 [ ...