lambda表达式的使用详解

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

interface ILambdaTest1{
	void print(String s);
}
public class LambdaTest1 {
	public static void main(String[] args) {
		//传统内部类的实现
		LambdaUse(new ILambdaTest1() {
			@Override
			public 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,用这个注解修饰一个接口,这个接口只能有一个抽象的方法。这类接口称为函数式接口。

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

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

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

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


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

@FunctionalInterface
interface 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 :26
sub :-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() {

			@Override
			public 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() {
			@Override
			public void printToString() {
				System.out.println(this);
			}
			@Override
			public String toString() {
				return "anonymous class toString";
			}
		};
		inner.printToString();
		inner2.printToString();
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "Outter toString";
	}
}

输出是:

Outter toString
anonymous 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());
		// 断言式接口
		// 判断一个数是否大于0
		Predicate<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>() {
			@Override
			public void accept(String t) {
				System.out.println(t + "  " + this);
			}

			@Override
			public String toString() {
				return "inner class ";
			}
		};

		consumer.accept("lambda");
		consumer2.accept("anonymous Class");
	}

	@Override
	public 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的底层实现原理。










  • 10
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值