本章内容:
Lambda管中窥豹
在哪里以及如何使用Lambda表达式
函数式接口,类型推断
方法引用
Lambda复合
2.1 Lambda管中窥豹
可以把Lambda表达式理解为简洁的表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、方法主体、返回类型等。
匿名:
它不像普通方法那样有一个明确的名称,写得少做得多
函数:
Lambda函数不像方法那样属于某个特定的类,但它和方法一样有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表
传递:
Lambda表达式可以作为参数传递给方法
简洁:
不需要像匿名类那样写很多的代码模板
参数列表:这里采用了Comparator中compare方法的参数,两个Apple
箭头:箭头 -> 将参数列表与Lambda主体分隔开
Lambda主体:比较两个Apple的重量,表达式就是Lambda的返回值
测试:
下面哪个不是有效的Lambda表达式:
1. () -> {}
2. () -> "Hello"
3. () -> {return "Hello";}
4. (Integer i) -> return "Hello" + i;
5. (String s) -> {"Hello";}
答案:
4和5不是有效的Lambda表达式,4:(Integer i) -> {return "Hello" + i;};5:(String s) -> {return "Hello";}
Lambda示例:
2.2 在哪里以及如何使用Lambda
2.2.1 函数式接口:只定义了一个抽象方法的接口
例如之前定义的Predicate<T>就是一个函数式接口,Comparator和Runnable也是函数式接口。
那使用函数式接口能干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。
public class LambdaDemo { public static void main(String[] args) { Runnable r1 = () -> System.out.println("Hello World1"); Runnable r2 = new Runnable() { @Override public void run() { System.out.println("Hello World2"); } }; process(r1); process(r2); process(() -> System.out.println("Hello World3")); } public static void process(Runnable r) { r.run(); } }
2.2.2 函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫做函数描述符。
2.3 使用函数式接口
2.3.1 Predicate
java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受一个泛型T对象,并返回一个boolean
public static <T> List<T> filter(List<T> list, Predicate<T> p){ List<T> result = new ArrayList<T>(); for (T t : list) { if(p.test(t)) { result.add(t); } } return result; } // 测试 // 使用Predicate System.out.println("使用Predicate:"); List<String> list = new ArrayList<String>(); list.add(null); list.add("Hello World"); list.add(""); Predicate<String> p = (String s) -> s != null && !s.isEmpty(); System.out.println(filter(list , p));
2.3.2 Consumer
java.util.function.Consumer<T>定义了一个叫accept的抽象方法,它接受一个泛型T的对象,并没有返回(void)
public static <T> void forEach(List<T> list, Consumer<T> c) { for(T t : list) { c.accept(t); } } // 测试 // 使用Consumer System.out.println("// 使用Consumer:"); forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));
2.3.3 Function
java.util.function.Function<T, R>定义了一个叫apply的抽象方法,它接受一个泛型T的对象,并返回一个泛型R对象
public static <T, R> List<R> map(List<T> list, Function<T, R> f){ List<R> result = new ArrayList<R>(); for (T t : list) { result.add(f.apply(t)); } return result; } // 测试 // 使用Function System.out.println("// 使用Function:"); List<Integer> l = map(Arrays.asList("lambda", "in", "action"), (String s) -> s.length()); System.out.println(l);
2.4 类型检查
Lambda的类型是从使用Lambda的上下文推断出来的,下面描述了Lambda表达式的类型检查过程
2.5 类型推断
你还可以进一步简化你的代码。java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来获取。这样就可以在Lambda语法中省去参数类型的声明。
List<Apple> heavirThan150 = filter(inventory, a -> a.getWeight() > 150);
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
参数没有显式类型。有时候显式写出参数类型更易读,这个取决于程序员的选择。
2.6 方法的引用
方法的引用让你可以重复使用现有的方法定义,并像Lambda一样传递他们。
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(Comparator.comparing((Apple a) -> a.getWeight()));
inventory.sort(Comparator.comparing(Apple::getWeight));//你的第一个方法的引用
2.6.1 管中窥豹
当你需要使用方法的引用时,目标引用放在分隔符 :: 前,方法的名称放在后面。例如 Apple::getWeight 就是引用了Apple类中的getWeight 方法。
Lambda 及其等效方法引用的例子:
Lambda | 等效的方法引用 |
(Apple a) -> a.getWeight() | Apple::getWeight |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s); | System.out::println |
BiFunction<String, Integer, String> bf = (String str, Integer i) -> str.substring(i);
bf = String::substring; String methadQuote = methadQuote("Hello", 2, bf); System.out.println("方法的引用:"); System.out.println(methadQuote); Consumer<String> consumer = (String str) -> System.out.println(str); consumer = System.out::println; methadQuote1("Hello World", consumer);
// 方法的引用
public static String methadQuote(String str, Integer i, BiFunction<String, Integer, String> bf) {
return bf.apply(str, i);
}
public static void methadQuote1(String str, Consumer<String> c) {
c.accept(str);
}
如何构建方法的引用:
方法引用主要有三类:
1. 指向静态方法的方法引用(例如Integer的parseInt方法,写成 Integer::parseInt)
2. 指向任意类型实例方法的方法引用(例如String的length方法,写成String::length)
3. 指向现有对象的实例方法的方法引用(例如Apple对象a有getWeight方法,写成Apple::getWeight)
解释一下第二种:类似于String::length这种引用思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。(这个对象必须放在泛型参数的第一位--不确定)
2.6.2 构造函数引用
对于一个现有的构造函数,你可以利用它的名称和关键字new来创建它的一个引用:ClassName::new。它适合Supplier的签名 () -> T
/**
* 构造函数引用
*/
// 无参
Supplier<Apple> s = Apple::new;
// 一个参数
Function<String, Apple> f = Apple::new;
// 两个参数
BiFunction<Integer, String, Apple> biFunction = Apple::new;
如果想要为更多参数的构造函数使用方法引用怎么办呢?
答案:
自己创建这样的函数式接口:
private String name; private Integer age; private String address; public interface TriFunction<T, U, V, R> { R apple(T t, U u, V v); } // 自定义的函数式接口 TriFunction<String, Integer, String, Student> tri = Student::new;
2.7 复合Lambda表达式的有用方法
即将讲到的方法为默认方法,后面章节会细讲
2.7.1 比较器复合
Comparator<Apple> com = Comparator.comparing(Apple::getWeight);
1. 逆序
inventory.sort(Comparator.comparing(Apple::getWeight).reversed());
2. 比较器链
inventory.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));
2.7.2 谓词复合
谓词接口包含三个方法:negate,and,or
Predicate<Apple> redApple = (Apple a) -> "red".equals(a.getColor()); Predicate<Apple> notRedApple = redApple.negate();// 产生现有对象redApple的非 Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150); Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor()));
注意:and和or在表达式中按照从左到右确定优先级。因此,a.or(b).and(c) 为 (a || b) && c
2.7.3 函数复合:
你还可以把Function接口所代表的的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,他们都返回一个Function的实例。
Function<Integer, Integer> g = x -> x + 1; Function<Integer, Integer> h = y -> y * 2; Function<Integer, Integer> j = g.andThen(h);// 相当于数学中的 h(g(x)) Integer apply = j.apply(1); System.out.println("andThen函数复合:" + apply);// 4 j = g.compose(h);// 相当于数学中的 g(h(x)) apply = j.apply(1); System.out.println("compose函数复合:" + apply);// 3
备注:
摘自文献:《Java8实战》(中文版)《Java8 in Action》(英文版)
代码(GitHub地址): https://github.com/changlezhong/java8InAction