Lambda 表达式

Lambda 管中窥豹

能够把 Lambda 表达式理解为简洁地可传递的匿名函数的一种方式:没有名称,有参数列表、函数主体、返回类型和可能的异常。理论上讲,匿名函数作不到的事,Lambda 也作不了。后者只是让前者可读性更强,写得更轻松。java

回顾上一章最后的那个 Lambda 表达式express

(Apple apple1) -> "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()

咱们能够发现 Lambda 能够划分为三个部分:app

  • 参数 (Apple apple1)
  • 箭头 ->
  • 主体 "green".equalsIgnoreCase(apple1.getColor()) && 2 == apple1.getWeight()

Lambda 的基本语法是这样的:ide

  • (parameters) -> expression
  • (parameters) -> { statements; }

在哪里以及如何使用 Lambda

咱们能够在函数式接口中使用 Lambda。函数

函数式接口就是只定义一个抽象方法的接口。学习

例如上一章的苹果谓词接口测试

public interface ApplePredicate {
    boolean test(Apple apple);
}

只有一个抽象方法的接口能让 Lambda 把整个表达式做为函数式接口的实例。匿名内部类同样能够完成一样的事,只不过很笨拙。设计

JDK 中的 Runnable 接口code

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

两种实现方式对象

Runnable runnable1 = () -> System.out.println("Hello Word!");
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello Word!");
            }
        };

这两钟实现结果是同样的。函数式接口的返回类型基本上就是 Lambda 的返回类型。

函数式接口通常均可以被 @FunctionalInterface 注解,这个注解就如同它的名字同样表明这个接口是函数式接口。而且它和 @Override 同样只是让编译期判断是否正确,运行期无关,并非必需的。若是在一个接口中定义了多个抽象方法,并加上这个注解再编译的话,编译器便会给你的报错,由于这样的接口已经不符合函数式接口的定义了。

使用函数式接口

JDK 自己也自带了几个函数式接口,好比 Predicate、Consumer、Function。咱们可使用一下练练手。

Predicate

这个和上一章最后咱们本身写的那个函数式接口,二者彻底同样,都是用来作条件测试的谓词接口。

经过谓词过滤泛型集合

public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (predicate.test(t)) {
                result.add(t);
            }
        }
        return result;
    }

过滤掉字符串集合中空字符串

List<String> stringList = new ArrayList<>();
        stringList.add("hello");
        stringList.add("");
        stringList.add("lambda");

        // Lambda 实现
        Predicate<String> stringPredicate = (String s) -> !s.isEmpty();
        // 匿名实现
        Predicate<String> stringPredicate1 = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return !s.isEmpty();
            }
        };
        List<String> result = filter(stringList, stringPredicate);

这样空字符串就会被过滤掉,只剩下 hellolambda

Consumer

这个函数式接口是用来接收一个对象并对其进行处理。

遍历一个泛型集合

public static <T> void forEach(List<T> list, Consumer<T> consumer) {
        for (T t : list) {
            consumer.accept(t);
        }
    }

取出字符串集合里面的对象并打印输出

// Lambda 实现
        Consumer<String> consumer = (String s) -> System.out.println(s);
        // 匿名实现
        Consumer<String> consumer1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        forEach(stringList, consumer);

这样就会打印输出 hello、 、lambda

Function

这个函数式接口是用来接收一个对象并映射到另外一个对象。

接收一个集合对象并返回另外一个集合对象

public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
        List<R> result = new ArrayList<>();
        for (T t : list) {
            result.add(function.apply(t));
        }
        return result;
    }

接收一个字符串集合并映射成其长度的整型集合后返回

// Lambda 实现
        Function<String, Integer> function = (String s) -> s.length();
        // 匿名实现
        Function<String, Integer> function1 = new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        };
        List<Integer> integerList = map(stringList, function);

这样 hello、 、lambda 就分别对应其长度 506

方法引用

方法引用能够看做对特定 Lambda 的一种快捷写法,本质上依然是 Lambda。它的基本思想是,若是一个 Lambda 表明的仅仅是 直接调用 这个方法而不是 描述如何去调用 这个方法,那最好仍是用名称来调用它。

例如在上一章的苹果实例中咱们须要用谓词判断是否成熟

// 普通 Lambda 写法
        Predicate<Apple> applePredicate1 = (Apple apple) -> apple.isAging();
        // 方法引用写法
        Predicate<Apple> applePredicate2 = Apple::isAging;

咱们能够把方法引用看做对 仅仅涉及单一方法 的 Lambda 的语法糖。

构造函数引用

对于一个现有的构造函数,咱们能够利用它的名称和 new 来建立它的一个引用:ClassName::new

// 普通 Lambda 建立对象
        // Supplier<Apple> appleSupplier = () -> new Apple();
        // 构造函数引用建立无参对象
        Supplier<Apple> appleSupplier = Apple::new;
        // 获取实例
        Apple apple1 = appleSupplier.get();
        
        // 构造函数引用建立有一个参数对象
        Function<String, Apple> appleFunction = Apple::new;
        // 获取实例
        Apple apple2 = appleFunction.apply("red");

        // 构造函数引用建立有两个参数对象
        BiFunction<String, Integer, Apple> appleBiFunction = Apple::new;
        // 获取实例
        Apple apple3 = appleBiFunction.apply("red", 1);

那么当参数有不少的时候怎么办呢?咱们能够自定义一个函数式接口进行处理。

三个参数的构造函数引用接口

public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

调用也是相似的

// 构造函数引用建立有三个参数对象
        TriFunction<String, Integer, Boolean, Apple> appleTriFunction = Apple::new;
        // 获取实例
        Apple apple4 = appleTriFunction.apply("red", 1, true);

Lambda 和方法引用实战

传递代码

咱们若是要对一个苹果集合按照重量从小到大排序,首先确定要进行判断大小,而后对其进行排序。按照咱们已经通过一章多的学习,应该能很轻松地构建一个解决方案。

一、建立比较器

public class AppleComparator implements Comparator<Apple> {
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
}

二、调用 List 已经实现好的排序方法

public class Main {
    public static void main(String[] args) {
        List<Apple> appleList = new ArrayList<>();

        // 重量为2的成熟红苹果 No.1
        Apple apple = new Apple();
        apple.setColor("red");
        apple.setWeight(2);
        apple.setAging(true);
        appleList.add(apple);

        // 重量为1的未成熟绿苹果 No.2
        apple = new Apple();
        apple.setColor("green");
        apple.setWeight(1);
        apple.setAging(false);
        // 如今 appleList 的顺序是放入的顺序 No.一、No.2
        appleList.add(apple);

        // 依照重量排序后 appleList 的顺序会变成 No.二、No.1
        appleList.sort(new AppleComparator());
    }
}

这只是简单的一个经过传递比较器来进行排序。下面咱们会用匿名类来实现上一章学习的 应对不断变化的需求

使用匿名类

到这一步其实已经算得上符合正常软件工程设计了,能够舍去 AppleComparator 这样的实现方式。

appleList.sort(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight().compareTo(o2.getWeight());
            }
        });

使用 Lambda 表达式

紧接着咱们能够更加高效地用 Lambda 实现。

appleList.sort((Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight()));

咱们还能够进一步简化代码。Java 编译器会从目标类型自动推断出适合 Lambda 的返回类型。所以能够省略对传入参数的类型定义。

// appleList 的类型定义是 List<Apple>,传递进 sort() 的 Comparator<T> 会自动定义泛型为 Apple,因此 Lambda 也能够自动推断为 Comparator 的 compare() 传入的类型。
        appleList.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));

使用方法引用

使用方法引用还能够更加让人通俗易懂。

Comparator 有个静态方法 comparing 能够接收 Function 函数式接口,用做比较依据。

// 传入的 Function 本质是将苹果(Apple)映射成了苹果的重量(Integer)
        // Function<Apple, Integer> function = (Apple o1) -> o1.getWeight();
        // 方法引用后
        Function<Apple, Integer> function = Apple::getWeight;
        // 传入到 comparing 静态方法
        Comparator.comparing(function);

因此最后能够简化到极致

// 表明按照苹果的重量进行比较后排序
        appleList.sort(Comparator.comparing(Apple::getWeight));

复合 Lambda 表达式的有用方法

Java 8提供了容许进行复合的方法。好比咱们可让两个谓词之间作 or 操做,组成一个更增强大的谓词。

比较器复合

若是想要从大到小排序苹果的重量(默认的 sort() 是从小到大排序)

appleList.sort(Comparator.comparing(Apple::getWeight).reversed());

可是若是两个的苹果重量同样,咱们须要再根据颜色或者是否成熟来排序呢?

咱们可使用 thenComparing 方法

appleList.sort(Comparator
                // 从大到小排列苹果的重量
                .comparing(Apple::getWeight).reversed()
                // 而后按照颜色字母顺序
                .thenComparing(Apple::getColor)
                // 而后按照是否成熟
                .thenComparing(Apple::getAging));

这样就能够建立一个比较器链了。

谓词复合

谓词接口包括三个方法:negate、and 和 or,咱们能够以此建立复杂的谓词。

选出苹果不是红的

Predicate<Apple> isRed = (Apple o1) -> "red".equalsIgnoreCase(o1.getColor());
        Predicate<Apple> noRed = isRed.negate();

选出苹果不是红的且成熟的

Predicate<Apple> noRedAndIsAging = noRed.and(Apple::getAging);

选出苹果不是红的且成熟的或重量大于1

Predicate<Apple> noRedAndIsAgingOrHeavey = noRedAndIsAging.or((Apple o1) -> o1.getWeight() > 1);

总结起来,除了 isRed 谓词要在第一步写,其他的地方均可以一句话写完

Predicate<Apple> predicate = noRed
                .and(Apple::getAging)
                .or((Apple o1) -> o1.getWeight() > 1);

这样从简单的 Lambda 出发,能够构建更加复杂的表达式,但读起来会更加轻松。注意,and 和 or 的是按照链中的位置执行。

函数复合

Function 接口包括两个方法:andThen 和 compose,它们都会返回一个 Function 的实例。

咱们能够用 Function 定义三个函数 f(x)、g(x)和 g(x),先看看 andThen()

// f(x) = x + 1
        Function<Integer, Integer> f = x -> x + 1;
        // g(x) = x * 2
        Function<Integer, Integer> g = x -> x * 2;
        // h(x) = f(g(x))
        Function<Integer, Integer> h = f.andThen(g);

传入 x 进行运算

// 结果为4
        int result = h.apply(1);

compose()

// h(x) = g(f(x))
        h = f.compose(g);
        // 结果为3
        result = h.apply(1);

第三章的东西有点多,须要反复消化理解。

Java 8 实战 第三章 Lambda 表达式 读书笔记

欢迎加入咖啡馆的春天(338147322)。

相关文章
相关标签/搜索