一文详解java闭包的用途是什么
java 闭包的用途是什么
闭包是一种编程概念,主要用在函数式编程中,其主要用途包括:
- 数据封装和私有变量:闭包可以使我们在函数内部创建私有变量,只能通过特定的公开方法进行访问和修改。这是模块模式的基础。
- 实现回调函数和高阶函数:闭包常常被用来作为回调函数,因为它们可以记住自己的词法环境,包括 this 和外部变量。
- 实现装饰器/函数修饰器:闭包可以用于修改或增强函数的行为。例如,可以创建一个闭包来“记住”前一个函数的调用并据此改变下一个函数调用。
- 实现柯里化(Currying):闭包可以用于实现柯里化,即把一个接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
然而,虽然闭包有很多用途,但也需要谨慎使用。因为闭包可以保留其词法环境,导致内存消耗增加,如果不当使用,可能会引发内存泄露的问题。因此,在使用闭包时,需要注意及时清理不再需要的闭包,避免造成内存浪费。
Lambda表达式如何实现函数闭包
在Java 8及之后的版本中,Lambda表达式是一种简洁、函数式编程的方法,可以创建只有一个抽象方法的接口(称为函数式接口)的实例。然而,Java的Lambda表达式并不直接支持传统意义上的“函数闭包”,这是因为Java是一种静态类型语言,并且在设计之初并没有考虑闭包的概念。
不过,Java 8引入了一些新特性,如Lambda表达式和函数式接口,这些特性在某些程度上模拟了闭包的行为。特别是,Java 8中的Function
,Consumer
,Predicate
等接口,允许你将代码作为参数传递,并允许在后续的执行过程中引用这些代码。
以下是一个使用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闭包用途内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
springboot3.0整合mybatis-flex实现逆向工程的示例代码
逆向工程先创建数据库表,由框架负责根据数据库表,自动生成mybatis所要执行的代码,本文就来介绍一下springboot mybatis-flex逆向工程,感兴趣的可以了解一下2024-06-06
最新评论