1、什么是lambda表达式?java
Lambda 是一个匿名函数,咱们能够把 Lambda 表达式理解为是一段能够传递的代码(将代码像数据同样进行传递)。能够写出更简洁、更灵活的代码。做为一种更紧凑的代码风格,使 Java的语言表达能力获得了提高。express
匿名内部类的写法:多线程
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的写法。app
Lambda表达式的写法ide
public void demo2(){ Comparator<Integer> comparator = (x,y) -> Integer.compare(x, y); Runnable runnable = () -> System.out.println("lambda表达式"); }
咱们会发现Lambda表达式的写法更加的简洁、灵活。它只关心参数和执行的功能(具体须要干什么,好比->后的Integer.compare(x, y))。函数
2、lambda表达式语法优化
lambda表达式的通常语法:this
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
包含三个部分:参数列表,箭头(->),以及一个表达式或语句块。spa
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 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”。
3、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表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。
4、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来处理。
5、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。
6、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