lambda表达式的使用详解

来源:互联网 发布:棋牌游戏作弊软件 编辑:程序博客网 时间:2024/04/29 06:33

首先我们来看一段简单的代码:

interface ILambdaTest1{void print(String s);}public class LambdaTest1 {public static void main(String[] args) {//传统内部类的实现LambdaUse(new ILambdaTest1() {@Overridepublic void print(String s) {System.out.println(s);}}, "hello world");}public static void LambdaUse(ILambdaTest1 lambda,String string){lambda.print(string);}}

这段代码,很简单,静态方法接受一个ILambdaTest1的实现和一个String参数,在方法中调用ILambdaTest1实现传递String参数进行处理,这里只是一个例子,实际开发中最多的是在按钮点击事件时传递onActionListeneronClickListener。这里只是为了演示,不考虑实际开发作用。

然后你会发现,new了一个内部类,实际上真正作用的就是System.out.println()方法,最重要的问题是不能很好的展示真正的目的。

那下面我们来看看基于lambda的实现。

interface ILambdaTest1{void print(String s);}public class LambdaTest1 {public static void main(String[] args) {//传统内部类的实现LambdaUse(s->System.out.println(s),"Hello world.");}public static void LambdaUse(ILambdaTest1 lambda,String string){lambda.print(string);}}

发现就一句话s->System.out.println(s)就完成了接口的传递,是不是觉得很神奇,又有点难理解,一开始我也是一头雾水,没关系,那我们继续往下面看。这篇博客主要向大家介绍lambda表达式怎么使用,之后会发布一篇博客来揭开lambda底层实现的面纱,让大家知道知其然并知其所以然。

那什么是lambda表达式呢?

简单的说,就是匿名函数。

有人会问?s->System.out.println(s)这句话是什么意思啊?其实这句话就是lambda表达式的组成。其中s表示参数,System.out.println(s);表示实现,换句话说就是要干的事情。那s是什么类型的呢?在这里很明显是String类型。编译器会根据上下文判断s的类型。例如这里LambdaUse方法接受的第一个参数是ILambda接口,而接口里面的方法是print(String s)lambda表达式简单的说就是print函数的实现,其中s->System.out.println(s)中的s就是要传递到方法print(String s)中的形参,System.out.println(s)就是print方法的里面要做的事情。那如果要做的事不单单是一条语句呢?而是代码块呢?那就加上花括号s->{System.out.println(s);},那如果我要传递的是几个参数呢?那就用小括号把要传递的参数括起来,如(s1,s2)->{System.out.println(s);},s1,s2的类型可以根据上下文确定,但是你也可以明确的表明,如(String s1,String s2)->{System.out.println(s1+s2);},那如果没有参数传递呢,那就用空的花括号。如:()->{System.out.println(“hello world.”);}.

刚才我们的表达式是和接口

interface ILambdaTest1{void print(String s);}

中的print方法相对应的,那为什么可以这样对应呢?代码上没有任何说明啊。如果接口只有一个方法,还可以知道lambda表达式只能映射到那个方法,但是如果接口有多个方法怎么确定呢?

好的,lambda表达式对能用它表示的接口很苛刻,这个接口只能有一个抽象方法,夷,那这样不就和上面猜测的一样了吗,只能有一个抽象的方法,然后lambda表达式就可以唯一的映射到这个方法上了,仔细想想也是,要是多个真没法映射。

为了确保接口可以使用lambda表达式,java新增加了一个注解,@FunctionalInterface,用这个注解修饰一个接口,这个接口只能有一个抽象的方法。这类接口称为函数式接口。

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

因为java中的接口增强了,在接口中可以使用defaule关键字定义默认的实现方法,子类当然也可以重写,还可以定义静态的方法了,这两个方法都不是抽象的,所以说用@FunctionalInterface修饰的接口还是可以包含这两类方法的。

@FunctionalInterfaceinterface ILambdaTest1{void print(String s);default void moren(){System.out.println("defalut");}static void staticMethod(){System.out.println("static");}}

但是如果定义两个抽象方法,编译器就会报错。


下面我们来看一个简单的例子

@FunctionalInterfaceinterface ILambdaCaculator{int result(int a,int b);}public class LambdaTest2 {public static void main(String[] args) {System.out.println("add :"+LambdaUse((a,b)->a+b,12,14));System.out.println("sub :"+LambdaUse((a,b)->a-b,12,14));}public static int LambdaUse(ILambdaCaculator lambda,int a,int b){return lambda.result(a, b);}}

运行结果是:

add :26sub :-2

眼细的读者可以发现了,ILambdaCaculator接口中int result(int a, int b)方法有返回值的,既然(a,b)->a+b是映射到int result(int a, int b)这个方法,那应该也有返回值啊,其实我们知道返回值就是a+b,知道单条执行语句的时候,编译器会自动的将语句的执行结果返回,我们不用显示的调用,显示调用还会错。



但是如果->后面跟的是 {……执行语句………},那就必须要显示的调用return了。

System.out.println("sub :"+LambdaUse((a,b)->{ return a-b;},12,14));

到现在大家都应该知道怎么使用了,那下面就介绍一些lambda表达式比内部类好的事情,简洁,直接进入主题,这些就不讨论了,我们来讨论一下lambda的词法作用域。简单地说,就是从代码的角度看lambda的作用域和他所在作用域一样,不会存在像内部类会出现变量隐藏的问题。那什么是变量隐藏问题呢?

举个例子:

import java.util.function.Consumer;public class VaraibleHide {interface IInner {void printInt(int x);}public static void main(String[] args) {int x = 20;IInner inner = new IInner() {@Overridepublic void printInt(int x) {System.out.println(x);}};inner.printInt(30);}}

然后你会发现在内部类中引用的x并不是在main静态方法中的x,也就是内部类里面的x将外面的x隐藏了,但是如果使用Lambda表达式呢?


你会发现如果你括号里面使用x就会报错,报错信息是:



然后那我们换一个变量a,然后输出x

import java.util.function.Consumer;public class VaraibleHide {interface IInner {void printInt(int x);}public static void main(String[] args) {int x = 20;IInner inner =(a)->{System.out.println(x);};inner.printInt(30);}}

输出很明显是20.

所以使用lambda表达式不会出现隐藏问题,而这个在用this的使用最容易出错。

首先看个例子:

import java.util.function.Consumer;public class VaraibleHide {interface IInner {void printToString();}public static void main(String[] args) {VaraibleHide hide=new VaraibleHide();hide.test();}public void test(){IInner inner =()->{System.out.println(this);};IInner inner2=new IInner() {@Overridepublic void printToString() {System.out.println(this);}@Overridepublic String toString() {return "anonymous class toString";}};inner.printToString();inner2.printToString();}@Overridepublic String toString() {// TODO Auto-generated method stubreturn "Outter toString";}}

输出是:

Outter toStringanonymous class toString

所以很明显在lambda表达式中的上下文和lambda表达式所在的上下文一样,但是内部类就不一样了,这种情况会经常在需要传上下文的时候出错。比如在安卓中,按钮的点击事件。

        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Toast.makeText(this,"Click me! "+v.getId(),Toast.LENGTH_SHORT).show();            }        });

之后我会告诉大家为什么lambda表达式的上下文和外面的方法的上下文是一样的。

我们知道我们要使用lambda表达式,都要有一个函数式接口和它对应,那是不是我们要使用的时候,都要自己定义接口呢?

其实java已经给我们提供了部分接口。这些接口在java.util.function的包里面。

总结起来就是四种类型:

 1.功能型接口  即接受参数也提供返回值  Function<P,T>

 2.供给型接口  不接受参数但是有返回值  Supplier<T>

 3.断言式接口  主要用于判断          Predicate<P>

 4.消费型接口  接受参数没有返回值     Sonsumer<P>

上面四个是最基础的,java.util.function还有基于它们的一个其他的接口,大家可以自行去看,那下面通过一段代码让大家看看这几个接口。这里例子纯属是为了显示,没有其他含义。

import java.util.ArrayList;import java.util.List;import java.util.Random;import java.util.function.Consumer;import java.util.function.Function;import java.util.function.Predicate;import java.util.function.Supplier;/** * 省去了书写繁琐的内部类 lambda表达式可以使用于函数式接口,此接口只有一个方法,注解@FunctionalInterface限制 * 一共有四种功能性接口(这些接口java已经内建了) 1.功能型接口 即接受参数也提供返回值 Function<P,T> 2.供给型接口 * 不接受参数但是有返回值 Supplier<T> 3.断言式接口 主要用于判断 Predicate * <P> * 4.消费型接口 接受参数没有返回值 Sonsumer * <P> *  * @author wangpeiyu * */public class LambdaTest {public static void main(String args[]) {// 测试功能型// 平方Integer aInteger = new Integer(12);String string = "outerline";Function<Integer, Integer> function = a -> {return a * a;};System.out.println("功能型接口  :" + function.apply(aInteger));// 测试供给型接口// 生成一个随机数Supplier<Integer> supplier = () -> {Random random = new Random();return random.nextInt(45);};System.out.println("供给型接口  " + supplier.get());// 断言式接口// 判断一个数是否大于0Predicate<Integer> predicate = a -> {return a > 0;};System.out.println("断言式接口  " + predicate.test(18));// 消费性接口// 输出输入的参数Consumer<String> consumer = a -> {System.out.println("消费性接口内部  " + a.length() + "  " + string);};System.out.println("调用消费性接口");consumer.accept("xiaofeixingjiekoucanshu");List<String> list = new ArrayList<>();list.stream().filter((String s) -> {return s.length() < 10;}).map(str -> str.length()).forEach(str2 -> System.out.println(str2));/** * 测试内部类与lambda表达式的词法作用域上的差别 */LambdaTest test = new LambdaTest();System.out.println("测试lambda和内部类的词法作用域");test.print();}public void print() {Consumer<String> consumer = str -> {System.out.println(str + "  " + this);};Consumer<String> consumer2 = new Consumer<String>() {@Overridepublic void accept(String t) {System.out.println(t + "  " + this);}@Overridepublic String toString() {return "inner class ";}};consumer.accept("lambda");consumer2.accept("anonymous Class");}@Overridepublic String toString() {return "outter class ";}}

下面我们介绍lambda表达式在android中如何使用。大家有知道一点,在java中能使用的东西在android中不一定能使用的哦,想想java运行在jvm上,android运行在Dalvik或者Art虚拟机,这得看android系统是否支持,所以要在android中使用虚拟机目前有两种配置方式:

方式一:也是Google官方推荐的:

1.Module:appbuild.grade文件中的

android {defaultConfig{………//添加这句话jackOptions.enabled=true;}}

2.同样在在Module:appbuild.grade文件中的,在

android{   //增加下面的compileOptions{        sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8;        targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8;    }}

3.点击同步就可以使用了。

注意:

当使用这个方式的时候,将会将javac的工具链转化成新的 Jack工具链, javac 工具链:javac (.java --> .class) --> dx (.class --> .dex)jack工具链Jack (.java --> .jack --> .dex)。使用Jack时不能同时使用APT,如果使用butterknifeDagger等使用了APT的注解框架就不行了。所以这个时候可以采用方式二。

方式2:

1.在项目的build.gradeProject)文件下的四个地方增加代码。

1.1 

buildScript{repositories{//增加这句话mavenCentral()}dependencies{//增加这两句话classpath 'me.tatarka:gradle-retrolambda:3.6.1'classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'}//增加这句话configurations.classpath.exclude group: 'com.android.tools.external.lombok'}allprojects {    repositories {//增加这句话mavenCentral()}}

2.Module:appbuild.grade文件中的增加两处地方

1.在最开头增加这两句话

apply plugin: 'com.android.application'apply plugin: ‘me.tatarka.retrolambda'

2.


android{   //增加下面的compileOptions{        sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8;        targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8;    }}

3.然后点击同步就可以使用lambda表达式了。

button = (Button) findViewById(R.id.button);        //使用Lambda表达式,完成点击事件的处理        button.setOnClickListener(v->Toast.makeText(this,"Click me! “+v.getId(),Toast.LENGTH_SHORT).show());


很清晰,完美。下一篇将会介绍lambda的底层实现原理。