Lambda编程

参考链接

Lambda 表达式本质上就是可以传递给其他函数一小段代码。

有了 Lambda 可以轻松地把通用代码结构抽取成函数库,Kotlin 标准库大量使用了它。

什么是Lambda

我们知道,对于一个 Java 变量,我们可以赋给其一个“值”。如果你想把“一块代码”赋给一个 Java 变量,应该怎么做呢?

在 Java 8 之前,这个是做不到的。但是 Java 8 问世之后,利用 Lambda 特性,就可以做到了。

aBlockOfCode = public void doSomeShit(String s){
    System.out.println(s);
}

简便写法

  1. public 和函数名是多余的,因为已经赋值给了 aBlockOfCode.
  2. 编译器可以自动判断参数类型和返回类型。
  3. 只有一行可以省略大括号。

    aBlockOfCode = (s) -> System.out.println(s);
    

这样我们就将一个代码块成功赋值给了变量,那么 aBlockOfCode 的类型是什么呢?

在 Java8 中,所有 Lambda 类型都是一个接口,而等号右边则是接口的实现。

一个接口函数需要被实现的接口类型,我们叫它“函数式接口”,为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成”非函数接口”,我们可以在这个上面加上一个声明 @FunctionalInterface, 这样别人就无法在里面添加新的接口函数了

@FunctionalInterface
interface MyLambdaInterface{
    void doSomeShit(String s);
}

这样,我们就得到了一个完整的 Lambda 表达式声明:

MyLambdaInterface aBlockOfCode = (s) -> System.out.println(s);

Lambda表达式有什么作用

使代码变的简洁

Java7 实现过程:

public static void enact(MyLambdaInterface myLambda, String s){
    myLambda.doSomeShit(s);
}

public class MyInterfaceImpl implements MyLambdaInterface{

    @Override
    public void doSomeShit(String s){
        System.out.println(s);
    }
}

MyLambdaInterface anInterfaceImpl = new MyInterfaceImpl();

enact(anInterfaceImpl, "Hello World");

Java8 实现过程:

enact(s -> System.out.println(s), "Hello World");

有些情况下,这个接口实现只需要用到一次。传统的 Java 7 必须要求你定义一个“污染环境”的接口实现 MyInterfaceImpl,而相较之下 Java 8 的 Lambda, 就显得干净很多。

另外

在 kotlin 中可以像 Java8 中那样使用 Lambda.

button.setOnclickListener{  /*点击后处理*/ }

Lambda和集合

需求:找出列表中年龄最大的那个人

data class Person(val name: String, val age: Int)

fun findTheOldest(people: List<Person>){
    var maxAge = 0;
    var theOldest: Person? = null
    for (person in people){
        if(person.age > maxAge){
            maxAge = person.age
            theOldest = person
        }
    }
    println(theOldest)
}

Lambda 实现

data class Person(val name: String, val age: Int)

people.maxBy({ it.age })

maxBy 函数可以在任何集合上调用,而且只有一个实参(一个函数)指定比较那个值来找到最大元素。

花括号中的 {it.age} 就是实现了这个逻辑的 Lambda.

Kotlin中Lambda的语法

{x: Int, y: Int -> x + y}

Kotlin 的 lambda 表达式始终使用花括号包围,箭头将实参和函数体隔开。

val sum = {x: Int, y: Int -> x+y}
println(sum(1, 2))

//上下对比

interface LambdaSumInterface{
    int sum(int x, int y);
}
LambdaSumInterface sumInterface = (x, y) -> x+y;
System.out.println(suminterface.sum(1, 2))

上面我们有一个需求:找出列表中年龄最大的那个人,简化过程如下

people.maxBy({p: Person -> p.age})
|
//kotlin约定:lambda表达式调用的最后一个实参可以放到括号外面
|
people.maxBy(){p: Person -> p.age}
|
//kotlin约定:函数只有唯一实参时可以去掉括号
|
people.maxBy{p: Person -> p.age}
|
//参数类型可以省略
|
people.maxBy{p -> p.age}
|
//只有一个参数,默认参数名称it代替命名参数
|
people.maxBy{it.age}

Lambda结合forEach和函数接口包

需求:遍历集合,找出名字中 LastName 以 “Z” 开头的 FirstName

public class MainActivity extends AppCompatActivity {

    class Person{
        private String firstName;
        private String lastName;
        private int age;
        public Person(String fname, String lname, int age){
            firstName = fname;
            lastName = lname;
            this.age = age;
        }

        public String getFirstName() {
            return firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public int getAge() {
            return age;
        }
    }

    @SuppressLint("NewApi")
    public static void checkAndExecute(List<Person> personList,
                                       Predicate<Person> predicate,
                                       Consumer<Person> consumer){
            for(Person p : personList){
                if(predicate.test(p)){
                    consumer.accept(p);
                }
            }
    }

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

        List<Person> guiltyPersons = Arrays.asList(
                new Person("YiXing", "Zhao", 25),
                new Person("XiaoHong", "Li", 23),
                new Person("DaLi", "Wang", 22)
        );
        checkAndExecute(guiltyPersons,
                p -> p.getLastName().startsWith("Z"),
                d -> System.out.println(d.getFirstName()));
    }
}

在 Java8 中有一个函数式接口包,里面定义了大量可能用到的函数式接口 java.util.function

使用 iterable 自带的 forEach 来替代。

@SuppressLint("NewApi")
public static void checkAndExecute(List<Person> personList,
                                    Predicate<Person> predicate,
                                    Consumer<Person> consumer){
        personList.forEach(p -> {if(predicate.test(p)) consumer.accept(p);});
}

利用 stream() 替代静态函数

List<Person> guiltyPersons = Arrays.asList(
        new Person("YiXing", "Zhao", 25),
        new Person("XiaoHong", "Li", 23),
        new Person("DaLi", "Wang", 22)
);

guiltyPersons.stream()
        .filter(p -> p.getLastName().startsWith("Z"))
        .forEach(p -> System.out.println(p.getFirstName()));

kotlin作用域中访问变量

和 Java 中不同的是,在 Kotlin 中不仅仅可以访问非 final 变量,也可以修改这些变量。

fun testFunction(messages: Collections<String>, prefix: String){
    var clientErrNum = 0
    var serverErrNum = 0
    message.forEach{
        if(it.startWith("4")){
            clientErrNum++
        }else if(it.startWith("5")){
            serverErrNum++
        }
    }
}

成员引用

val getAge = Person::age

这种表达式称为成员引用,可以引用一个属性或者一个方法,注意方法不能带括号。

等价于

val getAge = {p: Person -> p.age}

我们知道 Lambda 可以将代码块作为参数传递给函数,如果要传递一个被定义的函数可以使用引用。

还可以引用顶层函数

fun salute() = println("Salute!")

>>> run(::salute)

也可以用构造方法引用

data class Person(val name: String, val age: Int)

val createPerson = ::Person
val p = createPerson("Alice", 26)
println(p)

还可以用同样的方式引用扩展函数

fun Person.isAdult() = age >= 21
val predicate = Person::isAdult