一文详解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多线程同步问题完整代码,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • 浅谈Java数据结构之稀疏数组知识总结

    浅谈Java数据结构之稀疏数组知识总结

    今天带大家了解一下Java稀疏数组的相关知识,文中有非常详细的介绍及代码示例,对正在学习java的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-05-05
  • java二维码生成的方法

    java二维码生成的方法

    这篇文章主要为大家详细介绍了java二维码生成的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06
  • java中如何获取时间戳的方法实例

    java中如何获取时间戳的方法实例

    时间戳通常是一个字符序列,唯一地标识某一刻的时间,所以下面这篇文章主要给大家介绍了关于java中如何获取时间戳的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-11-11
  • Java报错:UnsupportedOperationException in Collections的解决方案

    Java报错:UnsupportedOperationException in Collection

    在Java编程中,UnsupportedOperationException是一种常见的运行时异常,通常在试图对不支持的操作执行修改时发生,它表示当前操作不被支持,本文将深入探讨UnsupportedOperationException的产生原因,并提供具体的解决方案和最佳实践,需要的朋友可以参考下
    2024-06-06
  • 使用maven生成可执行的jar包的方法

    使用maven生成可执行的jar包的方法

    这篇文章主要介绍了使用maven生成可执行的jar包的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • Scala中优雅的处理Null问题

    Scala中优雅的处理Null问题

    Spark 采用混合方式,大部分情况下使用 Option,但个别时候出于性能原因才使用了null。一个很好的习惯是当有方法返回值可能为null的时候,使用Option来代替,本文给大家介绍Scala处理Null的知识详解,一起看看吧
    2021-08-08
  • SpringBoot 使用Mongo的GridFs实现分布式文件存储操作

    SpringBoot 使用Mongo的GridFs实现分布式文件存储操作

    这篇文章主要介绍了Spring Boot 使用Mongo的GridFs实现分布式文件存储操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java实现的微信公众号获取微信用户信息示例

    Java实现的微信公众号获取微信用户信息示例

    这篇文章主要介绍了Java实现的微信公众号获取微信用户信息,结合实例形式分析了Java微信公众号获取微信用户信息相关原理、步骤与操作注意事项,需要的朋友可以参考下
    2019-10-10
  • SpringBoot集成ElasticSearch的示例代码

    SpringBoot集成ElasticSearch的示例代码

    Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎,本文给大家介绍SpringBoot集成ElasticSearch的示例代码,感兴趣的朋友一起看看吧
    2022-02-02

最新评论