今天上班在某封装的go框架里发现了久违的闭包问题,于是了解了一下Java的闭包实现。闭包是啥?有权访问另一个函数作用域内变量的函数都是闭包。
为什么需要闭包。
闭包的价值在于可以作为函数对象或者匿名函数,持有上下文数据,作为第一级对象进行传递和保存。
闭包广泛用于回调函数、函数式编程以及一些基础框架特殊场景。
JAVA中的闭包。
在JAVA中,闭包是通过“接口+内部类”实现,JAVA的内部类也可以有匿名内部类。
1、内部类。
在JAVA中,内部类可以访问到外围类的变量、方法或者其它内部类等所有成员,即使它被定义成private了,但是外部类不能访问内部类中的变量。这样通过内部类就可以提供一种代码隐藏和代码组织的机制,并且这些被组织的代码段还可以自由地访 问到包含该内部类的外围上下文环境。
public class DemoClass{
private int length =0;
private class InnerClass implements ILog
{
@Override
public void Write(String message) {
System.out.println("DemoClass.InnerClass:" + length);
}
}
public ILog logger() {
return new InnerClass();
}
public static void main(String[] args){
DemoClass demoClass = new DemoClass();
demoClass.logger().Write("abc");
//new
DemoClass dc = new DemoClass();
InnerClass ic = dc.logger();
ic.Write("abcde");
}
}
从上可见,InnerClass是定义在DemoClass内部的一个内部类,而且InnerClass还可以是Private。
如何通过this显式引用外围类的变量? * 通过此格式进行引用:{外围类名}.this.{变量名称}。如:
DemoClass.this.length = message.length();
2、局部内部类。
局部内部类是指在方法的作用域内定义的的内部类。
public class DemoClass2 {
private int length =0;
public ILog logger() {
//在方法体的作用域中定义此局部内部类
class InnerClass implements ILog
{
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass2.InnerClass:" + length);
}
}
return new InnerClass();
}
}
因为InnerClass类是定义在logger()方法体之内,所以InnerClass类在方法的外围是不可见的。
3、匿名内部类。
顾名思义,匿名内部类就是匿名、没有名字的内部类,通过匿名内部类可以更加简洁的创建一个内部类。
public class DemoClass3 {
private int length =0;
public ILog logger() {
return new ILog() {
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass3.AnonymousClass:" + length);
}
};
}
}
由此可见,要创建一个匿名内部类,可以new关键字来创建。
格式:new 接口名称(){}
格式:new 接口名称(args…){}
4、final关键字。
闭包所绑定的本地变量必须使用final修饰符,以表示为一个恒定不变的数据,创建后不能被更改。
public class DemoClass4 {
private int length =0;
public ILog logger(int level) {//final int level
//final
final int logLevel = level+1;
switch(level)
{
case 1:
return new ILog() {
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass4.AnonymousClass:InfoLog " + length);
System.out.println(logLevel);
}
};
default:
return new ILog() {
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass4.AnonymousClass:ErrorLog " + length);
System.out.println(logLevel);
}
};
}
}
public static void main(String[] args){
DemoClass4 demoClass4 = new DemoClass4();
demoClass4.logger(1).Write("abcefghi");
}
}
从例子中可以看到,logger方法接受了一个level参数,以表示要写的日志等级,这个level参数如果直接赋给内部类中使用,会导致编译时错误,提示level参数必须为final,这种机制防止了在闭包共享中变量取值错误的问题。解决方法可以像例子一样在方法体内定义一下新的局部变量,标记为final(JDK1.8之后默认final,可以不加),然后把参数level赋值给它:
final int logLevel = level ;
//或者直接参数中添加一个final修饰符:
public ILog logger(final int level {
5、实例初始化。
匿名类的实例初始化相当于构造器的作用,但不能重载。
public ILog logger(final int level) throws Exception {
return new ILog() {
{
//实例初始化,不能重载
if(level !=1)
throw new Exception("日志等级不正确!");
}
@Override
public void Write(String message) {
length = message.length();
System.out.println("DemoClass5.AnonymousClass:" + length);
}
};
}
匿名内部类的实例初始化工作可以通过符号 {…} 来标记,可以在匿名内部类实例化时进行一些初始化的工作,但是因为匿名内部类没有名称,所以不能进行重载,如果必须进行重载,只能定义成命名的内部类。
闭包的妙用
某日,基础框架的http请求方法需要提供一个功能:接收不定个数、顺序的参数,使得http请求时拥有参数所代表的能力。
首先,接收不定个数的参数这个很容易做到,可使用可变参数列表接收参数,但这个参数将会是个数组,需要统一类型。
经考虑,这里决定使用接口作为统一类型的可变参数,传递参数时,可以传不同的接口实现(单方法接口视为函数式接口,可传入lambda函数作为实现)。
但问题又来了,怎么样让接收参数的方法拥有对应的能力?
可以考虑在方法中设定默认参数列表,其中必备的能力设置默认值,其他设置为null,方法执行时,只有对应的能力属性非null,才会进行对应的能力。
有人会问,怎么才能让lambda传入的参数赋值到方法中呢?
注意,这里正式开始点题,闭包,lambda接收的参数的作用域理应只有lambda中,但可以通过在外层lambda接收参数,然后return一个lambda,返回的lambda中将接收方法中的对象设置属性为外lambda的值,这样就可以利用闭包将方法调用时的传参设置到方法中,实现了能力可拔插。
下面是go的实现,Java实现以后再说(开始摆烂。。
// 调用处
response := requests.NSGet(ctx, createPayOrderURL, params, requests.SetTimeout(2000), setLocalProxyV2())
这是闭包的实现,外方法接收的参数,供返回的方法来使用
// SetTimeout 设置超时时间
func SetTimeout(millisecond int) RequestOptionFun {
return func(r *RequestOption) {
r.timeOut = millisecond
}
}
// SetHeader 设置请求头时间
func SetHeader(headers map[string]string) RequestOptionFun {
return func(r *RequestOption) {
r.headers = headers
}
}
结构体,类似于Java的类和接口
// A RequestOption 请求控制器
type RequestOption struct {
timeOut int
headers map[string]string
proxy *url.URL
event *event.Request
}
// A RequestOptionFun 对象
type RequestOptionFun func(*RequestOption)
http请求方法内实际调用闭包赋值属性
func getOption(opts ...RequestOptionFun) *RequestOption {
option := &RequestOption{
timeOut: 3000,
headers: make(map[string]string),
proxy: nil,
}
for _, opt := range opts {
opt(option)
}
return option
}