文章目录
1、认识Lambda表达式
Lambda 表达式可以理解为一种匿名函数,它没有名称,但它有参数列表、方法主体、返回类型等
1.1、Lambda表达式语法
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
lambda 表达式的语法由参数列表、箭头符号 -> 和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:
表达式: 表达式会被执行然后返回执行结果。
语句块: 语句块中的语句会被依次执行,就像方法中的语句一样
- return 语句会把控制权交给匿名方法的调用者
- break 和 continue 只能在循环中使用
- 如果函数体有返回值,那么函数体内部的每一条路径都必须返回值
表达式函数体 适合小型 lambda 表达式,它消除了 return 关键字,使得语法更加简洁。
以下是lambda表达式的重要特征:
可选类型声明: 不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号: 一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号: 如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字: 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
1.2、Lambda 表达式示例
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值
(String s) -> System.out.print(s)
2、函数式接口(Functional interfaces)
在哪里可以使用lambda表达式呢?答案是:在函数式接口上使用lambda表达式。是否还记得在上一节 Java 8(一):行为参数化中的示例吗?其中 Predicate 接口就是一个函数式接口,因为 Predicate 仅仅定义了一个抽象方法:
interface Predicate<T>{
boolean test(T t);
}
函数式接口: 就是只定义了一个抽象方法的接口。例如,Java API 中的Comparator,Runnable,Callable 等。
Java SE 7 中已经存在的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.beans.PropertyChangeListener
Java SE 8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口:
- Predicate——接收 T 并返回 boolean
- Consumer——接收 T,不返回值
- Function<T, R>——接收 T,返回 R
- Supplier——提供 T 对象(例如工厂),不接收值
- UnaryOperator——接收 T 对象,返回 T
- BinaryOperator——接收两个 T,返回 T
2.1、Predicate
java.util.function.Predicate接口定义了一个名叫test的抽象方法,它接受一个泛型T对象,并返回一个boolean
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
使用示例如下:
public static void main(String[] args) {
//非空字符串判断,lambda表达式
Predicate<String> nonEmptyStringPredicate = s -> !s.isEmpty();
//过滤非空字符串,lambda表达式作为参数传递
List<String> nonEmptyStringLis = filter(Arrays.asList("hello", "world", ""), nonEmptyStringPredicate);
System.out.println(nonEmptyStringLis);
}
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> results = new ArrayList<>();
for (T s : list) {
if (predicate.test(s)) {
results.add(s);
}
}
return results;
}
2.2、Consumer
java.util.function.Consumer定义了一个叫accept的抽象方法,它接受一个泛型T的对象,并没有返回(void)
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
使用示例:
/**
* java.util.function.Consumer<T>定义了一个叫accept的抽象方法,它接受一个泛型T的对象,并没有返回(void)
*/
public class ConsumerDemo {
public static void main(String[] args) {
//遍历对象集合,并打印每一个元素
forEach(Arrays.asList(2, 4, 9), System.out::println);
}
/**
* 根据 consumer 遍历对象
*/
public static <T> void forEach(List<T> list, Consumer<T> consumer) {
for (T t : list) {
consumer.accept(t);
}
}
}
2.3、Function
java.util.function.Function<T, R>定义了一个叫apply的抽象方法,它接受一个泛型T的对象,并返回一个泛型R对象
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
使用示例:
/**
* java.util.function.Function<T, R>定义了一个叫apply的抽象方法,它接受一个泛型T的对象,并返回一个泛型R对象
*/
public class FunctionDemo {
public static void main(String[] args) {
//计算集合中的每一个元素的长度
List<Integer> strLength = map(Arrays.asList("hello,world", "lambda"), s -> s.length());
System.out.println(strLength);
}
/**
* 遍历 list 并转换每一个元素,返回转换后的集合
*
* @param list 被操作对象集合
* @param 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;
}
}
3、类型检查
Lambda 的类型是从Lambda 的上下文推断出来的。上下文中Lambda表达式需要的类型称为***目标类型***。
4、类型推断
还可以进一步简化代码。java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来获取。这样就可以在Lambda语法中省去参数类型的声明。
//需要类型推断,仅有一个参数需要类型推断时,参数两边的括号可以省略
List<Apple> greenApple = appleList.stream().filter(a -> a.getColor().equals("green")).collect(Collectors.toList());
//需要类型推断
Comparator<Apple> apple1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
//不需要类型推断
Comparator<Apple> apple2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
5、变量作用域
Lamdba 可以没有限制的使用实例变量和静态变量。但是局部变量必须显示声明为final或者事实上是final(即一个变量虽然没被final修饰,但是他在后面也没被修改)。
下面的代码将会编译错误,因为portNumber变量被赋值了两次
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
//编译错误
portNumber = 31337;
5.1、Lambda使用局部变量的限制
Lambda表达式对值封闭,不是对变量封闭。Lambda表达式引用的局部变量必须是final的,只能有一次赋值。
5.2、为什么对局部变量限制
实例变量是储存在堆中,而局部变量是储存在栈上。如果Lambda可以直接访问局部变量,而Lambda是在一个线程中使用的,则使用Lambda的线程可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问他的副本而不是访问原始变量。
6、方法引用
方法引用就是Lambda的快捷写法。目标引用放在分隔符::前面,方法的名称放在后面。例如,Apple::getWeight 就是引用了Apple类中一个的getWeight方法。这里时不需要括号的,因为并没有实际调用该方法。
使用方法引用之前:
Comparator<Apple> apple2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
使用方法引用之后:
Comparator<Apple> apple3 = Comparator.comparing(Apple::getWeight);
Lambda表达式及其等价方法引用:
Lambda表达式 | 等效的方法引用 |
(Apple a) -> a.getWeight() | Apple::getWeight |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s) | System.out::println |
6.1、如何构建方法引用
方法引用主要有三类:
(1)指向静态方法的方法引用
例如:Integer 的 parseInt 方法,等价于 Integer::parseInt
(2)指向任意类型实例方法的引用
例如:String 的 length 方法,等价于 String::length
(3)指向现有对象的实例方法的引用
例如:局部变量 apple 用来存放 Apple 类型的对象,它的实例方法是 getWeight,那么方法引用就可以这样写:apple::getWeight
6.2、Lambda构造函数引用
对于一个现有构造函数,可以利用它的名称和关键字new来创建它的一个引用:ClassName::new。它的功能与静态方法的引用类似。
Lambda表达式 | 等价构造函数引用Lambda表达式 |
Supplier<Apple> c1 = Apple::new; Apple a1 = c1.get(); | Supplier<Apple> c1 = () -> new Apple(); Apple a1 = c1.get(); |
Function<Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply(110); | Function<Integer, Apple> c2 = (weight) -> new Apple(weight); Apple a2 = c2.apply(110); |
BiFunction<String, Integer, Apple> c2 = Apple::new; Apple a2 = c2.apply("green", 110); | BiFunction<String, Integer, Apple> c2 = (color, weight) -> new Apple(color, weight); Apple a2 = c2.apply("green", 110); |
7、复合Lambda表达式
把多个简单的Lambda表达式复合成复杂的表达式,可以让两个谓词or操作,组成一个更大的谓词,可以让一个函数的结果成为另一个函数的输入参数。
复合类型 | 方法 | 说明 | 举例 |
比较器复合 | reversed() | 逆序 | //按重量递减排序 inventory.sort(comparing(Apple::getWeight)) .reversed(); |
thenComparing | 比较器链(接受一个函数作为参数,如果两个对象用第一个Comparator比较之后是一样的,就提供第二个Comparator | //两个苹果一样重时按国家排序 inventory.sort(comparing(Apple::getWeight)) .reversed(). thenComparing(Apple::getCountry); | |
谓词复合 (and和or方法的优先级是按照在表达式链中的位置,从左向右确定的) | negate | 非 | //产生现有对象redApple的非 Predicate<Apple> notRedApple = redApple.negate(); |
and | 将两个Lambda用and组合起来 | //一个苹果既是红色又比较重(链接两个谓词来生成一个Predicate对象) Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150); | |
or | 要么 | //要么重(150g以上)的红苹果,要么是绿苹果 Predicate<Apple> RedAndHeavyApple = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor())); | |
函数复合 | andThen | 先对输入应用一个给定函数,再对输出应用另一个函数 | //等同于数学上的g(f(x)),返回4 Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = f.andThen(g); int result = h.apply(1); |
compose | 先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果 | //等同于数学上的f(g(x)),返回3 Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h = f.compose(g); int result = h.apply(1); |