第一个Lambda表达式

认识Lambda表达式

Lambda有很多种形式,每一种形式的应用场景也略微不一样,下面对不同形式的Lambda表达式进行介绍。

ini 复制代码
// 示例1
Runnable noArguments=()-> System.out.println("Hello World"); 
// 示例2
ActionListener oneArgument=event-> System.out.println("Hello World");
// 示例3 
Runnable multiStatement=()-> { 
  System.out.print("Hello");
  System.out.println(" World");
};
// 示例4
BinaryOperator<Long> add=(x, y)-> x+y;
// 示例5
BinaryOperator<Long> addExplicit=(Long x, Long y)-> x+y;  
  • 示例1,Lambda表达式不包含参数,使用空括号()表示没有参数。
  • 示例2,Lambda表达式包含且只包含一个参数,可省略参数的括号。
  • 示例3,Lambda表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号{ }将代码块括起来。
  • 示例4,这行代码并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。
  • 示例5,显式声明参数类型,此时就需要使用小括号将参数括起来,多个参数的情况也是如此。

Lambda引用的是值,而不是变量

Lambda表达式对内部引用的变量是有要求的,内部引用的变量必须被声明为final类型或者在既成事实上必须是final的。

将变量声明为final,意味着不能为其重复赋值。同时也意味着在使用final变量时,实际上是在使用赋给该变量的一个特定的值。

ini 复制代码
final String name=getUserName();
button.addActionListener(event-> System.out.println("hi "+name));

Java 8放松了这一限制 可以引用非final变量,但是该变量在既成事实上必须是final。虽然无需将变量声明为final,但在Lambda表达式中,也无法用作非终态变量(非终态变量即变量的值还会改变)。如果坚持用作非终态变量,编译器就会报错,报错信息:Variable used in lambda expression should be final or effectively final(在lambda表达式中使用的变量应该是final的或者实际上是final的)

ini 复制代码
String name=getUserName();
// 修改非final变量,会报错
name="alan";
button.addActionListener(event-> System.out.println("hi "+name));
rust 复制代码
final String name=getUserName();
// 引用非final变量不做修改,不会报错
button.addActionListener(event-> System.out.println("hi "+name));

函数接口

函数接口是只有一个抽象方法的接口,用作Lambda表达式的类型。

Predicate<T>接口为例,接口内定义了很多个方法,但只有一个(test(T t))是抽象方法,其他的都是默认方法或者静态方法,所以它仍然被定义为一个函数式接口。

scss 复制代码
......
@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);
    
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    ......
}

类型推断

我们在编写Lambda表达式的时候,可以省略Lambda表达式中的所有参数类型,javac根据Lambda表达式上下文信息就能推断出参数的正确类型。

下面我们以JDK中两个具体的函数式接口,来介绍一下Java的推断系统是如何实现Lambda的类型推断的。

Predicate接口

csharp 复制代码
public interface Predicate<T> {
    boolean test(T t);
}

Predicate接口,接受一个对象,返回一个布尔值。

ini 复制代码
Predicate<Integer> atLeast5=x-> x > 5;

atLeast5是一个Lambda表达式,表达式x > 5是Lambda表达式的主体,表达式x > 5的返回值就是Lambda表达式主体的值。

对于表达式Predicate<Integer> atLeast5=x-> x > 5,其实现了Predicate接口,因此Predicate接口的单一参数被推断为Integer类型(Integer会被作为泛型参数传入Predicate中)。代码编译时,javac还会检查Lambda表达式的返回值是不是booleanboolean也正是Predicate方法的返回类型。

BinaryOperator接口

ini 复制代码
BinaryOperator<Long> addLongs=(x, y)-> x+y;

BinaryOperator接口:接受两个参数,返回一个值,参数和值的类型均相同。

类型推断系统,根据BinaryOperator<Long> addLongs,可以推断出来x,y都是Long类型,同时 返回值x+y的类型也是Long类型

ini 复制代码
BinaryOperator add=(x, y)-> x+y;

编译器报错:Operator '+' cannot be applied to 'java.lang.Object', 'java.lang.Object'编译器报这个错是因为上面的例子中并没有给出变量add的任何泛型信息,给出的是原始类型的定义。因此,编译器认为参数和返回值都是java.lang.Object实例。

如何理解下面的问题

变量在既成事实上必须是final,Lambda表达式引用的是值,而不是变量?

从两方面来解释这句话

  1. "既成事实上的final":在Java中,当你声明一个变量为final时,意味着这个变量只能被赋值一次,一旦赋值后就不能再改变。这是一个强制的规则。然而,也有另外一种情况,即一个变量在定义之后,只被赋值一次,并且没有修改过。虽然该变量在语法上并没有标记为final,但实际上,由于它的值从未改变过,因此我们可以将其看作是"既成事实上的final"。
  2. "Lambda表达式引用的是值,而不是变量":在Java的Lambda表达式中,如果你在lambda里面引用一个外部变量,那么这个变量必须是effectively final(这个变量的值在初始化后就不能再被改变,即终态变量) 。这是因为Lambda可能会在并发的环境中执行,所以引用的变量的值需要是稳定的,以防止数据的不一致性。 也就是说,Lambda引用的其实是变量初始化后的值。

因此,结合这两条,我们可以理解为在Lambda表达式中只能引用一次赋值并且之后未被修改过的变量,即所谓的"既成事实上的final"。

如何使用BinaryOperator?

BinaryOperator是 Java 8 中引入的一个函数式接口,它接受两个输入参数,返回一个结果,通常用于代表一种特定的操作,比如加法、乘法等。

arduino 复制代码
public class Main {
    public static void main(String[] args) {
        BinaryOperator<Long> addLongs = (x, y) -> x + y;
        Long result = addLongs.apply(2L, 3L);
        // 输出 5
        System.out.println("Result of addition is: " + result);  
    }
}

在这个代码中:我们首先定义了一个 BinaryOperator addLongs,然后通过调用它的 apply 方法,将参数 2L 3L 传给这个 BinaryOperator。这个 BinaryOperator 的作用是将两个参数相加,所以最后返回的结果是 5。打印结果显示:"Result of addition is: 5"

相关推荐
CodeClimb26 分钟前
【华为OD-E卷-最左侧冗余覆盖子串 100分(python、java、c++、js、c)】
java·python·华为od
Q_19284999061 小时前
基于Spring Boot的大学就业信息管理系统
java·spring boot·后端
xmh-sxh-13141 小时前
常用数据库类型介绍
java
single5941 小时前
【c++笔试强训】(第四十一篇)
java·c++·算法·深度优先·图论·牛客
1 9 J1 小时前
Java 上机实践11(组件及事件处理)
java·开发语言·学习·算法
爬菜1 小时前
java简单题目练习
java
bufanjun0011 小时前
JUC并发工具---ThreadLocal
java·jvm·面试·并发·并发基础
南宫生2 小时前
力扣-图论-70【算法学习day.70】
java·学习·算法·leetcode·图论
zfj3212 小时前
java日志框架:slf4j、jul(java.util.logging)、 log4j、 logback
java·log4j·logback·java日志框架·slf4j·jul
程序猿阿伟2 小时前
《探索 Apache Spark MLlib 与 Java 结合的卓越之道》
java·spark-ml·apache