Lambda表达式笔记
1. 为什么需要引入lambda?
痛点:传统方式的代码块传递问题。
因为java的面向对象特性,所以需要将代码块封装为一个类的方法然后在需要时进行传递。毕竟java参数当时还不能是代码块。
-
传统方式
新建线程方式。
@Test public void testNewThread(){ new Thread(new A()).start(); //普通内部类 new Thread(new Runnable() { //匿名内部类 public void run() { System.out.println("传统方式的代码块传递!"); } }).start(); } class A implements Runnable{ public void run() { System.out.println("传统方式的代码块传递!"); } }
-
lambda方式
new Thread(() -> System.out.println("lambda表达式方式!")).start();
这样一行就解决了传递一个代码块的问题
2. lambda表达式的基本使用
2.1 函数式编程介绍
函数:一定的输入对应一定的输出
lambda表达式就是指一块代码,这块代码可以赋值给一个变量,或者作为方法参数
然后问题就是,这块代码赋值的变量类型是什么?
java8中,所有的lambda的类型都是一个接口,而Lambda表达式本身就是接口的实现。而且要求这个接口中只能有一个抽象方法(java8中还可以有默认方法),默认这块代码就是这个抽象方法的实现。这种接口称为函数式接口。
所以我们就可以自定义一个简单的例子了:
public interface A{
void doSomething();
}
//测试方法中的代码
A a = () -> System.out.println("函数式接口的抽象方法");
2.2 函数式接口
上述代码中的interface A就是一个函数式接口,也被称之为SAM接口,即Single Abstract Method Interfaces
特点:
- 接口有且只有一个抽象方法
- 允许定义静态方法
- 允许定义默认方法
- 允许java.lang.Object中的public方法(如equals等)
- 该注解不是必需的,如果一个接口符合“函数式接口”的定义,那么加不加该注解都没有影响,比如上述接口A,加上注解是让编译器更好地进行检查,如果编写的不是函数式接口,也加了@FunctionalInterface,那么编译器会报错
例子
// 正确的函数式接口
@FunctionalInterface
public interface TestInterface {
// 抽象方法
public void sub();
// java.lang.Object中的public方法
public boolean equals(Object var1);
// 默认方法
public default void defaultMethod(){
}
// 静态方法
public static void staticMethod(){
}
}
// 错误的函数式接口(有多个抽象方法)
@FunctionalInterface
public interface TestInterface2 {
void add();
void sub();
}
JDK中的函数式接口举例
-
java.lang.Runnable,
-
java.awt.event.ActionListener,
-
java.util.Comparator,
-
java.util.concurrent.Callable
-
java.util.function包下的接口,如Consumer、Predicate、Supplier等
name type description Consumer Consuemr 接收T对象,不返回值 Predicate Predicate 接收T对象并返回boolean Function Function<T,R> 接收T对象,返回R对象 Supplier Supplier 不接收值,返回T对象 UnaryOperator UnaryOperator 接收T对象,返回T对象 BinaryOperator BinaryOperator 接收两个T对象,返回T对象 结合StreamAPI可以干很多事。
Lambda可以理解为对应函数式接口的对象。
注意:
-
lambda表达式中的this
与传统的内部类不同,lambda不改变lambda的指向。
@Test public void testLambdaThis(){ System.out.println(this); new Thread( () -> System.out.println("this in lambda: " + this)).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("this in old way: "+ this); } }).start(); } //lambda.TestLambda@1b701da1 //this in lambda: lambda.TestLambda@1b701da1 //this in old way: lambda.TestLambda$1@3c1971c
-
Lambda表达式实现接口的六种类型,这里将两种返回值与三种参数类型进行组合举例
@Test public void testLambdaInterface(){ NoreturenNoParam noreturenNoParam = () -> System.out.println("NoreturenNoParam"); noreturenNoParam.method(); NoreturnOneParam noreturnOneParam = (a) -> System.out.println("NoreturnOneParam: " + a); noreturnOneParam.method(1); NoreturenMultiParam noreturenMultiParam = (a,b) -> System.out.println("NoreturenMultiParam: " + a +" "+ b); noreturenMultiParam.method(1,2); ReturnNoParam returnNoParam = () -> { System.out.println("ReturnNoParam"); return 1; }; returnNoParam.method(); ReturnOneParam returnOneParam = (a) -> { System.out.println("ReturnOneParam: " + a); return 1; }; returnOneParam.method(1); ReturnMultiParam returnMultiParam = (a,b) -> { System.out.println("ReturnMultiParam: " + a + " " + b); return 1; }; returnMultiParam.method(1,2); } //NoreturenNoParam //NoreturnOneParam: 1 //NoreturenMultiParam: 1 2 //ReturnNoParam //ReturnOneParam: 1 //ReturnMultiParam: 1 2
-
语句优化
- 简化参数类型,可以不写参数类型,因为实现的接口方法定义中有。
- 简化参数小括号,如果只有一个参数则可以省略小括号。
- 简化方法体大括号,如果方法只有一条语句,则可以省略大括号。
- 简化方法体大括号,如果方法只有一条return语句,则可以省略大括号。
- 如果这个方法与接口提供的参数和需要的返回值完全一致,就可以进一步简化为方法引用。
2.3 引用方法
快速指向一个已经被实现的方法。
语法
方法归属者::方法名
静态方法的归属者为类名,普通方法归属者为对象。
@Test
public void testLambdaRef(){
Arrays.asList(1,2,3,4,5).forEach(System.out::println);
}
这里的调用的forEach函数如下:
需要出入一个Consumer接口,该接口的唯一抽象方法是:接收一个参数且没有返回值。
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
而System.out.println也是接收一个参数且没有返回值。
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
所以这里就可以直接将该方法作为Consumer的实现,用极简的方式实现方法引用(本质上也就是指向一个代码块),也可以称之为语法糖。
再举一些常用实例:
-
removeIf方法
default boolean removeIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); boolean removed = false; final Iterator<E> each = iterator(); while (each.hasNext()) { if (filter.test(each.next())) { each.remove(); removed = true; } } return removed; }
这里需要一个Predicate接口,该接口的唯一抽象方法是:接受一个参数且返回值类型为boolean。
测试用例
@Test public void testLambdaRef(){ ArrayList<String> list = new ArrayList<>(); list.add("damon1"); list.add("damon2"); list.add("damon3"); list.add("evil"); System.out.println(list.toString()); list.removeIf(ele -> ele.contains("evil")); System.out.println(list.toString()); } //[damon1, damon2, damon3, evil] //[damon1, damon2, damon3]
-
sort方法
public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
这里需要传入一个Comparator接口,该接口唯一的抽象方法是:接受两个参数且返回值为int
测试用例
@Test public void testLambdaRef() { @Data @AllArgsConstructor @NoArgsConstructor class User { String name; int age; } ArrayList<User> users = new ArrayList<>(); users.add(new User("damon1", 5)); users.add(new User("damon1", 1)); users.add(new User("damon1", 3)); System.out.println(users.toString()); users.sort((u1,u2) -> u1.getAge()-u2.getAge()); System.out.println(users.toString()); } //[User(name=damon1, age=5), User(name=damon1, age=1), User(name=damon1, age=3)] //[User(name=damon1, age=1), User(name=damon1, age=3), User(name=damon1, age=5)]