转载请标明出处:

http://blog.csdn.net/xmxkf/article/details/51532028

本文出自:【openXu的博客】

目录:

  Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁 ,他是一种函数式推导语言,能够大量减少匿名内部类那种冗余的代码。我们在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());

1. 为什么要使用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带来的困扰

2. 让Android Stutio支持Lambda

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

(1). 引入retrolambda插件:

  在Module:app 的build.gradle中添加

apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'  //添加此句

(2). 设置java版本

  在Module:app 的build.gradle的android节点中添加如下代码

android {
        compileSdkVersion 21
        buildToolsVersion "21.1.2"
         ...
         //设置java版本
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
}

(3). 引入retrolambda的类路径

  在project的build.gradle中的buildscript->dependencies节点中添加如下代码

buildscript {
    repositories {
        jcenter()
        mavenCentral() //添加
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
        classpath 'me.tatarka:gradle-retrolambda:3.2.5'  //添加
    }
}

//添加
// Required because retrolambda is on maven central
repositories {
    mavenCentral()
}

(4). 对build.gradle进行build

(5). 将下面代码拷贝到Activity中运行

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView textView = (TextView)findViewById(R.id.textview);

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

通过上面的配置,Android Stutio就能使用Lambda语法了,下面我们具体学习关于Lambda的相关知识。

3. 函数式接口(Functional interfaces)

  接下来的内容会一步步带大家了解Lambda表达式,但是在讲Lambda语法之前,我们需要认识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个抽象方法)。

4. 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"));

5. Lambda表达式语法

(1). 基础语法和语法简化

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

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

请看下面示例:

/**
* 示例中用到的函数接口:
*/
private interface Interface1{
    boolean hasPerson(String name, int age, String addr);
}
private interface Interface2{
    boolean hasPerson(String name);
}
private interface Interface3{
    boolean eat();
}
private interface Interface4{
    void eat();
}

下面是完整的形式:

private void lambda3(){
    Interface1 in1 = (String name, int age, String addr) -> {
        Log.v(TAG, "有这个人吗?");
        Log.v(TAG, "没有啊");
        return false;
    };
}

参数列表中的参数类型一般都能省略,译器都可以从上下文环境中推断出lambda表达式的参数类型。这样lambda表达式就变成了:

Interface1 in2 = (name, age, addr) -> {
    Log.v(TAG, "有这个人吗?");
    Log.v(TAG, "没有啊");
    return false;
};

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

Interface2 in21 = name -> {
    Log.v(TAG, "有这个人吗?");
    Log.v(TAG, "没有啊");
    return false;
};

当参数为空时,不能省略()

Interface3 in31 = () -> {
    Log.v(TAG, "吃饭了吗");
    return true;
};

当lambda表达式只包含一条语句时:

//当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号
Interface4 in4 = ()-> Log.v(TAG, "吃饭了吗");
//只有一return语句
Interface3 in32 = ()-> {return false;};
//可以省略return、大括号和分号(要省略都省略,不能只省略其中几项)
Interface3 in33 = () -> false;

(2). Lambda表达式的词法作用域

  在内部类中使用变量名(以及this)非常容易出错。内部类中通过继承得到的成员(包括来自Object的方法)可能会把外部类的成员掩盖,此外未限定的this引用会指向内部类自己而非外部类。

  相对于内部类,lambda表达式的语义就十分简单。它不会从父类(supertype)中继承任何变量名,也不会引入一个新的作用域。lambda表达式基于词法作用域,也就是说lambda表达式函数体里面的变量和它外部环境的变量具有相同的语义(也包括lambda表达式的形式参数)。此外,’this’关键字及其引用在lambda表达式内部和外部也拥有相同的语义。示例:

/**
 * Lambda表达式中的词法作用域
 */
private void lambdaThis(){
    //this代表的是外部的Activity对象
    Runnable r1 = () -> Log.v(TAG, this+"");//com.openxu.rxjava.MainActivity@527ed28c
    r1.run();
    //toString()是外部类Activity的toString,而不是Runnable的
    Runnable r2 = () -> Log.e(TAG, toString());//com.openxu.rxjava.MainActivity@527ed28c
    r2.run();
}

(3). 变量捕获

  在Java 7中,编译器对内部类中引用的外部变量(即捕获的变量)要求非常严格:如果捕获的变量没有被声明为final就会产生一个编译错误。java 8中放宽了这个限制,对于lambda表达式和内部类,我们允许在其中捕获那些符合有效只读的局部变量。 简单的说,如果一个局部变量在初始化后从未被修改过,那么它就符合有效只读的要求,换句话说外部变量加上final后也不会导致编译错误的局部变量就是有效只读变量。尽管我们放宽了对捕获变量的语法限制,但试图修改捕获变量的行为仍然会被禁止。示例:

/**
 * Lambda表达式中的变量捕获
 * lambda表达式对值封闭,对变量开放
 */
private void lambdaFinal() {
    String str = "abcdefg";
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            Log.v(TAG, str);   //只是打印str,并不作修改,允许
            // str = "abc"; //编译错误,str不能修改
        }
    };
    Runnable r2 = () -> Log.e(TAG, str);//只是打印str,并不作修改,允许
    //Runnable r3 = () -> {str = ""};//编译错误,str不能修改
}

特别说明:

  对this的引用,以及通过this对未限定字段的引用和未限定方法的调用在本质上都属于使用final局部变量。包含此类引用的lambda表达式相当于捕获了this实例。在其它情况下,lambda对象不会保留任何对this的引用。这个特性对内存管理是一件好事:内部类实例会一直保留一个对其外部类实例的强引用,而那些没有捕获外部类成员的lambda表达式则不会保留对外部类实例的引用。要知道内部类的这个特性往往会造成内存泄露。

Android Stutio中使用java8的Lambda表达式的更多相关文章

  1. 在Android中引入Java8的lambda表达式

    我用的是retrolambda这个插件,下面来说下如何添加它. 项目地址:https://github.com/evant/gradle-retrolambda 在根项目的build.gradle中添 ...

  2. 在Android项目中使用Java8

    前言 在过去的文章中我介绍过Java8的一些新特性,包括: Java8新特性第1章(Lambda表达式) Java8新特性第2章(接口默认方法) Java8新特性第3章(Stream API) 之前由 ...

  3. java8的lambda表达式,将List<DTO> 转为 List<DO>

    将List<PhoneDTO>转为List<PhoneDO>,通过java8的lambda表达式来操作,比传统的for循环精简很多: /** * List<PhoneDT ...

  4. java8的lambda表达式

    关于java8的lambda表达式 lambda表达式一般用于接口,因为lambda表达式是函数式编程. 1.有且仅有一个抽象方法被称为函数式接口,函数式接口可以显示的被@FunctionalInte ...

  5. JAVA8之lambda表达式具体解释,及stream中的lambda使用

    前言: 本人也是学习lambda不久,可能有些地方描写叙述有误,还请大家谅解及指正! lambda表达式具体解释 一.问题 1.什么是lambda表达式? 2.lambda表达式用来干什么的? 3.l ...

  6. IDEA无法编译java8的lambda表达式提示Error:(16, 48) java: -source 1.5 中不支持 lambda 表达式

    在idea中新建了一个java8的项目,但是写lambda表达式提示语法错误,提示如下错误信息: Error:(16, 48) java: -source 1.5 中不支持 lambda 表达式 (请 ...

  7. 30分钟入门Java8之lambda表达式

    前言 Google在今年发布Android N开发者预览版,一并宣布开始支持Java 8.我们终于能在Android开发中使用到Java8的一些语言特性了.目前支持: 默认方法 lambda表达式 多 ...

  8. java8之lambda表达式(1)-基本语法

    lambda表达式,即带有参数的表达式,为更清晰地理解lambda表达式,先看如下例子: (1) class Student{ private String name; private Double ...

  9. java8之lambda表达式入门

    1.基本介绍 lambda表达式,即带有参数的表达式,为了更清晰地理解lambda表达式,先上代码: 1.1 两种方式的对比 1.1.1 方式1-匿名内部类 class Student{ privat ...

随机推荐

  1. Python系列之 - python循环语句

    前两篇说的是数据类型和数据运算,本篇来讲讲条件语句和循环语句. 1. 条件语句 条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块. 可以通过下图来简单了解条件语句的执 ...

  2. [Codeforces 873B]Balanced Substring

    Description You are given a string s consisting only of characters 0 and 1. A substring [l, r] of s  ...

  3. [Tjoi2013]最长上升子序列

    Description 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.每插入一个数字,我们都想知道此时最长上升子序列长度是多少? Input 第一行一 ...

  4. NOIWC2018 游记

    day1 上午是自习,做了一些杂题,看了一下ppt,中午准备了一下行李,就出发了,提前了一个小时,谁知道被坑爹导航弄得居然到晚了一点 当走到这里的时候我愣住了 纠结了一分钟,直到有个boy走了进去,我 ...

  5. ●codeforces 553E Kyoya and Train

    题链: http://codeforces.com/problemset/problem/623/E 题解: FFT,DP 题意: 一个有向图,给出每条边的起点u,终点v,费用c,以及花费每种时间的概 ...

  6. 2015 多校联赛 ——HDU5400(水)

    Sample Input 5 2 -2 0 2 0 -2 0 5 2 3 2 3 3 3 3   Sample Output 12 5 求最多多少序列满足,前半部分满足d(j+1) = d(j)+d1 ...

  7. bzoj 2594: [Wc2006]水管局长数据加强版

    Description SC省MY市有着庞大的地下水管网络,嘟嘟是MY市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的水从x处送往y处,嘟嘟需要为供水公司找到一 ...

  8. POJ 2832 How Many Pairs?

    Description You are given an undirected graph G with N vertices and M edges. Each edge has a length. ...

  9. 勤拂拭软件Android开发之旅(1) 之 Android 开发环境搭建

    勤拂拭软件工作室原创出品,欢迎转载,欢迎交流. 转载请注明原文:http://www.cnblogs.com/wangleiblog/p/6019063.html 勤拂拭软件Android开发之旅目录 ...

  10. SpringCloud学习之Hystrix

    一.为什么要有断路器 在分布式系统当中,服务之间调用关系会随着业务的发展而变的复杂,一个服务可能依赖多个服务,服务之间层层依赖也是家常便饭的事情,如果一个服务的瘫痪很有可能导致整个系统的崩溃.比如说, ...