[关闭]
@changedi 2017-01-24T01:40:51.000000Z 字数 4836 阅读 2927

一起爪哇Java 8(二)——Lambda表达式和方法引用

Java



定义

Java 表达式有很多种,声明一个class是一个表达式,定义一个变量是一个表达式,写一个=赋值逻辑是一个表达式……

Lambda表达式是这样一个表达式:

lambdaParameters -> lambdaBody

在lambdaParameters传递参数,在lambdaBody中编写逻辑。lambda表达式生成的结果就是一个函数式接口(上文提到过的)。lambdaBody中的逻辑内容(各种表达式)不会在定义时执行,在实际函数式接口调用时才会执行。

举几个官方的例子看看:

  1. () -> {} // No parameters; result is void
  2. () -> 42 // No parameters, expression body
  3. () -> null // No parameters, expression body
  4. () -> { return 42; } // No parameters, block body with return
  5. () -> { System.gc(); } // No parameters, void block body
  6. () -> { // Complex block body with returns
  7. if (true) return 12;
  8. else {
  9. int result = 15;
  10. for (int i = 1; i < 10; i++)
  11. result *= i;
  12. return result;
  13. }
  14. }
  15. (int x) -> x+1 // Single declared-type parameter
  16. (int x) -> { return x+1; } // Single declared-type parameter
  17. (x) -> x+1 // Single inferred-type parameter
  18. x -> x+1 // Parentheses optional for
  19. // single inferred-type parameter
  20. (String s) -> s.length() // Single declared-type parameter
  21. (Thread t) -> { t.start(); } // Single declared-type parameter
  22. s -> s.length() // Single inferred-type parameter
  23. t -> { t.start(); } // Single inferred-type parameter
  24. (int x, int y) -> x+y // Multiple declared-type parameters
  25. (x, y) -> x+y // Multiple inferred-type parameters
  26. (x, int y) -> x+y // Illegal: can't mix inferred and declared types
  27. (x, final y) -> x+y // Illegal: no modifiers with inferred types

lambda表达式的参数部分

可以通过上面的例子看到,lambda的参数声明主要包含两大类,一类是声明类型的,一类是不声明类型的(依赖推断的)。其中声明类型的参数,与定义一个方法时声明参数是一样的。

几个注意的点:
1. _不能作为lambda参数。
2. int...与int[]是一致的。
3. 当参数是推断类型时,注意推断类型的类型转换错误,类型是依据上下文变化的。

来个推断的例子:

  1. Function inferedFunc = x -> {
  2. System.out.println(x.getClass().getTypeName());
  3. return x.toString();
  4. };
  5. Object a = inferedFunc.apply(10);
  6. Object b = inferedFunc.apply(100D);

lambda表达式的body部分

body部分的形式同一个方法的描述基本一致,或者是一个表达式,或者是一个block代码。整体理解lambda的参数和body,可以对应上一节的Function接口来看:()的参数部分,对应Function的第一个泛型参数;{}或者类似x+1这样的表达式作为body,对应Function的第二个泛型参数。空参数对应Supplier,而空return对应Consumer。

不同于匿名内部类的形式,lambda表达式的body共享上下文类的this变量。另一个注意点是lambda表达式的body里包含的外部变量,变量需要是final的或者effectively final。

effectively final的定义如下:

这里又引入两个概念:绝对赋值和绝对未赋值。
- 绝对赋值:变量在复杂逻辑中的每个执行路径中都保证赋值语句存在。
- 绝对未赋值:变量在复杂逻辑中的每个执行路径中都保证没有赋值语句存在。

看个例子:(绝对赋值,需要注释掉n=6)

  1. {
  2. int k;
  3. while (true) {
  4. k = n;
  5. if (k >= 5) break; //这里之前是绝对赋值
  6. n = 6; //如果n=2,那么k需要被赋值两次,就不是绝对赋值
  7. }
  8. System.out.println(k);
  9. }

不满足绝对赋值:

  1. {
  2. int k;
  3. while (n < 4) {
  4. k = n;
  5. if (k >= 5) break;
  6. n = 6;
  7. }
  8. System.out.println(k); /* k is not "definitely assigned"
  9. before this statement */
  10. }

绝对未赋值:

  1. void unflow(boolean flag) {
  2. final int k;
  3. if (flag) {
  4. k = 3;
  5. System.out.println(k);
  6. }
  7. else {
  8. k = 4;
  9. System.out.println(k);
  10. }
  11. }

不满足绝对未赋值:

  1. void unflow(boolean flag) {
  2. final int k;
  3. if (flag) {
  4. k = 3;
  5. System.out.println(k);
  6. }
  7. if (!flag) {
  8. k = 4;
  9. System.out.println(k); /* k is not "definitely unassigned"
  10. before this statement */
  11. }
  12. }

body部分也表达出了一部分兼容性,即当body部分是表达式语句时,如果语句允许独立执行,那么该表达式等价于body部分是void返回值的。即如下的例子,list.add是个返回boolean的方法,因为可以独立执行,那么下面的例子都是OK的:

  1. List list = new ArrayList();
  2. // Predicate has a boolean result
  3. java.util.function.Predicate<String> p = s -> list.add(s);
  4. // Consumer has a void result
  5. java.util.function.Consumer<String> c = s -> list.add(s);

方法引用

方法引用表达式是另一类执行函数式接口的模式,在Java 8之前是没有能力表达一个函数方法的,在Java 8引入函数式接口后,每个lambda表达式都代表了一个函数,可以指向性的将lambda表达式赋值给一个Function类的接口。另一个重要的方法就是直接使用函数方法引用。

方法引用是通过[对象名]::[方法名]这种模式来引用的,其中::两个冒号的操作符非常重要。具体的场景针对类、对象实例、数组、泛型等均有不同的支持,下面的例子看看各种方法引用的表达方式:

  1. public class TestJ8MethodReference {
  2. public static void main(String[] args) {
  3. // static method
  4. Function<Integer, Integer> f1 = TestJ8MethodReference::add;
  5. System.out.println(f1.apply(1));
  6. // instance method
  7. Function<String, String> f2 = String::trim;
  8. System.out.println(f2.apply(" abd b"));
  9. TestJ8MethodReference testJ8MethodReference = new TestJ8MethodReference();
  10. Function<Integer, String> f3 = testJ8MethodReference::getStr;
  11. System.out.println(f3.apply(3));
  12. // super
  13. testJ8MethodReference.testSuper();
  14. // explicit type arguments for generic type
  15. testJ8MethodReference.testExplicitType();
  16. // implicit type arguments for generic type
  17. testJ8MethodReference.testImplicitType();
  18. // new
  19. Supplier s1 = TestJ8MethodReference::new;
  20. System.out.println(s1.get());
  21. // type arguments inferred from context
  22. Consumer<int[]> c1 = Arrays::sort;
  23. int[] array = new int[]{4, 3, 2, 1};
  24. c1.accept(array);
  25. // explicit type arguments
  26. Consumer<int[]> c2 = Arrays::<int[]>sort;
  27. c2.accept(array);
  28. // new array
  29. Function<Integer, int[]> f4 = (int[]::new);
  30. int[] a = f4.apply(10);
  31. System.out.println(a.length);
  32. }
  33. public static int add(int x) {
  34. return x + 1;
  35. }
  36. public String getStr(int x) {
  37. return "" + x;
  38. }
  39. public void testSuper() {
  40. Supplier<String> f = super::toString;
  41. System.out.println(f.get());
  42. }
  43. public void testExplicitType() {
  44. List<String> list = new ArrayList<>();
  45. Function<String, Boolean> func = list::add;
  46. System.out.println(func.apply("a"));
  47. }
  48. public void testImplicitType() {
  49. List list = new ArrayList();
  50. Function<String, Boolean> func = list::add;
  51. System.out.println(func.apply("a"));
  52. }
  53. }

其中需要注意的是,数组的new方法引用等价于一个有入参的Function,因为new一个数组是需要指定size的。

总结

无论lambda表达式还是方法引用表达式,所指向的都是一个方法或者是函数。而它们指向的内容能赋值的也一定是函数式接口。这两种指向也是实用场景各异,方法引用需要使用在已有方法上(显而易见),而lambda表达式是一种快速行内声明一个方法且指向一个函数式接口的方法。两者交互配合,基本可以覆盖各种函数式接口使用的场景。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注