Android专题Android基础知识Java专题

Lambda表达式告别@override

2020-09-10  本文已影响0人  千夜零一

引言

 什么是Lambda呢?你肯定想到了数学符号中的那个“入”形状的符号吧?它可不是数学界的专利,在java中也有哦,本文针对Android开发中的Lambda表达式,详细介绍并且教你如何使用,简化你的代码,让你的代码看起来清新脱俗~


介绍

  Lambda 是匿名函数的别名。简单来说,就是对匿名内部类的进一步简化。使用 Lambda 表达式的前提是编译器可以准确的判断出你需要哪一个匿名内部类的哪一个方法。


用法

第一步:添加Lambda表达式(Module下build.gradle)

因为java1.8环境下支持lambda表达式的使用,故

compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
}

第二步:布局文件

简单添加一个按钮,并为其设置id值

    <Button
        android:id="@+id/btn_LambdaShow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="千夜零一"
        android:textSize="20sp" />

第三步:点击事件处理

我们最经常接触使用匿名内部类的行为是为 view 设置 OnClickListener ,这时你的代码是这样的:

//一般写法
public class Case20 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_case20);

        Button btnShow = findViewById(R.id.btn_LambdaShow);
        btnShow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getBaseContext(),"hello,千夜零一!",Toast.LENGTH_SHORT).show();
            }
        });
    }
}

使用匿名内部类,实现了对象名的隐匿;而匿名函数,则是对方法名的隐匿。所以当使用 lambda 表达式实现上述代码时,是这样的:

//lambda表达式
public class Case20 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_case20);
        btnShow.setOnClickListener((View)->{
             Toast.makeText(getBaseContext(),"hello,Lambda!",Toast.LENGTH_SHORT).show();
        });
    }
}

你只要理解,lambda表达式不仅对对象名进行隐匿,更完成了方法名的隐匿,展示了一个接口抽象方法最有价值的两点:参数列表和具体实现。


【 干货】:lambda表达式的三种形式

在 Java 中,lambda表达式共有三种形式:==函数式接口==、==方法引用==和==构造器引用==。其中,函数式接口形式是最基本的 lambda 形式,其余两种形式都是基于此形式进行拓展。

函数式接口(形式一)

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

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

排序比较函数例子:两个参数

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;
    }
};

此时使用lambda进行简化,就变成了这样

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

常用点击事件函数例子:一个参数

当参数只有一个时,参数列表两侧的圆括号也可省略,比如 OnClickListener 接口可写成

interface OnClickListener {
    void onClick(View v);
}

OnClickLisenter listener = v -> {语句块...};

然而,当方法没有传入参数的时候,则记得提供一对空括号假装自己是参数列表(雾),比如 Runnable 接口:

interface Runnable {
    void void run();
}

Runnable runnable = () -> {语句块...};

当语句块内的处理逻辑只有一句表达式时,其两侧的花括号也可省略,特别注意这句处理逻辑表达式后面也不带分号。比如这个关闭 activity 的点击方法:

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

同时,当只有一句去除花括号的表达式且接口方法需要返回值时,这个表达式不用(也不能)在表达式前加 return ,就可以当作返回语句。下面用 Java 的 Function 接口作为示例,这是一个用于转换类型的接口,在这里我们获取一个 User 对象的姓名字符串并返回:

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();
    }
};
Function<User, String> function = user -> user.getName();

方法引用(形式二)

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

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

我们知道,TextUtils的isEmpty()方法实现了上述功能,所以我们可以写作:

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

这时我们调用了已存在的方法来进行逻辑判断,我们就可以使用方法引用的形式继续简化这一段 lambda 表达式:

Predicate<String> predicate = TextUtils::isEmpty(s);

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


方法引用形式的三种格式:

第一种:

object :: instanceMethod

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

第二种:

ClassName :: staticMethod

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

第三种:

ClassName :: instanceMethod

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

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;

运行效果

Lambda.gif

构造器引用

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


解析

this 关键字

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

方法数量差异

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


千夜零一:“之前总是看各种博客学习东西,现在我想用博客记录下我的学习脚步,好东西也需要分享,索取和给予是相互的。以后会尽量日更的!目标完成1001篇博客哈哈。”
  如果觉得对你有所帮助,请不要吝啬你的点赞,有问题也可以在下方评论区留言哦,关注我一起学习吧~

上一篇下一篇

猜你喜欢

热点阅读