在JAVA中,闭包是通过“接口+内部类”实现,JAVA的内部类也可以有匿名内部类。
1、内部类。
顾名思义,内部类就是将一个类定义在另一个类的内部。在JAVA中,内部类可以访问到外围类的变量、方法或者其它内部类等所有成员,即使它被定义成private了,但是外部类不能访问内部类中的变量。这样通过内部类就可以提供一种代码隐藏和代码组织的机制,并且这些被组织的代码段还可以自由的访问到包含该内部类的外围上下文环境。
这里提供了一个例子展示这种机制:
- /JavaClosure/src/innerclass/DemoClass1.java
- public class DemoClass1 {
- private int length =0;
- //private|public
- private class InnerClass implements ILog
- {
- @Override
- public void Write(String message) {
- //DemoClass1.this.length = message.length();
- length = message.length();
- System.out.println("DemoClass1.InnerClass:" + length);
- }
- }
- public ILog logger() {
- return new InnerClass();
- }
- public static void main(String[] args){
- DemoClass1 demoClass1 = new DemoClass1();
- demoClass1.logger().Write("abc");
- //.new
- DemoClass1 dc1 = new DemoClass1();
- InnerClass ic = dc1.new InnerClass();
- ic.Write("abcde");
- }
}
该例子的主要功能是实现一个写日志的ILog接口,但是该接口的类被定义在DemoClass1这个外围类中了,而且这个InnerClass内部类还可以访问其外围类中的私有变量length。
1.1、.new
从上面的例子可见,InnerClass是定义在DemoClass1内部的一个内部类,而且InnerClass还可以是Private。
如何创建这个InnerClass的实例? 可以通过外围类的实例进行创建,如:
- DemoClass1 dc1 = new DemoClass1();
- InnerClass ic = dc1.new InnerClass();
- ic.Write("abcde");
1.2、.this
如何通过this显式引用外围类的变量?通过此格式进行引用:{外围类名}.this.{变量名称}。如:
DemoClass1.this.length = message.length();
2、局部内部类。
局部内部类是指在方法的作用域内定义的的内部类。
- /JavaClosure/src/innerclass/DemoClass2.java
- 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、匿名内部类。
顾名思义,匿名内部类就是匿名、没有名字的内部类,通过匿名内部类可以更加简洁的创建一个内部类。
- /JavaClosure/src/innerclass/DemoClass3.java
- 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修饰符,以表示为一个恒定不变的数据,创建后不能被更改。
- /JavaClosure/src/innerclass/DemoClass4.java
- 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,然后把参数level赋值给它:
final int logLevel = level ;
或者直接参数中添加一个final修饰符:
public ILog logger(final int level {
5、实例初始化。
匿名类的实例初始化相当于构造器的作用,但不能重载。
- /JavaClosure/src/innerclass/DemoClass5.java
- 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);
- }
- };
- }
匿名内部类的实例初始化工作可以通过符号 {...} 来标记,可以在匿名内部类实例化时进行一些初始化的工作,但是因为匿名内部类没有名称,所以不能进行重载,如果必须进行重载,只能定义成命名的内部类。
四、为什么需要闭包。
闭包的价值在于可以作为函数对象或者匿名函数,持有上下文数据,作为第一级对象进行传递和保存。闭包广泛用于回调函数、函数式编程中。
原生java没有提供Lambda表达式,不过可以使用尝试使用Scala的Lambda:
例子1:这个是闭包不?
- scala> var add = (x: Int) => x +1
- scala> add(10)
例子2:
- scala> var more = 1
- scala> var addMore = (x: Int) => x + more
- scala> addMore(10)
五、闭包的问题。
1、让某些对象的生命周期加长。
让自由变量的生命周期变长,延长至回调函数执行完毕。
2、闭包共享。
inal关键字
- /JavaClosure/src/innerclass/ShareClosure.java
- interface Action
- {
- void Run();
- }
- public class ShareClosure {
- List<Action> list = new ArrayList<Action>();
- public void Input()
- {
- for(int i=0;i<10;i++)
- {
- final int copy = i;
- list.add(new Action() {
- @Override
- public void Run() {
- System.out.println(copy);
- }
- });
- }
- }
- public void Output()
- {
- for(Action a : list){a.Run();}
- }
- public static void main(String[] args) {
- ShareClosure sc = new ShareClosure();
- sc.Input();
- sc.Output();
- }
- }
这个例子创建一个接口列表List<Action> ,先向列表中创建 i 个匿名内部类new Action(),然后通过for遍历读出。
因为 i 变量在各个匿名内部类中使用,这里产生了闭包共享,java编译器会强制要求传入匿名内部类中的变量添加final
关键字,所以这里final int copy = i;需要做一个内存拷贝,否则编译不过。(在c#中没有强制要求会导致列有被遍历时
始终会取 i 最大值,这是因为延迟执行引起的)