Lambda在Android中的使用

Lambda语法在Android中的应用

0.作用

lambda是匿名函数的别名,用来简化匿名内部类,使用lambda的前提是编译器可以准确的推断出你需要哪个匿名内部类被简化。

0.1 例子

我们最常接触到的例子是view设置onClickListener:

1
2
3
4
5
button.setOnClickListener(new View.OnClickListener(){
@Override public void onClick(View v){
doSomeWork();
}
});

使用lambda:

1
2
3
4
5
button.setOnClickListener(
(View v) -> {
doSomeWork();
}
);

lambda不仅把对象名隐匿(View.OnClickLisetener),更完成了方法名的隐匿(onClick(View v)),展示了一个接口抽象方法最有价值的亮点:参数实现方法

1.lambda表达式的形式

1.1 函数式接口

函数式接口是最基本的lambda表达式,其余的形式都是由其扩展而来

函数式接口是指有且只有一个抽象方法的接口, 比如各种Listener和Runnable接口。lambda表达式就是对这类接口的匿名接口内部类进行简化,基本形式如下:

(参数列表 ... ) -> { 语句块...}

面以 Java 提供的 Comparator 接口来展示一个实例,该接口常用于排序比较:

1
2
3
4
5
6
7
8
9
10
11
interface Comparator<T> {int compare(T var1, T var2);}
Comparator<String> comparator = new Comparator<String> (){
@Override public int compare(String s1, String s2) {
doSomeWork();
return result;
}
};
Comparator<String> comparator = (String s1, String s2) -> {
doSomeWork();
return result;
};

当编译器可以推到出具体的参数类型,可以忽略参数类型,简化:

1
2
3
4
Comparator<String> comparator = ( s1 , s2 ) -> {
doSomeWork();
return result;
};

当参数只有一个时, 参数列表可以去掉括号(), 简化:

1
2
interface OnClickListener { void onClick(View v); }
OnClickListener listener = v -> { 语句块... } ;

但是,当没有参数传入的时候,参数列表的括号就要保留:

1
2
interface Runnable { void run(); }
Runnable runnable = () -> { 语句块... } ;

当具体实现的处理逻辑只有一句的时候,也可以省略语句块的{}:

1
button.setOnClickListener( v -> activity.finish() );

具体实现只有一句,并且是个return返回值的时候,这个表达式不用在表达式前面加retrun:

1
2
3
4
5
6
7
8
interface Function <T, R> { R apply(T t); }
Function <User, String> function = new Function <User, String>(){
@Override public String apply(User user) {
return user.getName();
}
};
//不能加return,因为只有一句表达式,并且是return一个值
Function <User, String> function = user -> user.getName() ;

1.2 方法引用

当我们使用函数接口的lambda的时候,进行逻辑实现的时候,我们既可以自己实现一系列处理,也可以调用已存在的方法,下面以 Java 的 Predicate 接口作为示例,此接口用来实现判断功能,我们来对字符串进行全面的判空操作:

1
2
3
4
5
6
interface Predicate<T> { boolean test(T t); }
Predicate<String> predicate=
s -> {
//用基本代码组合进行判断
return s==null || s.length()==0 ;
};

我们知道,TextUtils.isEmpty()实现了上述功能,所以我们可以调用已存在的方法来进行逻辑判断:

1
Predicate<String> predicate = s -> TextUtils.isEmpty(s);

当我们调用已存在的方法来进行逻辑判断,可以简化使用lambda的方法引用:

1
Predicate<String> predicate = TextUtils::isEmpty;

方法引用就是:当逻辑实现只有一句,且调用了已存在的方法进行处理(静态方法,this,super的方法也包括在内)时,对函数接口形式的lambda的进一步简化。传入方法的参数就是原接口的参数。

接下来总结三种方法引用的形式:

  1. object::instanceMethod

    直接调用任意对象的实例方法,如obj::equals 代表调用了obj这个对象的equals方法与接口方法参数比较是否相等,效果等同于obj.equals(t)。当前类的方法可以直接调用this::method,父类方法同理。

  2. ClassName::staticMethod

    直接调用某类的静态方法,并将接口方法参数传入,如上述TextUtils::isEmpty 等同于TextUtils.isEmpty(s)

  1. ClassName::instanceMethod

    较为特殊,将接口方法参数列表的第一个参数作为方法调用者,其余参数作为方法参数。由于此类接口较少,故选择Java提供的BiFunctin接口作为示例,该接口接受一个T1类,T2类对象,并返回R对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface BiFunction<T1, T2, R> {
    R apply(T1 t1, T2 t2);
    }
    BiFunction<String,String,Boolean> biFunction=
    new BiFunction<String, String, Boolean>() {
    @Override public Boolean apply(String s1, String s2){
    return s1.equals(s2);
    }
    };
    // ClassName 为接口方法的第一个参数的类名,同时利用接口方法的第一个参数作为方法调用者,其余参数作为方法参数,实现 s1.equals(s2);
    BiFunction<String,String,Boolean> biFunction= String::equals;

1.3 构造器引用

Lambda 表达式的第三种形式,其实和方法引用十分相似,只不过方法名替换为 new 。其格式为 ClassName :: new。这时编译器会通过上下文判断传入的参数的类型、顺序、数量等,来调用适合的构造器,返回对象。

2. 一点玄学

2.1 this关键字

在匿名内部类中,this 关键字指向的是匿名类本身的对象,而在 lambda 中,this 指向的是 lambda 表达式的外部类。

2.2 方法数差异

前 Android Studio 对 Java 8 新特性编译时采用脱糖(desugar)处理,lambda 表达式经过编译器编译后,每一个 lambda 表达式都会增加 1~2 个方法数。而 Android 应用的方法数不能超过 65536 个。虽然一般应用较难触发,但仍需注意。