suninf 's blog

It’s not what you know, it’s how you think

Java8 之Lambda

Catalog

本文整理下Java8引入的最重要的函数式编程的特征 — lambda表达式,能让代码更加简单易懂。

Java中lambda表达式的关键特征:

  • 是一个带参数的代码块,一般用于代码块的延后执行
  • 可以转化为函数接口
  • 能访问作用域内的不可变变量(final variables),这也是闭包的重要特征
  • 支持函数引用和构造函数引用
  • 接口支持实现默认方法和静态方法,多个接口的相同签名的默认方法,需要自己来解决冲突

在Lambda支持之前的代码块方式

在lambda支持之前,需要 显式定义类型 或者 使用匿名类 来支持代码块,下面展示一些例子。

线程任务

class Worker implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++)
           doWork();
    }
}

Worker w = new Worker();
new Thread(w).start();

数组排序

class LengthComparator implements Comparator<String> {
    @Override
    public int compare(String first, String second) {
        return Integer.compare(first.length(), second.length());
    }
}

Arrays.sort(strings, new LengthComparator());

匿名类

strings.sort(new Comparator<String>() {
    @Override
    public int compare(String first, String second) {
        return Integer.compare(first.length(), second.length());
    }
});

Lambda表达式语法

基本语法:(parameters) -> { statements; }

不需要指定返回类型,返回类型,能从上下文自动推断;同时,函数体如果有多个分支(如if),需要每个分支都有return值。

表达式单句形式

如果函数体只有一句,可以直接写该表达式,不用写return和大括号。

(String first, String second)
     -> Integer.compare(first.length(), second.length())

没有参数的形式

() -> { for (int i = 0; i < 1000; i++) doWork(); }

如果参数类型可以推断,可以不提供类型

Comparator<String> comp
     = (first, second) -> Integer.compare(first.length(), second.length());

如果只有一个参数,并且可以推断,则参数的小括号可以省略

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

参数可以添加final控制符和注解

(final String name) -> ...
(@NonNull String name) -> ...

关于闭包特性(closure)

Lambda表达式可以引用表达式作用域外的变量,但是不允许对变量进行修改:

  • 基本类型的话,如int,值不能修改
  • 对象类型的话,引用不能变,但是其实可以调用对象方法,对象内部数据是会变的,比较危险
  • this关键字,指代表达式代码所在的类对象

通过数组对象来改变捕获对象内部状态的例子:

Integer[] value = {0};
Runnable callback = () -> {
    value[0]++;
    System.out.println(value[0]);
};
callback.run(); // 输出 1

函数式接口(Functional Interfaces)

  • 函数式接口是只包含一个抽象方法声明的接口
  • 函数式接口可以用来接收lambda表达式,当然也可以接收实现了接口的类型
  • java.util.function包 定义了一些默认的函数式接口

JDK1.8 之前已有的函数式接口

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

JDK1.8 新定义的函数式接口

java.util.function中定义了几组类型的函数式接口以及针对基本数据类型的子接口。

  • Predicate 传入一个参数,返回一个bool结果, 方法为boolean test(T t)
  • Consumer 传入一个参数,无返回值,纯消费。 方法为void accept(T t)
  • Function 传入一个参数,返回一个结果,方法为R apply(T t)
  • Supplier 无参数传入,返回一个结果,方法为T get()
  • UnaryOperator 一元操作符,继承Function,传入参数的类型和返回类型相同。
  • BinaryOperator 二元操作符,传入的两个参数的类型和返回类型相同,继承BiFunction

自定义函数式接口

函数接口定义好之后,就能用来保存lambda表达式。

public interface PrintVal<T> {
    void printVal(T t);
}

PrintVal<String> pv = s -> System.out.println(s);
pv.printVal("yeah");

方法引用

::操作符

可以通过::操作符引用现有的方法来传递给函数接口,支持3中方式:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod
Arrays.sort(words, (first, second) -> first.compareTo(second));

等价于:
Arrays.sort(words, String::compareTo);

关于this和super

方法引用还支持this和super的捕获

如:this::equals 等价于 x -> this.equals(x)

super::instanceMethod 可以引用超类中的方法:

class Greeter {
    public void greet() {
        System.out.println("Hello, world!");
    }
}

class ConcurrentGreeter extends Greeter {
    public void greet() {
        Thread t = new Thread(super::greet);
        t.start();
    }
}

构造函数引用

使用ClassName::new来引用构造函数

class StringHolder {
    String strVal;

    StringHolder(String strVal) {
        this.strVal = strVal;
    }
}

ArrayList<String> strs = new ArrayList<>();
strs.add("hello");
strs.add("world");

List<StringHolder> shList = strs.stream().map(StringHolder::new).collect(Collectors.toList());

另外,可以使用ClassName[]::new支持数组构造函数引用

关于接口 默认方法和静态方法

java8支持接口中定义default方法和static静态方法。

参考

Comments