能够把 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
咱们能够在函数式接口中使用 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。咱们可使用一下练练手。
这个和上一章最后咱们本身写的那个函数式接口,二者彻底同样,都是用来作条件测试的谓词接口。
经过谓词过滤泛型集合
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);
这样空字符串就会被过滤掉,只剩下 hello
、lambda
。
这个函数式接口是用来接收一个对象并对其进行处理。
遍历一个泛型集合
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
。
这个函数式接口是用来接收一个对象并映射到另外一个对象。
接收一个集合对象并返回另外一个集合对象
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
就分别对应其长度 5
、0
、6
。
方法引用能够看做对特定 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);
咱们若是要对一个苹果集合按照重量从小到大排序,首先确定要进行判断大小,而后对其进行排序。按照咱们已经通过一章多的学习,应该能很轻松地构建一个解决方案。
一、建立比较器
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 实现。
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));
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)。