Java内部类——闭包与回调
前些天研究单例模式时看到了静态内部类,感觉对于内部类的理解还不是很清晰透彻,于是重新翻了下书和网上的资料关于Java内部类的资料。整理如下:
内部类是什么:
“可以将一个类的定义放在另一个类的定义内部,这就是内部类”——这是《Thinking in Java》的解释。在最初,内部类看起来就像是一种代码隐藏机制,但不仅如此,它还能与外围类通信,并持有其引用。
为什么需要内部类:
因为存在某些问题,如果没有内部类提供的继承多个具体或抽象的类的能力,很难被解决。《Thinking in Java》的作者建议,如果不使用内部类能满足需求,就应该不使用内部类。因此应该避免以程序员炫技为目的地在重要项目中滥用内部类。
内部类如何使用:
在这里,通过几个概念来说明内部类的使用,当然只是一部分用法,仅当抛砖引玉:
1)嵌套类(也称静态内部类)。如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static,单例模式其中的一种写法应用到这个特性来实现延迟加载对象的实例(详细见我另外一篇博文Java设计模式-单例模式)。普通的内部类对象隐式地保存了一个指向创建它的外围类对象的引用,但内部类时static的时候,就不是这样了。静态内部类有三个特点
1.1 要创建嵌套类的对象,并不需要其外围类的对象。
1.2 不能从嵌套类的对象中访问非静态的外围类对象。
1.3 普通内部类的字段和方法,只能放在类的外部层次,所以不能有static数据和static字段,但是嵌套类可以包含这些。
2)匿名内部类。下面的语法,第一次看见可能会觉得有点奇怪:
1 public class Person { 2 ... 3 return new Walking() { 4 //继承Walking的这个匿名类的定义 5 }; 6 }
Walking()将返回值的生成与表示这个返回值的类的定义结合在一起了。这种语法指的是:创建一个继承自Walking的匿名类的对象。通过new表达式返回的引用被自动向上转型为对Walking的引用。return语句返回的,就是此匿名类的对象。
缺陷:相比一般的继承,匿名内部类可以实现接口,也可以继承类,但不两者兼备,而且就算实现接口,只能实现一个接口。
3)闭包和回调
闭包(closure)是一个可调用的对象,记录了一些来自于创建它的作用域的信息。内部类是面向对象的闭包,因为它不仅包含外围类的信息,还自动拥有一个指向此外围类的引用。《Thinking in Java》提到:“Java最引人争议的问题之一就是,人们认为Java应该包含某种类似指针的机制,以允许回调(callback)。通过回调,对象能携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。如果回调通过指针实现,就只能寄希望于程序员不会误用指针。”
通过内部类提供闭包的功能,比指针更灵活,安全。如下例子所示:
1 package com.test.innerclass; 2 3 public interface Incrementable { 4 void increment(); 5 }
1 package com.test.innerclass; 2 3 public class Callee1 implements Incrementable { 4 private int i = 0; 5 @Override 6 public void increment() { 7 i++; 8 System.out.println(i); 9 } 10 11 }
1 package com.test.innerclass; 2 3 public class MyIncrement { 4 public void increment() { 5 System.out.println("Other Operation"); 6 } 7 static void f(MyIncrement mi) { 8 mi.increment(); 9 } 10 }
1 package com.test.innerclass; 2 3 public class Callee2 extends MyIncrement { 4 private int i = 0; 5 public void increment() { 6 super.increment(); 7 i++; 8 System.out.println(i); 9 } 10 private class Closure implements Incrementable { 11 @Override 12 public void increment() { 13 // TODO Auto-generated method stub 14 Callee2.this.increment(); 15 } 16 } 17 Incrementable getCallbackReference() { 18 return new Closure(); 19 } 20 }
1 package com.test.innerclass; 2 3 public class Caller { 4 private Incrementable callbackReference; 5 Caller(Incrementable cbh) { 6 callbackReference = cbh; 7 } 8 void go() { 9 callbackReference.increment(); 10 } 11 }
下面来进行测试看结果:
1 package com.test.innerclass; 2 3 public class Callback { 4 5 public static void main(String[] args) { 6 Callee1 c1 = new Callee1(); 7 Callee2 c2 = new Callee2(); 8 MyIncrement.f(c2); 9 Caller caller1 = new Caller(c1); 10 Caller caller2 = new Caller(c2.getCallbackReference()); 11 caller1.go(); 12 caller1.go(); 13 caller2.go(); 14 caller2.go(); 15 16 } 17 18 }
这个例子进一步展示了外围类实现一个接口和内部类实现这个接口的区别。Callee1直接实现Incrementable接口最简单,Callee2继承MyIncrement,后者已经有了一个不同的increment()方法,并且与Incrementable接口期望的increment()方法毫不相关。所以如果Callee2继承了MyIncrement,就不能为了Incrementable的用途而覆盖increment()方法了,所以只能用内部类独立实现Incrementable接口。这里,内部类closure实现了Incrementable。提供一个返回Callee2的“钩子”(hook)——能保证安全,无论谁获得这个Incrementable的引用,都只能调用increment(),没有其他功能。Caller的构造器需要一个Incrementable的引用作为参数,这样Caller对象就可以调用Callee类了。
由这个例子可以看出,使用闭包和回调,最大的价值是它的灵活性。可以在运行时动态地决定需要调用什么方法。其次是安全性,通过内部类的机制来限制调用者能使用的方法。最后,感谢Bruce Eckel提供的例子。