Java 核心技术卷 I 学习记录八
- 六、接口、lambda表达式与内部类
- 3、lambada表达式
- 1、为什么引入lambda表达式
- 2、lambda表达式的语法
- 3、函数式接口
- 4、方法引用
- 5、构造器引用
- 6、变量作用域
- 7、处理lambda表达式
- 8、再谈Comparator
六、接口、lambda表达式与内部类
3、lambada表达式
使用 lambda 表达式采用一种简洁的语法定义代码块。
1、为什么引入lambda表达式
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
2、lambda表达式的语法
java">// 检查一个字符串是否比另一个字符串短
first.length() -second.length();// Java是一种强类型语言,指定变量类型
(String first, String second) -> first.length() -second.length();
lambda表达式就是一个代码块,以及必须传入代码的变量规范。
Java中的一种lambda表达式形式:参数,箭头(->)以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在 {} 中,并包含显式的return语句。
java">// lambda例子:检查一个字符串是否比另一个字符串短
(String first, String second) -> {if (first.length() < second.length()) return -1;else if (first.length() > second.lenght()) return 1;else return 0;
}
即使lambda表达式没有参数,仍然要提供空括号,就像无参数方法一样:
java">() -> { for (int i=100;i>=0;i-- ) System.out.println(i);
}
如果可以推导出一个lambda表达式的参数类型,就可以忽略类型:
java">Comparator<String> comp= (first, second) // Same as (String first, String second) -> first.length() - second.length();
在这里,编译器可以推导出first和second必然是字符串,因为这个lambda表达式将赋给一个字符串比较器。
如果方法只有一个参数,而且这个参数的类型可以推导出来,甚至还可以省略小括号:
java">ActionListener listener = event ->System.out.println("The time is " + new Date()); // Instead of (event) -> . . . or (ActionEvent event) -> . . .
无需指定lambda表达式的返回类型。lambda表达式的返回类型会根据上下文推导出。
3、函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口(functional interface)。
在Java中,对lambda表达式所能做的也只是能转换为函数式接口。在其他支持函数字面量的程序设计语言中,可以声明函数类型(如(String, String) -> int)、声明这些类型的变量,还可以使用变量保存函数表达式。不过,Java设计者还是决定保持我们熟悉的接口概念,没有为Java语言增加函数类型。
4、方法引用
如果已经有现成的方法已经可以完成你想要传递到其他代码的某个动作。
java">// 假如希望只要出现一个定时器事件就打印这个事件对象
Timer t = new Timer(1000, event -> System.out.println(event));// 如果直接把println方法传递到Timer构造器
Timer t = new Timer(1000, System.out::println);
表达式System.out::println是一个方法引用(method reference),它等价于lambda表达式x -> System.out.println(x);。
要用::操作符分隔方法名与对象或类名。主要有3种情况:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
在前2种情况中,方法引用等价于提供方法参数的lambda表达式。对于第3种情况,第1个参数会成为方法的目标。
可以在方法引用中使用this参数。例如,this::equals等同于x -> this.equals(x)。使用super也是合法的。super::instanceMethod,使用this作为目标,会调用给定方法的超类版本。
5、构造器引用
构造器引用与方法引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用。具体引用哪一个构造器取决于上下文。
可以用数组类型建立构造器引用。例如,int[]::new是一个构造器引用,它有一个参数:即数组的长度。这等价于lambda表达式 x -> new int[x]。
Java有一个限制,无法构造泛型类型T的数组。数组构造器引用对于克服这个限制很有用。表达式new T[n]会产生错误,因为这会改为new Object[n]。对于开发类库的人来说,这是一个问题。
6、变量作用域
可能希望能够在lambda表达式中访问外围方法或类中的变量。
lambda表达式有3个部分:
- 1、一个代码块;
- 2、参数;
- 3、自由变量的值,这是指非参数而且不在代码中定义的变量。
关于代码块以及自由变量值有一个术语:闭包(closure)。如果有人吹嘘他们的语言有闭包,现在你也可以自信地说Java也有闭包。在Java中,lambda表达式就是闭包。
7、处理lambda表达式
使用lambda表达式的重点是延迟执行(deferred execution)毕竟,如果想耍立即执行代码,完全可以直接执行,而无需把它包装在一个lambda表达式中。之所以希望以后再执行代码,这有很多原因,如:
- 在一个单独的线程中运行代码;
- 多次运行代码;
- 在算法的适当位置运行代码(例如,排序中的比较操作);
- 发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等);
- 只在必要时才运行代码。
Java API中提供的最重要的函数式接口。
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 无 | void | run | 作为无参数或返回值的动作运行 | |
Supplier | 无 | T | get | 提供一个T类型的值 | |
Consumer | T | void | accept | 处理一个T类型的值 | addThen |
BiConsumer<T, U> | T, U | void | accept | 处理T和U类型的值 | addThen |
Function<T, R> | T | R | apply | 有一个T类型参数的函数 | compose, addThen, indetify |
BiFunction<T, U, R> | T, U | R | apply | 有T和U类型参数的函数 | addThen |
UnaryOperator | T | T | apply | 类型T上的一元操作符 | compose, addThen, identify |
BinaryOperator | T, T | T | apply | 类型T上的二元操作符 | andThen, maxBy, minBy |
Predicate | T | boolean | test | 布尔值函数 | and, or, negate, isEqual |
BiPredicate<T, U> | T, U | boolean | test | 有两个参数的布尔值函数 | and, or, negate |
基本类型int、long和double的34个可能的规范。最好使用这些特殊化规范来减少自动装箱。
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 |
---|---|---|---|
BooleanSupplier | none | boolean | getAsBoolean |
PSupplier | none | p | getAsP |
PConsumer | p | void | accept |
ObjPConsumer | T, p | void | accept |
PFunction | p | T | apply |
PToQFunction | p | q | applyAsQ |
ToPFunction | T | p | applyAsP |
ToPBiFunction<T, U> | T, U | p | applyAsP |
PUnaryOperator | p | p | applyAsP |
PBinaryOperator | p, p | p | applyAsP |
PPredicate | p | boolean | test |
8、再谈Comparator
Comparator接口包含很多方便的静态方法来创建比较器。这些方法可以用于lambda表达式或方法引用。
静态comparing方法取一个“键提取器”函数,它将类型T映射为一个可比较的类型(如 String)。对要比较的对象应用这个函数,然后对返回的键完成比较。