Lambda简介

A lambda expression is a block of code with parameters. lambda表达式是带参数的代码块。

虽然看着很先进,其实Lambda表达式的本质只是一个”语法糖”,由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。本人建议不要乱用(前提是接手的开发人员没有学过Lambda),因为这就和某些很高级的黑客写的代码一样(简洁,难懂,难以调试)

Lambda表达式可以和RxJava结合,写出简洁优雅的高质量代码,大大缩短代码量;

为什么要使用Lambda表达式

随着回调模式和函数式编程风格的日益流行,我们需要在Java中提供一种尽可能轻量级的将代码封装为数据(Model code as data)的方法。匿名内部类并不是一个好的选择,因为:

  • 语法过于冗余

  • 匿名类中的this和变量名容易使人产生误解

  • 类型载入和实例创建语义不够灵活

  • 无法捕获非final的局部变量

  • 无法对控制流进行抽象

上面的多数问题均在Java SE 8中得以解决:

  • 通过提供更简洁的语法和局部作用域规则,Java SE 8彻底解决了问题1和问题2

  • 通过提供更加灵活而且便于优化的表达式语义,Java SE 8绕开了问题3

  • 通过允许编译器推断变量的“常量性”(finality),Java SE 8减轻了问题4带来的困扰

让Android Stutio支持Lambda

目前Android开发工具已经从Eclipse 的ADT迁移到了Android Studio,但是Android Studio目前还没有直接支持Lambda,需要插件retrolambda支持,当然,JDK版本也必须使用JDK 8 或者以上。按照下面步骤配置Gradle:

首先先项目根目录下的build.gradle中加入

 classpath 'me.tatarka:gradle-retrolambda:3.6.1'

然后再module目录下的build.gradle中使用插件,加入

 apply plugin: 'me.tatarka.retrolambda'

并且在android节点下加入

 android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

对build.gradle进行build,OK,大功告成;

Lamdba使用

我们在Android开发中,经常会使用到大量的监听设置以及异步回调等场景。比如点击事件:

 textView.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "hello Lambda", Toast.LENGTH_LONG).show();
}
});

这种写法是我们以前最常见的,一个简单的监听事件,需要这么多行代码才能搞定,而且{}中嵌套{},看起来非常臃肿。匿名类型最大的问题就在于其冗余的语法,有人戏称匿名类型导致了“高度问题”(height problem):比如前面OnClickListener的例子里的五行代码中仅有一行在做实际工作。lambda表达式是匿名方法,它提供了轻量级的语法,从而解决了匿名内部类带来的“高度问题”。使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且可读性很高:

 textView.setOnClickListener( v -> Toast.makeText(getApplicationContext(), "Lambda", Toast.LENGTH_LONG).show());

Lambda表达式语法

lambda表达式通常以(argument)->(body)这样的格式书写。

lambda表达式的语法由参数列表、箭头符号->和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:

  • 表达式:表达式会被执行然后返回执行结果。
  • 语句块:语句块中的语句会被依次执行,就像方法中的语句一样

首先列举一个完整的lambda expression:

 (int a, int b) -> {
System.out.println("Performing add operation...");
return a+b;
}

一个lambda expression由三部分组成:

  • 参数:(int a, int b)是这个lambda expression的参数部分,包括参数类型和参数名

  • 箭头:->

  • 代码块:就是用”{}”包含着的那两句代码。

上面说的是一个完整的lambda表达式,在很多情况下,很多东西是可以省略的。比如说,当系统可以根据上下文自动推断出参数的类型的时候,参数类型是可以省略的。这样的话就可以写成:

lambda表达式中,参数的类型可省略。Java编译器根据表达式的上下文推导出参数的类型。就像上面中view的类型是View。

 (a, b) -> {
System.out.println("Performing add operation...");
return a+b;
}

系统怎么自动推断出参数类型的呢?这个在下面我们就可以看到。
再比如,如果只有一个参数,而参数的类型又可以自动判断,那么连()也是可以省略的,那么就写成了:

 a -> {
System.out.println("Performing add operation...");
return a+a;
}

再再比如,如果代码块里面只有一行代码,那么{}也是可以省略的,那么就写成了:

 a ->
return a+a;

是的,甚至可以写在同一行

 a -> return a+a;

让我们更进一步,在这里,return其实也是没必要的。

 a -> a+a;

很好(Great), 如果没有参数的话,是不是就可以写成呢?:

 -> a+a

很可惜,答案是否定的。如果没有参数,那么前面的()是必须存在的。也就是说,必须写成:

 ()-> a+a

lambda表达式的结构:

  1. 参数可以是零个或多个
  2. 参数类型可指定,可省略(根据表达式上下文推断)
  3. 参数包含在圆括号中,用逗号分隔
  4. 表达式主体可以是零条或多条语句,包含在花括号中
  5. 表达式主体只有一条语句时,花括号可省略
  6. 表达式主体有一条以上语句时,表达式的返回类型与代码块的返回类型一致
  7. 表达式只有一条语句时,表达式的返回类型与该语句的返回类型一致

函数式接口(Functional interfaces)

实际上,如果你直接把上面的代码放到你的编辑器里面,你的IDE是会报错的,因为lambda是不能这样使用的。lambda的使用永远要跟一个叫做Functional Interface的东西绑定在一起。什么叫Functional Interface呢?Functional Interface也是Java8 中引入的概念.是的,是为了lambda。我们知道java中的interface,而Functional Interface其实就是一个只定义了一个抽象方法的interface。比如Runnable 这个interface就只有一个run方法,那么它就是一个Functional Interface

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

函数式接口是只有一个抽象方法的接口。用作表示lambda表达式的类型。

在文章开头,我们使用OnClickListener这个监听接口体验了Lambda表达式,有的同学可能在想,如果有了Lambda表达式以后,是不是所有的接口都能用这么简洁的方式了?比如下面:

 EditText editText = (EditText) findViewById(R.id.editText);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
} @Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
} @Override
public void afterTextChanged(Editable s) {
}
});

TextWatcher这个接口能使用Lambda语法吗?答案是NO,并不是所有的接口都能使用Lambda语法,如果要使用Lambda语法,这个接口必须符合规范,它必须是“函数式接口”。什么是函数式接口?

一个函数式接口是一个能够使用类函数做为参数的类型,一个lambda表达式支持提供这样一个实参。好深奥的样子,举个通俗的栗子,我们设置点击监听是为了在按钮被点击之后执行一段代码,按照普通人的思想,代码应该这样写(伪代码):

button.set点击监听({点击后需要执行的代码});

我们应该将{点击后需要执行的代码}直接作为参数传入,当然为了体现封装的特性,我们需要将这段代码块封装成方法:

 button.set点击监听(onClick());

 onClick(){
点击后需要执行的代码;
}

我们知道java是面向对象的编程语言,不支持方法作为参数被传入,所以必须用一个接口去封装它,这就是OnClickListener,我们传统的匿名类方式。而Lambda表达式在表面上来看就像是直接将一段代码作为方法参数传入了(本质就是一个匿名的方法),更像是面向函数的编程语言,为了能够使用这样简洁的编程方式,必须给它定义个规矩,这就是“函数式接口”为什么会出现,它就是规矩,要想使用Lambda表达式,接口必须遵循这个规矩。

那函数式接口到底需要满足怎样的条件?

一个接口,如果只有一个显式声明的抽象方法(可以有其他非抽象方法),那么它就是一个函数接口(之前它们被称为SAM类型,即单抽象方法类型(Single Abstract Method))。函数式接口一般用@FunctionalInterface标注出来(也可以不标)。

卧槽,接口怎么能允许有非抽象方法呢?那不是抽象类了吗?但是jdk8中接口就能有非抽象方法,这些方法由default修饰,jdk8在java.util.function包中新增了很多函数式接口,这些接口都只有一个抽象方法,但有多个default修饰的实现方法,这些接口上都有一个@FunctionalInterface的注解,表示这是一个函数式接口,加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。如果带@FunctionalInterface注解的接口定义了两个抽象方法会报语法错误,这也成了检测函数式接口的一种方式。比如下面function包中新增的函数式接口:

 /**
* 函数式接口
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> { /**
* 抽象方法
*/
R apply(T t); default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
} default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
} static <T> Function<T, T> identity() {
return t -> t;
}
}

默认方法和静态接口方法:

Java SE 7时代为一个已有的类库增加功能是非常困难的。具体的说,接口在发布之后就已经被定型,除非我们能够一次性更新所有该接口的实现,否则向接口添加方法就会破坏现有的接口实现。默认方法(之前被称为虚拟扩展方法或守护方法)的目标即是解决这个问题,使得接口在发布之后仍能被逐步演化。

默认方法利用面向对象的方式向接口增加新的行为。它是一种新的方法:接口方法可以是抽象的或是默认的。默认方法拥有其默认实现,实现接口的类型通过继承得到该默认实现(如果类型没有覆盖该默认实现)。此外,默认方法不是抽象方法,所以我们可以放心的向函数式接口里增加默认方法,而不用担心函数式接口的单抽象方法限制。

除了默认方法,Java SE 8还在允许在接口中定义静态方法。这使得我们可以从接口直接调用和它相关的辅助方法(Helper method),而不是从其它的类中调用(之前这样的类往往以对应接口的复数命名,例如Collections)。比如,我们一般需要使用静态辅助方法生成实现Comparator的比较器,在Java SE 8中我们可以直接把该静态方法定义在Comparator接口中:

 public static <T, U extends Comparable<? super U>>
Comparator<T> comparing(Function<T, U> keyExtractor) {
return (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

关于函数式接口,有兴趣的同学可以去了解一下java.util.function这个包中的接口;我就不多说了。关键的是记住一条,Lambda表达式只适用于只有只有一个抽象方法的“函数式接口”。这就是为什么TextWatcher不能使用Lambda表达式(有3个抽象方法)。

Lambda表达式的目标类型以及匹配

我们可以把Lambda表达式当做是一个Object,但它有自己的类型名称叫做“目标类型(target type)”。Lambda表达式的目标类型是“函数接口(functional interface)”,我们不能把Lambda表达式直接赋值给Object,一个Lambda表达式只有在转型成函数接口后才能被当作Object使用。比如:

 //可以用一个Lambda表达式为一个函数接口赋值
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
//然后再赋值给一个Object
Object obj1 = r1; //不能这样干 ERROR! Object is not a functional interface!
Object obj2 = () -> {System.out.println("Hello Lambda!");}; //必须显式的转型成一个函数接口才可以
Object obj3 = (Runnable) () -> { System.out.println("Hello Lambda!"); }; //Lambda表达式只有在转型成一个函数接口后才能被当做Object使用,所以下面这句也不能编译
System.out.println( () -> {} ); //错误! 目标类型不明确,println方法可以接受任何类型的参数,而() -> {}能匹配上很多函数接口 //必须先转型
System.out.println( (Runnable)() -> {} ); // 正确

  一个Lambda表达式可以有多个目标类型(函数式接口),只要函数匹配成功即可,java编译时会自动根据上下文去推断对应的函数接口。但是Lambda表达式必须至少有一个目标类型,要不然就是语法错误。栗子:

如果SDK中有另一个跟OnClickListener一样的函数接口,但是接口名不一样:

 public interface MyOnClickListener {
void onClick(View v);
}

下面这句代码也是正确的,编译的时候发现setOnClickListener方法接受的是OnClickListener类型,而v -> Log.v(TAG, “Lambda”)正好符合onClick(View v)方法,所以java编译器就会自动将其匹配为OnClickListener函数接口,而不是MyOnClickListener。

 findViewById(R.id.textView).setOnClickListener( v -> Log.v(TAG, "Lambda"));

Lambda表达式 For Android的更多相关文章

  1. Lambda表达式在Android开发中的应用

    在Java8中拥有Lambda表达式的新功能,如果现在Android项目中使用,首先,必须在项目中的build.gradle配置一下 使用Lambda表达式必须满足只有一个待实现方法这个规则,否则就不 ...

  2. Android Stutio中使用java8的Lambda表达式

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51532028 本文出自:[openXu的博客] 目录: 为什么要使用Lambda表达式 让A ...

  3. 释放Android的函数式能量(I):Kotlin语言的Lambda表达式

    原文标题:Unleash functional power on Android (I): Kotlin lambdas 原文链接:http://antonioleiva.com/operator-o ...

  4. 在Android中使用Java 8的lambda表达式

    作为一名Java开发者,或许你时常因为缺乏闭包而产生许多的困扰.幸运的是:Java's 8th version introduced lambda functions给我们带来了好消息;然而,这咩有什 ...

  5. Android Studio 引入Lambda表达式

    依次点击 [File][Other Settings][Default Project Structure]确保当前项目使用的JDK版本是1.8. 打开项目(Project)的build.gradle ...

  6. Android 1.7 中不支持 lambda 表达式

    Error:(129, 32) 错误: -source 1.7 中不支持 lambda 表达式 (请使用 -source 8 或更高版本以启用 lambda 表达式) lambda expressio ...

  7. Android中使用Lambda表达式开发

    参考文章:ImportNew 要在Android开发中使用lambda表达式,首先需要在 Module 的build.gradle中加入: compileOptions { targetCompati ...

  8. Android中使用lambda表达式

    lambda 语法简介 视频为本篇播客知识点讲解,建议采用超清模式观看, 欢迎点击订阅我的优酷 如果刚学Android,不知道怎么写点击事件可以跳转,传送门 要想在Android中使用lambda语法 ...

  9. Kotlin的Lambda表达式以及它们怎样简化Android开发(KAD 07)

    作者:Antonio Leiva 时间:Jan 5, 2017 原文链接:https://antonioleiva.com/lambdas-kotlin/ 由于Lambda表达式允许更简单的方式建模式 ...

随机推荐

  1. jdk1.8一键安装脚本(linux环境)

    1.下载jdk安装包和安装脚本 下载地址:https://pan.baidu.com/s/1bo6ADQ3 其中包括: jdk安装包:jdk-8u151-linux-x64.tar.gz jdk一键安 ...

  2. 08-01 java 帮助文档的制作和使用,使用jdk提供的帮助文档

    01_帮助文档的制作和使用 制作说明书的流程 如何制作一个说明书呢? A:写一个工具类 B:对这个类加入文档注释 怎么加呢? 加些什么东西呢? C:用工具解析文档注释 javadoc工具 D:格式 j ...

  3. odoo开发笔记--字段追踪,消息通知机制

    odoo有着强大的消息记录.通知机制: 实际开发中,常常会有客户的需求,页面上form视图中的某些字段不允许反复修改, 假如有的用户修改了,恶意搞坏,往往容易给公司利益造成损失,或破坏,那么如何有效的 ...

  4. How To Scan QRCode For UWP (4)

    QR Code的全称是Quick Response Code,中文翻译为快速响应矩阵图码,有关它的简介可以查看维基百科. 我准备使用ZXing.Net来实现扫描二维码的功能,ZXing.Net在Cod ...

  5. MVC3学习:Sql Server2005中时间类型DateTime的显示

    在Sql Server2005中,如果将某字段定义成日期时间类型DateTime,那么在视图中会默认显示成年月日时分秒的方式(如 2013/8/6 13:37:33) 如果只想显示成年月日形式,不要时 ...

  6. (转)WebSphere的web工程中怎么获取数据源

    原文:http://aguu125.iteye.com/blog/1694313 https://blog.csdn.net/bigtree_3721/article/details/44900325 ...

  7. Enterprise Library 6 学习笔记

    今天是2014年上班第一天,想着今年要做点与往年不同的事情,就从写博客开始吧. 公司的项目一般都用微软的企业库,一直没时间好好研究,第一天上班还不忙,就抽空研究了下.搜索一下,发现这个企业库已经到了6 ...

  8. 解决Redisson出现Failed to instantiate [org.redisson.api.RedissonClient]: Factory method 'create' threw exception; nested exception is java.lang.ArrayIndexOutOfBoundsException: 0的问题

    一.背景 最近项目中使用了redisson的哨兵模式来作为redis操作的客户端,然后一个意外出现了,启动报:Failed to instantiate [org.redisson.api.Redis ...

  9. sql查询其他服务器数据库表

    exec sp_addlinkedserver 'abc', '', 'SQLOLEDB', '192.168.49.34' exec sp_addlinkedsrvlogin ' go --查询 s ...

  10. mysql的mysqli异步与php的携程

    <?php $begin = time(); //同步请求 function multi_sync(){ $host = '192.168.2.87'; $user = 'census'; $p ...