Java Lambda基础入门

img1

1.背景

经常在使用Rxjava或者RxBind等一些函数式编程语言的时候就会发现有很多简写的语句,比如()->{}这样子的东西,刚开始看不懂这些都代表什么意思,更不知道要怎么去写这样子看起来有逼格的代码,查了些资料,发现原来这是JAVA 8新特性Lambda表达式。喜欢研究新技术的我呢,当然是不会落下这学习新东西的机会,于是,玩了一周的零碎时间来看了下我们的Lambda表达式,今天来总结一下我对Lambda表达式的粗浅认识。

2.什么是Lambda?

Lambda表达式:我们可以简单的理解为一个匿名函数的速写形式(当然在java里的实现是和匿名函数有区别的),它帮助我们解决了很多项目中的冗余代码,在几乎不增加代码宽度的情况下大幅减少了代码的高度,使我们的代码变得更加精练、简洁,而且由于其实现方式有别于匿名函数,在一定程序上提高了效率。

3.什么是函数式接口?

函数式接口是只包含一个抽象方法的接口。比如我们的OnClickListener接口

public interface OnClickListener {

    void onClick(View v);
}

就是一个典型的函数式接口,Java 8里也定义了一种新的接口:@FunctionalInterface,用于指明该接口类型声明是根据 Java 语言规范定义的函数式接口。

4.Lambda的结构构成

() -> {} 一段带有输入参数的可执行语句块。

1.一个Lambda由(),->,{},其中()表示参数,{}表示执行语句或者代码块,由->隔开。
2.所有的参数都在()内,这里的参数主要是函数式接口里抽象方法所接收的参数,如果参数只有一个可以不用写(),如v -> {代码体}代码一个点击事件的Lambda写法;如果没有参数,那么()必须写,如() -> {System.out.println("aaa")}
3.所有的代码体都必须写在{}里,代码体可以为0或者多条代码。如果只有一条语句,{}可省略,反之,必须要有。
4.代码体的返回值类型与函数式接口的返回值类型一致。若没有,则返回为空。

5.Lambda表达式举例

触摸事件的监听处理:

//old way:
mBtn.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return false;
        }
    });

    //new way: 
    mBtn.setOnTouchListener((v, event) -> {
        return false;
    });

6.方法引用(Method Preference)

方法引用允许我们定义一个方法,然后以函数式接口的方式去访问它,也就是说通过方法引用,我们只需要写一个方法,然后通过方法名称直接引用该方法就可以了.方法引用可以说是在lambda表达式的基础上又进行了一次简化。而且方法引用还可以直接对已有方法进行引用,也就是说,连方法都不需要定义直接使用已有方法就可以了。代码相当的简洁。

7.方法引用格式

方法引用使用::分隔符来定义方法引用。常见的方法引用方式有以下三种:

1.ObjectName::instanceMethod (对象::实例方法)
2.ClassName::StaticMethod (类名::静态方法)
3.ClassName::instanceMethod (类名::实例方法)

1,2都是比较正常的,参数都是函数式接口对应的参数,而3就比较特别一点,函数式接口对应参数的第一个参数会被拿来当作执行对象。如String::compareToIgnoreCase等同于(x,y) -> x.compareToIgnoreCase(y)。

方法引用例子:

class Person {
private final String name;
private final int age;
public int getAge() { return age; }
public String getName() {return name; }
  ...
}

Person[] people = ...
//这里使用的是普通的lambda
Comparator<Person> byName = Comparator.comparing(p -> p.getName());
Arrays.sort(people, byName);

//使用方法引用 也就是上面的p参数会调用我们这里的getName方法
Comparator<Person> byName = Comparator.comparing(Person::getName);

看到上面的Person::getName是不是觉得好神奇…我们这里调用的相当于自定义的方法,lambda自动帮我们封装了一个函数式接口。
至于调用已存在不需要自己自定义的方法,在后面Rxbinding再讲。

8.构造器引用

构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。

9.Lambda表达式综合例子

这里主要是对比一下普通不用lambda表达式,使用lambda表达式以及使用方法引用完成同一个功能的实现方式.

//普通实现
List<String> names = new ArrayList<>();
names.add("zhangshan");
names.add("lisi");
List<String> upcaseNames = new ArrayList<>();
for (String name : names) {
  upcaseNames.add(name.toUpperCase());
}

//lambda实现
List<String> upcaseNames = names.stream().map(name -> name.toUpperCase()).collect(Collectors.toList());

//方法引用的实现(这里调用的就是已经存在的方法)
List<String> upcaseNames = names.stream().map(String::toUpperCase).collect(Collectors.toList());

上面的三种实现已经很明显了吧,代码结构越来越简洁。当然这里我们先不用去管Stream,map以及collect这三个操作符,它们是Java 8函数式编程的新特性。

10.Lambda表达式与匿名内部类的区别

在很大程度上,我们可以理解为lambda解决的就是匿名内部类的高度问题,但实际上,它们是有本质区别的。

1.它们在底层实现上是不一样的,lambda的底层实现更加优化。
2.关于关键字this,两者的有区别的。lambda表示创建表达式的外部类,而匿名内部类指向的是自己本身。
3.两者同样都可以引用外部类的变量,而且都是要在变量不可变的情况下。匿名内部类需要显示的对外部类的变量加final修辞符,而lambda不需要显示的加final修辞符,编译器会隐式的加上。

11.Lambda扩展:Rxbinding的使用

使用Rxjava的同学们应该也会使用Rxbinding来进行一些绑定的操作,为什么用呢?应该它是函数式的。RxBinding也支持使用lambda表达式

比如:按钮的点击事件处理

RxView.clicks(mBtn).subscribe(this::onclicka);
private void onclicka(Void aVoid) {

}

这里使用的就是方法引用的调用自定义方法的操作。

再来看,TextView的textChanged事件

RxTextView.textChanges(mTv).subscribe(mTv::setText);

这里使用的就是方法引用的调用已存在方法的操作。

好了,今天的lambda之旅就到这里了。