什么是Lambda

Lambda (大写 Λ ,小写 λ) 读音:lan b (m) da (兰亩达) [‘læ;mdə]
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。

对接口的要求

虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda只能接受函数式接口,所谓的函数式接口指的是只能有一个抽象方法的接口,或者说只能有一个需要被实现的方法,不是规定接口中只能有一个方法。
jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

@Functionallnterface
修饰函数式接口的,要求接口中的抽象方法只有一个。这个注解往往会和 lambda 表达式一起出现。
该注解是给编译器做检查使用的,如果使用了该注解,编译器就会检查该接口中的抽象方法是不是只有一个,如果有多个就会报错:在接口Xxx中找到多个非覆盖抽象方法

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.TYPE)
  4. public @interface FunctionalInterface {}

Lambda语法

Lambda表达式通过操作符->分为两个部分:

  • 左侧:指定了Lambda表达式所需要的所有参数。
  • 右侧:指定了Lambda体,即Lambda表达式所要执行的功能。

语法形式为() -> {},其中()用来描述参数列表,{}用来描述方法体,->为 lambda 运算符 ,读作(goes to)。

一行执行语句的写法:
(parameters) -> expression
如果有多行执行语句,可以加上 {}
(parameters) -> { statements; }
如:

  1. public int add(int x, int y) {
  2. return x + y;
  3. }

转换成Lambda表达式有以下几种写法:

  1. // 指定参数类型及return
  2. (int x, int y) -> { return x + y; }
  3. // 指定参数类型,不指定return
  4. (int x, int y) -> x + y;
  5. // 不指定参数类型和return,编译器会自动推断
  6. (x, y) -> x + y;

Lambda表达式的基本语法结构如下,当然,这里只是简单的Lambda 表达式的应用。

描述 格式
无参数,无返回值 () -> System.out.print(“Lambda…”);
一个参数,无返回值 (String s) -> System.out.print(“Lambda..”)
一个参数,无返回值(参数类型省略,由编译器推断,称为类型推断) (s) -> System.out.print(“Lambda..”)
若只有一个参数,方法的括号可以省略,如果多个参数则必须写上 s -> System.out.print(“Lambda..”)
有参数,且有返回值,如果显式返回语句时就必须使用花括号“{}” (s,t) -> s+t; 或 (s,t) -> {return s+t;};
如果有两个或两个以上的参数,并且有多条语句则需要加上”{}“,一条执行语句可以省略。 (s,t) ->{
System.out.print(s)
System.out.print(5)
return s+t;
};

Lambda对语法的简化(说明示例)

  1. 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值

    (int a, int b) -> {System.out.println(a + “,” + b)} 等价于 (a, b) -> {System.out.println(a + “,” + b)}

  2. 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号

    (a) -> {System.out.println(a)} 等价于 a -> {System.out.println(a)}

  3. 可选的大括号:如果主体包含了一个语句,就不需要使用大括号

    a -> {System.out.println(a)} 等价于 a -> System.out.println(a)

  1. 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值

    a -> return a 等价于 a -> a

Lambda对语法的简化(代码示例)

  1. // 无参数、无返回值
  2. @FunctionalInterface
  3. public interface NoReturnNoParam {
  4. void method();
  5. }
  6. // 1参数、无返回值
  7. @FunctionalInterface
  8. public interface NoReturnOneParam {
  9. void method(String a);
  10. }
  11. // 多参数、无返回值
  12. @FunctionalInterface
  13. public interface NoReturnMultiParam {
  14. void method(String a, String b);
  15. }
  16. // 无参数、有返回值
  17. @FunctionalInterface
  18. public interface ReturnNoParam {
  19. String method();
  20. }
  21. // 1参数、有返回值
  22. @FunctionalInterface
  23. public interface ReturnOneParam {
  24. String method(String a);
  25. }
  26. // 多无参数、有返回值
  27. @FunctionalInterface
  28. public interface ReturnMultiParam {
  29. String method(String a, String b);
  30. }
  1. public class Test1 {
  2. public static void main(String[] args) {
  3. //无参数、无返回值
  4. NoReturnNoParam noReturnNoParam = () -> {
  5. System.out.println("NoReturnNoParam");
  6. };
  7. noReturnNoParam.method();
  8. //一个参数、无返回值
  9. NoReturnOneParam noReturnOneParam = (int a) -> {
  10. System.out.println("NoReturnOneParam param:" + a);
  11. };
  12. noReturnOneParam.method(6);
  13. //多个参数、无返回值
  14. NoReturnMultiParam noReturnMultiParam = (int a, int b) -> {
  15. System.out.println("NoReturnMultiParam param:" + "{" + a + "," + +b + "}");
  16. };
  17. noReturnMultiParam.method(6, 8);
  18. //无参数、有返回值
  19. ReturnNoParam returnNoParam = () -> {
  20. System.out.print("ReturnNoParam");
  21. return 1;
  22. };
  23. int res = returnNoParam.method();
  24. System.out.println("return:" + res);
  25. //一个参数、有返回值
  26. ReturnOneParam returnOneParam = (int a) -> {
  27. System.out.println("ReturnOneParam param:" + a);
  28. return 1;
  29. };
  30. int res2 = returnOneParam.method(6);
  31. System.out.println("return:" + res2);
  32. //多个参数、有返回值
  33. ReturnMultiParam returnMultiParam = (int a, int b) -> {
  34. System.out.println("ReturnMultiParam param:" + "{" + a + "," + b + "}");
  35. return 1;
  36. };
  37. int res3 = returnMultiParam.method(6, 8);
  38. System.out.println("return:" + res3);
  39. }
  40. }

我们可以通过观察以下代码来完成代码的进一步简化,写出更加优雅的代码。

  1. public class Test2 {
  2. public static void main(String[] args) {
  3. //1.简化参数类型,可以不写参数类型,但是必须所有参数都不写
  4. NoReturnMultiParam lamdba1 = (a, b) -> {
  5. System.out.println("简化参数类型");
  6. };
  7. lamdba1.method(1, 2);
  8. //2.简化参数小括号,如果只有一个参数则可以省略参数小括号
  9. NoReturnOneParam lambda2 = a -> {
  10. System.out.println("简化参数小括号");
  11. };
  12. lambda2.method(1);
  13. //3.简化方法体大括号,如果方法条只有一条语句,则可以胜率方法体大括号
  14. NoReturnNoParam lambda3 = () -> System.out.println("简化方法体大括号");
  15. lambda3.method();
  16. //4.如果方法体只有一条语句,并且是return语句,则可以省略方法体大括号
  17. ReturnOneParam lambda4 = a -> a + 3;
  18. System.out.println(lambda4.method(5));
  19. ReturnMultiParam lambda5 = (a, b) -> a + b;
  20. System.out.println(lambda5.method(1, 1));
  21. }
  22. }

手写一个示例

  1. public interface IHello {
  2. String sayHello(String name, String msg);
  3. }
  1. public class IHelloImpl {
  2. public static void main(String[] args) {
  3. // 将一个Lambda表达式赋值给一个接口,说明Lambda表达式就是一种接口数据类型,只不过该接口只能有一个抽象方法
  4. // 参数列表可以省略参数类型,可以写成(name, msg),在JDK1.8中有个叫做类型推断的东西,可以自动推断出参数的类型,
  5. // 因为IHello中只有一个抽象方法,知道方法了就知道参数列表了,从而就能推出参数类型来
  6. IHello iHello = (String name, String msg) -> {
  7. String hello = name + ": " + msg;
  8. return hello;
  9. };
  10. // 调用接口的方法
  11. String content = iHello.sayHello("mengday", "happy new year everyone!");
  12. System.out.println(content);
  13. }
  14. }

函数式接口

函数式接口介绍

函数式接口的定义:只允许有一个抽象方法的接口,那么它就是一个函数式接口。针对函数式接口,官方给我们提供了一个注解@FunctionalInterface,该注解会检查它是否是一个函数式接口,所以如果我们需要自定义一个函数式接口的话,可以在接口类的上方声明@FunctionalInterface。
需要注意的是,数式接口虽然规定只能有一个抽象方法,但是同时可以有多个非抽象方法(如静态方法,默认方法,私有方法)。下面是一个自定义的函数式接口:

  1. @FunctionalInterface
  2. public interface FunctionalInterface {
  3. /**
  4. * 抽象方法(只能有一个)
  5. */
  6. void method();
  7. //void method1();再定义一个会提示:找到多个抽象方法
  8. /**
  9. * 默认方法,必须用default修饰
  10. */
  11. default void defaultMethod() {
  12. System.out.println("默认方法...");
  13. }
  14. /**
  15. * 静态方法方法
  16. */
  17. static void staticMethod() {
  18. System.out.println("静态方法...");
  19. }
  20. }

四大类型接口

Java8中增加了一个包:java.util.function。它们里面包含了常用的函数式接口,该包下定义的函数式接口非常多,主要有四大类:

接口类型 函数式接口 参数类型 返回类型 方法 示例
消费型接口 Consumer T void void accept(T t) 输出一个值
供给型接口 Supplier T T get(
判断型接口 Predicate T boolean boolean test(T t)
功能(函数)型接口 Function T R R apply(T t)
二元函数 BiFunction< T, U, R > R apply(T t, U u);
比较器 Comparator< T > int compare(T o1, T o2);
二元操作 BinaryOperator (T,T) (T) apply(T t,U u) 求两个数的乘积(*)
一元操作 UnaryOperator T T T apply(T t) 逻辑非(!)

可以看出四种函数式接口抽象方法的特点如下:

  • 消费型接口:有参数传入,无结果返回。
  • 供给型接口:无参数传入,但是有返回值
  • 判断型接口:有参传入,但是返回值类型是boolean结果。
  • 功能型接口:既有参数传入又有结果值返回

四种函数式接口的用法简单举例如下:

  1. /**
  2. * 内置最常用的四种函数式接口
  3. */
  4. class FunctionalnterfaceTest{
  5. //消费型接口
  6. public void test0(){
  7. Consumer<String> consumer = s -> System.out.println("[Consumer<T>]--->" + s);
  8. consumer.accept("hello");
  9. }
  10. //供给型接口
  11. public void test1() {
  12. Supplier<String> supplier = () -> "hello";
  13. System.out.println("[Supplier<T>]--->" + supplier.get());
  14. }
  15. //判断型接口
  16. public void test2() {
  17. Predicate<String> predicate = s -> s.equals("hello");
  18. System.out.println("[Predicate<T>]--->" + predicate.test("hello"));
  19. }
  20. //功能性接口
  21. public void test3() {
  22. Function<String, String> function = (s) -> {
  23. return "hello" + s;
  24. };
  25. System.out.println("[Function<T, R>]--->" + function.apply("666"));
  26. }
  27. }
  28. public class FunctionalnterfaceExample {
  29. public static void main(String[] args) {
  30. FunctionalnterfaceTest functionalnterfaceTest = new FunctionalnterfaceTest();
  31. functionalnterfaceTest.test0();
  32. functionalnterfaceTest.test1();
  33. functionalnterfaceTest.test2();
  34. functionalnterfaceTest.test3();
  35. }
  36. }

**【运行结果】** [Consumer]—->hello [Supplier]—->hello [Predicate]—->true [Function]—->hello666

其实,这四种函数式接口每种都有其变种形式,如消费型接口有如下几种形式:

接口名 参数类型 返回类型 抽象方法 描述
Consumer T void void accept(T t) 接收一个对象用于完成功能,不返回结果
BiConsumer T,U void void accept(T t,U u) 接收两个对象用于完成功能,不返回结果
DoubleConsumer double void void accept(double value) 接收一个double值,不返回结果
IntConsumer int void void accept(int value) 接收一个int值,不返回结果
longConsumer long void void accept(long value) 接收一个long值,不返回结果
ObjDoubleConsumer T,double void void accept(T t,double value) 接收一个对象和一个double值,不返回结果
ObjIntConsumer T,int void void accept(T t,int value) 接收一个对象和一个int值,不返回结果
ObjLongConsumer T,long void void accept(T t,long value) 接收一个对象和一个long值,不返回结果
  1. package java.util.function;
  2. import java.util.Objects;
  3. @FunctionalInterface
  4. public interface Predicate<T> {
  5. // 在给定的参数上评估这个谓词
  6. boolean test(T t);
  7. // 返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑AND
  8. default Predicate<T> and(Predicate<? super T> other) {
  9. Objects.requireNonNull(other);
  10. return (t) -> test(t) && other.test(t);
  11. }
  12. // 返回表示此谓词的逻辑否定的谓词,相当于not
  13. default Predicate<T> negate() {
  14. return (t) -> !test(t);
  15. }
  16. // 返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑或
  17. default Predicate<T> or(Predicate<? super T> other) {
  18. Objects.requireNonNull(other);
  19. return (t) -> test(t) || other.test(t);
  20. }
  21. // 返回根据 Objects.equals(Object, Object)测试两个参数是否相等的谓词
  22. static <T> Predicate<T> isEqual(Object targetRef) {
  23. return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object);
  24. }
  25. }
  26. public static void main(String[] args) {
  27. // 可以构造复杂的条件:并且and、或者or、否negate
  28. String email = "mengday@gmal.com";
  29. Predicate<String> predicate = (str) -> str.length() > 20;
  30. // 测试 emial.length > 0 的boolean
  31. boolean result = predicate.test(email); // false
  32. // 测试 !(emial.length > 0) 的boolean
  33. result = predicate.negate().test(email); // true
  34. Predicate<String> orPredicate = (str) -> str.contains("@");
  35. // 测试 emial.length > 0 or emial.contains("@")
  36. result = predicate.or(orPredicate).test(email); // true
  37. }

自带接口示例

  1. @FunctionalInterface
  2. public interface Comparator<T> {
  3. int compare(T o1, T o2);
  4. // 其它static、default方法
  5. }
  6. public class Main {
  7. public static void sayHello(IHello iHello, String name) {
  8. iHello.sayHello(name);
  9. }
  10. public static void main(String[] args) {
  11. IHello iHello = name -> {
  12. String content = name + ": " + "happy new year everyone!";
  13. System.out.println(content);
  14. };
  15. // 这里可以把iHelo看成一个匿名实现类来传递参数
  16. sayHello(iHello, "mengday");
  17. // 如果去掉变量的接收,直接将Lambda表达式传递到参数中,此时Lambda表达式更像是一个函数
  18. // 也就是说JDK1.8竟然可以将一个函数作为参数传递到方法中,这是之前版本做不到的
  19. // 将函数作为方法的参数传递,一般用于回调函数,将回调函数传递到方法中
  20. sayHello(name -> {
  21. String content = name + ": " + "happy new year everyone!";
  22. System.out.println(content);
  23. }, "mengday");
  24. }
  25. }

练习示例

  1. public class Test {
  2. public static void main(String[] args) {
  3. Predicate<Integer> predicate = x -> x > 185;
  4. Student student = new Student("9龙", 23, 175);
  5. System.out.println(
  6. "9龙的身高高于185吗?:" + predicate.test(student.getStature()));
  7. Consumer<String> consumer = System.out::println;
  8. consumer.accept("命运由我不由天");
  9. Function<Student, String> function = Student::getName;
  10. String name = function.apply(student);
  11. System.out.println(name);
  12. Supplier<Integer> supplier =
  13. () -> Integer.valueOf(BigDecimal.TEN.toString());
  14. System.out.println(supplier.get());
  15. UnaryOperator<Boolean> unaryOperator = uglily -> !uglily;
  16. Boolean apply2 = unaryOperator.apply(true);
  17. System.out.println(apply2);
  18. BinaryOperator<Integer> operator = (x, y) -> x * y;
  19. Integer integer = operator.apply(2, 3);
  20. System.out.println(integer);
  21. test(() -> "我是一个演示的函数式接口");
  22. }
  23. /**
  24. * 演示自定义函数式接口使用
  25. *
  26. * @param worker
  27. */
  28. public static void test(Worker worker) {
  29. String work = worker.work();
  30. System.out.println(work);
  31. }
  32. public interface Worker {
  33. String work();
  34. }
  35. }
  36. //9龙的身高高于185吗?:false
  37. //命运由我不由天
  38. //9龙
  39. //10
  40. //false
  41. //6
  42. //我是一个演示的函数式接口

Lambda常用示例

方法引用

普通方法的引用

有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。

方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象

  1. public class TestSample1 {
  2. public static void main(String[] args) {
  3. ReturnOneParam lambda1 = a -> doubleNum(a);
  4. System.out.println(lambda1.method(3));
  5. //lambda2 引用了已经实现的 doubleNum 方法
  6. ReturnOneParam lambda2 = TestSample1::doubleNum;
  7. System.out.println(lambda2.method(3));
  8. TestSample1 exe = new TestSample1();
  9. //lambda4 引用了已经实现的 addTwo 方法
  10. ReturnOneParam lambda4 = exe::addTwo;
  11. System.out.println(lambda4.method(2));
  12. }
  13. /**
  14. * 要求
  15. * 1.参数数量和类型要与接口中定义的一致
  16. * 2.返回值类型要与接口中定义的一致
  17. */
  18. public static int doubleNum(int a) {
  19. return a * 2;
  20. }
  21. public int addTwo(int a) {
  22. return a + 2;
  23. }
  24. }

构造方法的引用

一般我们需要声明接口,该接口作为对象的生成器,通过 类名::new 的方式来实例化对象,然后调用方法返回对象。

  1. public class TestSample2 {
  2. public static void main(String[] args) {
  3. ItemCreatorBlankConstruct creator = () -> new Item();
  4. Item item = creator.getItem();
  5. ItemCreatorBlankConstruct creator2 = Item::new;
  6. Item item2 = creator2.getItem();
  7. ItemCreatorParamContruct creator3 = Item::new;
  8. Item item3 = creator3.getItem(112, "鼠标", 135.99);
  9. }
  10. }

lambda 表达式创建线程

我们以往都是通过创建 Thread 对象,然后通过匿名内部类重写 run() 方法,一提到匿名内部类我们就应该想到可以使用 lambda 表达式来简化线程的创建过程。

  1. public class TestSample3Thread {
  2. public static void main(String[] args) {
  3. //通过使用匿名类创建线程
  4. Thread myThread = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. for (int i = 0; i < 10; i++) {
  8. System.out.println(2 + ":" + i);
  9. }
  10. }
  11. });
  12. myThread.start();
  13. //简化方式
  14. Thread myThread2 = new Thread(() -> {
  15. for (int i = 0; i < 10; i++) {
  16. System.out.println(2 + ":" + i);
  17. }
  18. });
  19. myThread2.start();
  20. new Thread(() -> {
  21. for (int i = 0; i < 10; i++) {
  22. System.out.println(2 + ":" + i);
  23. }
  24. }, "myThread").start();
  25. }
  26. }

集合操作

遍历集合

我们可以调用集合的public void forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。以下是 Consumer 接口的方法以及遍历集合的操作。Consumer 接口是 jdk 为我们提供的一个函数式接口。

  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3. void accept(T t);
  4. //....
  5. }
  1. public class TestSample4Collection {
  2. public static void main(String[] args) {
  3. ArrayList<Integer> list = new ArrayList<>();
  4. Collections.addAll(list, 1, 2, 3, 4, 5);
  5. //lambda表达式 方法引用
  6. list.forEach(System.out::println);
  7. list.forEach(element -> {
  8. if (element % 2 == 0) {
  9. System.out.println(element);
  10. }
  11. });
  12. }
  13. }

删除集合中的某个元素

我们通过public boolean removeIf(Predicate<? super E> filter)方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。

  1. public class TestSample4CollectionDel {
  2. public static void main(String[] args) {
  3. ArrayList<Item> items = new ArrayList<>();
  4. items.add(new Item(11, "小牙刷", 12.05));
  5. items.add(new Item(5, "日本马桶盖", 999.05));
  6. items.add(new Item(7, "格力空调", 888.88));
  7. items.add(new Item(17, "肥皂", 2.00));
  8. items.add(new Item(9, "冰箱", 4200.00));
  9. items.removeIf(ele -> ele.getId() == 7);
  10. //通过 foreach 遍历,查看是否已经删除
  11. items.forEach(System.out::println);
  12. }
  13. }

集合内元素的排序

在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。

  1. public class TestSample4CollectionSort {
  2. public static void main(String[] args) {
  3. ArrayList<Item> list = new ArrayList<>();
  4. list.add(new Item(13, "背心", 7.80));
  5. list.add(new Item(11, "半袖", 37.80));
  6. list.add(new Item(14, "风衣", 139.80));
  7. list.add(new Item(12, "秋裤", 55.33));
  8. /* list.sort(new Comparator<Item>() {
  9. @Override
  10. public int compare(Item o1, Item o2) {
  11. return o1.getId() - o2.getId();
  12. }
  13. });*/
  14. list.sort((o1, o2) -> o1.getId() - o2.getId());
  15. System.out.println(list);
  16. }
  17. }

Lambda表达式中的闭包问题

这个问题我们在匿名内部类中也会存在,如果我们把注释放开会报错,告诉我 num 值是 final 不能被改变。这里我们虽然没有标识 num 类型为 final,但是在编译期间虚拟机会帮我们加上 final 修饰关键字。

  1. public class Main {
  2. public static void main(String[] args) {
  3. int num = 10;
  4. Consumer<String> consumer = ele -> {
  5. System.out.println(num);
  6. };
  7. //num = num + 2;
  8. consumer.accept("hello");
  9. }
  10. }

修改Lambda表达式中的变量值
使用 Java AtomicInteger 和数组修改 Lambda 表达式中的变量值
Java 8语言规范§15.27.2提出了上述问题:

使用任何 lambda 表达式外部声明的局部变量、形式参数或异常参数 必须声明为 final或 effective final 变量(§4.12.4), 否则会报告编译错误。

3种方案:

  1. 使静态变量
  2. 使用数组
  3. AtomicInteger

优缺点

优点

  1. 简化了函数式接口匿名内部类的语法。
  2. 使用Lambda表达式可以让我们的代码更少,看上去更简洁,代码更加灵活。

缺点

  1. 调试运行和后期维护比较麻烦;
  2. 开发目前还是习惯普通的方式Stream+Lambda表达式容易看晕;

mybatis

总结

刻意练习

Stream+Lambda JDK