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

简介:

定义

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

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

lambdaParameters -> lambdaBody

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

举几个官方的例子看看:

() -> {}                // No parameters; result is void
() -> 42                // No parameters, expression body
() -> null              // No parameters, expression body
() -> { return 42; }    // No parameters, block body with return
() -> { System.gc(); }  // No parameters, void block body

() -> {                 // Complex block body with returns
  if (true) return 12;
  else {
    int result = 15;
    for (int i = 1; i < 10; i++)
      result *= i;
    return result;
  }
}                          

(int x) -> x+1              // Single declared-type parameter
(int x) -> { return x+1; }  // Single declared-type parameter
(x) -> x+1                  // Single inferred-type parameter
x -> x+1                    // Parentheses optional for
                            // single inferred-type parameter

(String s) -> s.length()      // Single declared-type parameter
(Thread t) -> { t.start(); }  // Single declared-type parameter
s -> s.length()               // Single inferred-type parameter
t -> { t.start(); }           // Single inferred-type parameter

(int x, int y) -> x+y  // Multiple declared-type parameters
(x, y) -> x+y          // Multiple inferred-type parameters
(x, int y) -> x+y    // Illegal: can't mix inferred and declared types
(x, final y) -> x+y  // Illegal: no modifiers with inferred types
AI 代码解读

lambda表达式的参数部分

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

几个注意的点:

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

来个推断的例子:

        Function inferedFunc = x -> {
            System.out.println(x.getClass().getTypeName());
            return x.toString();
        };

        Object a = inferedFunc.apply(10);
        Object b = inferedFunc.apply(100D);
AI 代码解读

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的定义如下:

  • 如果是有初始值的变量(赋值过一次),需要满足:

    • 没有声明final
    • 从未出现在赋值语句的左值部分
    • 从未作为一个变量被++或--之类的递增递减形式操作过
  • 如果是没有初始值的变量,需要满足:

    • 没有声明final
    • 在实际赋值前,绝对未赋值或者未绝对赋值
    • 从未作为一个变量被++或--之类的递增递减形式操作过
  • 方法、构造函数、lambda和异常的参数,会被认为是effectively final

这里又引入两个概念:绝对赋值和绝对未赋值。

  • 绝对赋值:变量在复杂逻辑中的每个执行路径中都保证赋值语句存在。
  • 绝对未赋值:变量在复杂逻辑中的每个执行路径中都保证没有赋值语句存在。

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

{
    int k;
    while (true) {
        k = n;
        if (k >= 5) break;  //这里之前是绝对赋值
        n = 6;    //如果n=2,那么k需要被赋值两次,就不是绝对赋值
    }
    System.out.println(k);
}
AI 代码解读

不满足绝对赋值:

{
    int k;
    while (n < 4) {
        k = n;
        if (k >= 5) break;
        n = 6;
    }
    System.out.println(k);  /* k is not "definitely assigned"
                               before this statement */
}
AI 代码解读

绝对未赋值:

void unflow(boolean flag) {
    final int k;
    if (flag) {
        k = 3;
        System.out.println(k);
    }
    else {
        k = 4;
        System.out.println(k);
    }
}
AI 代码解读

不满足绝对未赋值:

void unflow(boolean flag) {
    final int k;
    if (flag) {
        k = 3;
        System.out.println(k);
    }
    if (!flag) {
        k = 4;
        System.out.println(k);  /* k is not "definitely unassigned"
                                   before this statement */
    }
}
AI 代码解读

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

List list = new ArrayList();
// Predicate has a boolean result
java.util.function.Predicate<String> p = s -> list.add(s);
// Consumer has a void result
java.util.function.Consumer<String> c = s -> list.add(s);
AI 代码解读

方法引用

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

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

public class TestJ8MethodReference {

    public static void main(String[] args) {
        // static method
        Function<Integer, Integer> f1 = TestJ8MethodReference::add;
        System.out.println(f1.apply(1));
        // instance method
        Function<String, String> f2 = String::trim;
        System.out.println(f2.apply("   abd b"));
        TestJ8MethodReference testJ8MethodReference = new TestJ8MethodReference();
        Function<Integer, String> f3 = testJ8MethodReference::getStr;
        System.out.println(f3.apply(3));
        // super
        testJ8MethodReference.testSuper();

        // explicit type arguments for generic type
        testJ8MethodReference.testExplicitType();

        // implicit type arguments for generic type
        testJ8MethodReference.testImplicitType();

        // new
        Supplier s1 = TestJ8MethodReference::new;
        System.out.println(s1.get());

        // type arguments inferred from context
        Consumer<int[]> c1 = Arrays::sort;
        int[] array = new int[]{4, 3, 2, 1};
        c1.accept(array);

        // explicit type arguments
        Consumer<int[]> c2 = Arrays::<int[]>sort;
        c2.accept(array);

        // new array
        Function<Integer, int[]> f4 = (int[]::new);
        int[] a = f4.apply(10);
        System.out.println(a.length);
    }

    public static int add(int x) {
        return x + 1;
    }

    public String getStr(int x) {
        return "" + x;
    }

    public void testSuper() {
        Supplier<String> f = super::toString;
        System.out.println(f.get());
    }

    public void testExplicitType() {
        List<String> list = new ArrayList<>();
        Function<String, Boolean> func = list::add;
        System.out.println(func.apply("a"));
    }

    public void testImplicitType() {
        List list = new ArrayList();
        Function<String, Boolean> func = list::add;
        System.out.println(func.apply("a"));
    }
}
AI 代码解读

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

总结

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

目录
打赏
0
0
0
0
951
分享
相关文章
|
17天前
|
《从头开始学java,一天一个知识点》之:方法定义与参数传递机制
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 🚀 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。上篇:《输入与输出:Scanner与System类》 | 下篇剧透:《方法重载与可变参数》。
46 25
|
11天前
|
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
21 1
|
19天前
|
怎么理解Java中的lambda表达式
Lambda表达式是JDK8引入的新语法,用于简化匿名内部类的代码写法。其格式为`(参数列表) -&gt; { 方法体 }`,适用于函数式接口(仅含一个抽象方法的接口)。通过Lambda表达式,代码更简洁灵活,提升Java的表达能力。
|
23天前
|
《从头开始学java,一天一个知识点》之:运算符与表达式:算术、比较和逻辑运算
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列为你打造Java「速效救心丸」,每天1分钟,地铁通勤、午休间隙即可完成学习。直击高频考点和实际开发中的「坑位」,拒绝冗长概念,每篇都有可运行的代码示例。明日预告:《控制流程:if-else条件语句实战》。
31 6
Java中的异常处理方法
本文深入剖析Java异常处理机制,介绍可检查异常、运行时异常和错误的区别与处理方式。通过最佳实践方法,如使用合适的异常类型、声明精确异常、try-with-resources语句块、记录异常信息等,帮助开发者提高代码的可靠性、可读性和可维护性。良好的异常处理能保证程序稳定运行,避免资源泄漏和潜在问题。
|
25天前
|
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
46 5
|
1月前
|
java.time常用方法汇总
`java.time` API 是从 Java 8 开始引入的时间日期处理库,旨在替代老旧的 `java.util.Date` 和 `Calendar`。它提供了更简洁、强大和灵活的方式处理日期、时间、时区及时间间隔,支持全球化和时间计算需求。API 包含获取当前时间、创建指定时间、解析和格式化字符串、进行加减运算、比较时间、获取年月日时分秒、计算时间间隔、时区转换以及判断闰年等功能。示例代码展示了如何使用这些功能,极大简化了开发中的时间处理任务。
JAVA方法的定义
JAVA方法的定义
124 0
杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
杭州 【Java基础知识 11】java泛型方法的定义和使用(学习+改进+自己理解,想法) (借鉴-侵-删)
68 1
|
10月前
|
Java数组与带参数方法:定义、调用及实践
Java数组与带参数方法:定义、调用及实践
103 1
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等