java1.8 拉姆达表达式_java1.8新特性(二)lambda表达式

一、什么是lambda表达式?

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

匿名内部类的写法:

48304ba5e6f9fe08f3fa1abda7d326ab.png

public voiddemo1(){

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() {}

};

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

Lambda表达式的写法

public voiddemo2(){

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;//.............

returnstatmentM;

}

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

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

2.一个箭头符号:->

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

总体看起来像这样:

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

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

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

(param1,param2, ..., paramN) ->{

statment1;

statment2;//.............

returnstatmentM;

}

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

param1 ->{

statment1;

statment2;//.............

returnstatmentM;

}

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

param1 -> statment

下面看几个例子:

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

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

demo2:Lambda 只需要一个参数

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

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

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

demo4:Lambda 需要两个参数

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

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

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

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

BinaryOperator bo=(x,y)->{

System.out.println("Lambda");return x+y;};

类型推断

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

三、lambda表达式的类型

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

View.OnClickListener listener = newView.OnClickListener() {

@Overridepublic voidonClick(View v) {//...

}

};

button.setOnClickListener(listener);

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

public class View implementsDrawable.Callback, KeyEvent.Callback,

AccessibilityEventSource {

...

public interface OnClickListener {

void onClick(View v);

}

...

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

@FunctionalInterfacepublic interface Runnable { voidrun(); }

public interface Callable { V call() throwsException; }

public interface ActionListener { voidactionPerformed(ActionEvent e); }

public interface Comparator{intcompare(T o1, T o2);

boolean equals(Object obj);

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

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

String []datas = new String[] {"peng","zhao","li"};

Comparator 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一模一样:

@FunctionalInterfacepublic interfaceMyRunnable {public voidrun();

}

那么

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

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

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

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

@FunctionalInterfacepublic interface Function{

R apply(T t);

}

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

@FunctionalInterfacepublic interface Consumer{voidaccept(T t);

}

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

@FunctionalInterfacepublic interface Predicate{booleantest(T t);

}

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

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

先举例:

48304ba5e6f9fe08f3fa1abda7d326ab.png

@Testpublic voidtest1(){//将为列表中的字符串添加前缀字符串

String waibu = "lambda :";

List proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});

ListexecStrs = proStrs.stream().map(chuandi ->{

Long zidingyi=System.currentTimeMillis();return waibu + chuandi + " -----:" +zidingyi;

}).collect(Collectors.toList());

execStrs.forEach(System.out::println);

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

输出:

lambda :Ni -----:1498722594781lambda :Hao-----:1498722594781lambda :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表达式的变量作用域与内部类非常相似,只是条件相对来说,放宽了些,以前内部类要想引用外部类的变量,必须像下面这样

48304ba5e6f9fe08f3fa1abda7d326ab.png

final String[] datas = new String[] { "peng", "Zhao", "li"};new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(datas);

}

}).start();

48304ba5e6f9fe08f3fa1abda7d326ab.png

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

48304ba5e6f9fe08f3fa1abda7d326ab.png

String []datas = new String[] {"peng","Zhao","li"};new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(datas);

}

}).start();

48304ba5e6f9fe08f3fa1abda7d326ab.png

也可以这样写:

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表达式产生的那个对象,而是声明它的外部对象。

例如:

48304ba5e6f9fe08f3fa1abda7d326ab.png

packagecom.demo;

importjava.util.Arrays;importjava.util.List;importjava.util.stream.Collectors;

public classWhatThis {

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

}

}

48304ba5e6f9fe08f3fa1abda7d326ab.png

输出:

com.wzg.test.WhatThis

com.wzg.test.WhatThis

com.wzg.test.WhatThis

ni

hao

lambda

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值