一文详解java闭包的用途是什么

 更新时间:2024年03月12日 11:51:11   作者:程序媛小刘  
闭包的价值在于可以作为函数对象或者匿名函数,持有上下文数据,作为第一级对象进行传递和保存,下面这篇文章主要给大家介绍了关于java闭包的用途是什么,需要的朋友可以参考下

java 闭包的用途是什么

闭包是一种编程概念,主要用在函数式编程中,其主要用途包括:

  • 数据封装和私有变量:闭包可以使我们在函数内部创建私有变量,只能通过特定的公开方法进行访问和修改。这是模块模式的基础。
  • 实现回调函数和高阶函数:闭包常常被用来作为回调函数,因为它们可以记住自己的词法环境,包括 this 和外部变量。
  • 实现装饰器/函数修饰器:闭包可以用于修改或增强函数的行为。例如,可以创建一个闭包来“记住”前一个函数的调用并据此改变下一个函数调用。
  • 实现柯里化(Currying):闭包可以用于实现柯里化,即把一个接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

然而,虽然闭包有很多用途,但也需要谨慎使用。因为闭包可以保留其词法环境,导致内存消耗增加,如果不当使用,可能会引发内存泄露的问题。因此,在使用闭包时,需要注意及时清理不再需要的闭包,避免造成内存浪费。

Lambda表达式如何实现函数闭包

在Java 8及之后的版本中,Lambda表达式是一种简洁、函数式编程的方法,可以创建只有一个抽象方法的接口(称为函数式接口)的实例。然而,Java的Lambda表达式并不直接支持传统意义上的“函数闭包”,这是因为Java是一种静态类型语言,并且在设计之初并没有考虑闭包的概念。

不过,Java 8引入了一些新特性,如Lambda表达式和函数式接口,这些特性在某些程度上模拟了闭包的行为。特别是,Java 8中的FunctionConsumerPredicate等接口,允许你将代码作为参数传递,并允许在后续的执行过程中引用这些代码。

以下是一个使用Java Lambda表达式模拟函数闭包的例子:

import java.util.function.Function;

public class LambdaClosureExample {
    public static void main(String[] args) {
        // 定义一个Lambda表达式,它接受一个整数并返回其平方
        Function<Integer, Integer> square = x -> x * x;

        // 使用Lambda表达式计算5的平方
        int result = square.apply(5);
        System.out.println("5的平方是: " + result);

        // 使用Lambda表达式计算10的平方
        result = square.apply(10);
        System.out.println("10的平方是: " + result);
    }
}

在这个例子中,square变量是一个Function接口的实例,它接受一个整数并返回其平方。你可以将这个Function对象传递给其他方法,并在后续的执行过程中使用它。这在一定程度上模拟了函数闭包的行为,因为它允许你在后续的执行过程中引用和重用一段代码。

然而,需要注意的是,尽管Java的Lambda表达式和函数式接口在某些情况下可以模拟闭包的行为,但它们并不完全等同于传统意义上的闭包。在Java中,你仍然不能直接引用外部作用域的变量(除非这些变量是final的),因此Java的Lambda表达式并不支持真正的闭包语义。

附:关于Java闭包为什么规定局部变量是final

Java规定:闭包函数使用的局部变量必须是final或者effectively final ( 等效 final ) 的。但是,从直观上看,即使在方法体内改了局部变量,也不像能导致什么谬误的样子。所以,这个final的规矩让人心生疑惑。

  • 先po代码(来自《On Java 8》):
// lambda使用的局部变量必须是final或等效final...

// 基本类型
class Closure6 {
    IntSupplier makeFun(int x) {  // IntSupplier接口中只有一个方法getAsInt(),无参,返回值类型int.
        int i = 0;
        i++;
        x++;
        // return () -> x + i;  // 编译器报错: Variables in lambda expressions must be final or effectively final.
        						// 即:lambda表达式中的变量必须是final 或者 effectively final.
        // 不报错的做法:
        final int iFinal = i;  // final关键字在这里很多余.
        final int xFinal = x;  // 因为这两个变量赋值后没有做任何更改,是等效final的.
        return () -> xFinal + iFinal;
    }
}

// 对象引用
class Closure9 {
    Supplier<List<Integer>> makeFun() {  // Supplier接口中只有一个方法get(),无参,返回<>中类型,此处即 List<Integer>.
        List<Integer> ai = new ArrayList<>();
//        ai = new ArrayList<>(); // Reassignment
        return () -> ai;  // 若前一行不注释, 则这里报错.
    }
}

class Closure1 {
    int i;
    IntSupplier makeFun(int x) {
        return () -> x + i++;  // 使用类成员变量时,可以更改而不报错。
    }
}

这两个报错展示了文章开头的规则。那么这是为啥嘞?

  • 我们已经知道,一个局部变量在它的作用域之外是不存在的,那么把它放在lambda表达式里并返回至作用域外,看起来好像需要java的“特许”。

  • 根据这个猜想,我原以为:java出于某种善意避免程序员犯错,所以立下了规矩,即:被lambda“捕捉”的局部变量必须是final或者"等效final"的,来避免变量被重复访问修改,从而导致confusion(混淆)。

  • 但遗憾的是,我高估了java的“善意”了,通过RednaxelaFX大佬的解析习得:实际上java只是copy了一份value到表达式中(capture-by-value),而不是capture-by-reference(比如C#把被捕获的局部变量“提升”(hoist)到对象里,使变量依托于对象存在),所以lambda使用的变量跟最初定义的局部变量(包括基本类型标识符和对象引用)彻底脱钩了。lambda表达式访问的只是一个副本。

  • 而为了掩饰这种“简单粗暴”导致的“变量不能再次访问”,java干脆告诉你:“变量定义之后就别改了哈”,好像不能再次访问的原因仅仅是这个规定而已。但实际上由于java并没有实现capture-by-reference,因此对于一个离开了作用域就不复存在的局部变量,你即便想改,也改不了。所以,这个规定好像脱裤子放屁,掩耳盗铃了。

  • 被lambda使用的类成员变量则没有这样的束缚,这是因为,(非static)成员变量只依赖于对象存在。而这个对象以一种看不见的“this”方式存在于lambda表达式的参数列表中,所以垃圾回收器不会去伤害这个对象(这个原则对普通函数同样适用)。故,类成员变量不需要被主动capture(捕获),这也印证了代码在Closure1处为何不报错,因为变量 i 始终有参数中隐含的this对象可以依赖。

总结

到此这篇关于java闭包用途是什么的文章就介绍到这了,更多相关java闭包用途内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java实现页面多查询条件必选的统一处理思路

    java实现页面多查询条件必选的统一处理思路

    这篇文章主要为大家介绍了java实现页面多查询条件必选的统一处理思路详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • Java安全框架——Shiro的使用详解(附springboot整合Shiro的demo)

    Java安全框架——Shiro的使用详解(附springboot整合Shiro的demo)

    这篇文章主要介绍了Java安全框架——Shiro的使用详解,帮助大家更好的理解和学习使用Shiro,感兴趣的朋友可以了解下
    2021-04-04
  • spring boot linux启动方式详解

    spring boot linux启动方式详解

    这篇文章主要介绍了spring boot linux启动方式详解,分为为前台启动,后台启动和脚本启动的各种方式讲解,需要的朋友可以参考下
    2017-11-11
  • Java用20行代码实现抖音小视频批量转换为gif动态图

    Java用20行代码实现抖音小视频批量转换为gif动态图

    这篇文章主要介绍了Java用20行代码实现抖音小视频批量转换为gif动态图,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 快速定位Java 内存OOM的问题

    快速定位Java 内存OOM的问题

    这篇文章主要介绍了快速定位Java 内存OOM的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-03-03
  • Spring事件监听器之@EventListener原理分析

    Spring事件监听器之@EventListener原理分析

    这篇文章主要介绍了Spring事件监听器之@EventListener原理分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • java分析html算法(java网页蜘蛛算法示例)

    java分析html算法(java网页蜘蛛算法示例)

    近来有些朋友在做蜘蛛算法,或者在网页上面做深度的数据挖掘,下面使用示例
    2014-03-03
  • Spring配置与依赖注入基础详解

    Spring配置与依赖注入基础详解

    依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例
    2022-08-08
  • 在2023idea中实现SpringBoot的IoC和AOP的方法

    在2023idea中实现SpringBoot的IoC和AOP的方法

    这篇文档详细介绍了如何在Spring Boot中实现IoC(控制反转)和AOP(面向切面编程),深入探讨了AOP的基本概念,包括AOP的作用、优势以及实现方式,最后,它提到了AOP的注解,如@Aspect、@Pointcut、@Before、@After、@AfterReturning、@AfterThrowing和@Around
    2024-11-11
  • Android Studio中ButterKnife插件的安装与使用详解

    Android Studio中ButterKnife插件的安装与使用详解

    本篇文章主要介绍了Android Studio中ButterKnife插件的安装与使用详解,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01

最新评论