第一个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表达式的返回值是不是boolean,boolean也正是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"。

相关推荐
哦虎!3 小时前
【JavaEE】网络初识
java
for_ever_love__3 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
我是人✓4 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
欣栀♡4 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
IT研究室5 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计
慧都小项5 小时前
MyEclipse 中创建和测试 JAX-RS REST 接口:从项目配置到 REST Explorer
java·restful·myeclipse·jax-rs
Wang's Blog5 小时前
Java 项目实战: 交友项目 - 入门案例:编写 Dubbo 服务提供者
java·dubbo·交友
秋饼5 小时前
Spring AI 2.0 结构化输出企业级实战:从 BeanOutputConverter 源码到生产级解析容错
java·ai·技术分享·后端开发
摇滚侠5 小时前
《计算机是如何工作的:人人都能懂的计算机软硬件工作原理》第 9 章 机器码与汇编语言 阅读笔记 9
java·开发语言·笔记
Cc.Y5 小时前
Java零基础入门:深入Java核心:文件IO与序列化全解
java·开发语言·python