为什么要使用内部类
一般情况,内部类是一个接口或者类的实现,并且可以作为外围类的一个窗口,即通过内部类对象提供的接口方法操作外围类私有的成员。
如果只是需要实现一个接口,那么为什么不直接用外围类实现该接口,而需要使用内部类呢?
这个问题其实说明了我们在选择的时候,是选择直接实现接口,还是使用内部类,实际情况下,如果通过直接实现接口比较方便,那么就不用内部类。
现在我们只考虑内部类实现方式,这个方式有什么特点呢?使用内部类实现接口或继承类,可以做到多重继承
。
下面看一个简单的例子:
interface A {}
interface B {}
class X implements A, B {
}
class Y implement A {
B getB() {
return new B() {};//匿名内部类
}
}
public class test {
static void takeA(A a) {
}
static void takeB(B b) {
}
public static void main(String [] args) {
X x = new X();
Y y = new Y();
takeA(x);
takeA(y);//y本身实现了接口A
takeB(x);
takeB(y.getB());//通过方法获得类B的内部类对象
}
}
上述代码中,使用内部类和直接实现两个接口,语法上都过关,实际开发中选择哪种方式应当以实际业务为主。比如:实际业务需要一个类继承多个类,那么就只能以内部类的方式来实现了。
内部类多重继承的特点:
- 一个类的内部类,可以创建多个对象,每个对象都有独立的信息并且可以访问外围类的私有成员
- 在单个类中,可以存在多个内部类以不同的方式实现同一个接口或继承同一个类
- 创建内部类对象的时刻不依赖外围类对象的创建
- 使用内部类实现接口或继承对象,不存在’is a’这种复杂的关系
闭包与回调
闭包是指一个可调用的对象,它包含一些信息,这些信息是来自创建时的作用域。所以内部类实际上是一个面向对象的闭包,因为它不仅拥有外部类对象的信息,还拥有对外部类对象的引用。
interface Incrementable {
void increment();
}
class callee1 implements Incrementable {
private int i=0;
public void increment() {
i++;
System.out.println(i);
}
}
class MyIncrement {
public void increment() {
System.out.println("其他操作");
}
static void f(MyIncrement m) {
m.increment();
}
}
//此类继承 MyIncrement 类,并且通过内部类实现接口 Incrementable
class callee2 extends MyIncrement {
private int i=0;
//重写父类的方法,手动调用父类的方法
public void increment() {
super.increment();
i++;
System.out.println(i);
}
//内部类实现 Incrementable 接口的 increment() 方法
public class Closure implements Incrementable {
public void increment() {
callee2.this.increment();
}
}
//得到内部类对象的方法
Incrementable getCallBackRef() {
return new Closure();
}
}
class caller {
private Incrementable ref;
caller(Incrementable ref) {
this.ref = ref;
}
void go() {
ref.increment();
}
}
public class test1 {
public static void main(String [] args) {
callee1 c1 = new callee1();//实现 Incrementable 接口
callee2 c2 = new callee2();//继承 MyIncrement 类,并且通过内部类实现接口 Incrementable
MyIncrement.f(c2);
caller caller1 = new caller(c1); // caller类接收一个Incrementable对象
caller caller2 = new caller(c2.getCallBackRef());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
/*Output:
其他操作
1
1
2
其他操作
2
其他操作
3
*/
上述代码中,callee1
类简单的实现了接口Incrementable
。而callee2
继承了类MyIncrement
,其中已经存在一个与接口Incrementable
中同名的方法,并且两者毫不相干,所以只能通过内部类来实现这个接口。
也就是说在一个操作类caller
中,只接受接口Incrementable
的类,那么要想使得callee2
类也能够被操作,只需要在callee2
类中创建一个内部类并实现接口Incrementable
,内部类对象作为一个钩子,可以让操作类间接操作钩子所在的外围类。
这样实现的好处是这个钩子受接口的限制,只能使用接口的方法。如果以指针的形式交给操作类操作的话,可以对原类做任何操作,是不够安全的。
控制框架中的内部类
控制框架是指用于解决响应事件的需求,Java Swing 库就是一个典型的控制框架。
举个栗子,现在有一个简单控制框架,在事件就绪的时候,这里指倒计时结束时,触发事件,要触发的事件可以是实现一些操作,我们这里简化为执行action()
方法。
首先定义事件类:
public abstrat class Event {
public long startTime;
protected final long waitTime;
public Event(long waitTime) {
this.waitTime = waitTime;
start();
}
public start() {
startTime = System.nanoTime() + waitTime;
}
public boolean ready() {
return System.nanoTime() >= startTime;
}
public abstract action();
}
创建事件Event
时,指定多少时间后触发,start()
方法可以使事件触发后重新开始计时,再触发。
下面实现用于管理事件、触发事件的一个简单控制框架。事件统一被保存于一个列表中。
public class Controller {
private List<Event> eventList = new ArrayList<Event>();
public void addEvent(Event e) {
eventList.add(e);
}
public void run() {
while(eventList.size() > 0) {
for(Event e : eventList) {
if(e.ready()) {
System.out.println(e);
e.action();//触发事件
eventList.remove(e);//从列表中移除已经触发过的事件
}
}
}
}
}
到目前为止,控制框架不变的部分已经实现完成,但是现在还不知道那些事件触发时到底做了什么,这就是将变化的事物与不变的事物相互分离。变化的事物就是不同的Event
子类具有不同的行为。
内部类要做的是:
- 控制框架的完整实现是一个单个类,从而使得细节全部封装起来,这个类可能需要很多不同的
action
,这就需要内部类来逐个实现了。 - 内部类可以很容易访问外部类的成员,使得整体代码非常简洁。
接下来考虑实现一个温室调节系统,即可以调节温度、湿度、灯光以及重启系统功能。考虑到这个温室调节系统是一个整体,但其内部所具有的事件有多个,因此内部的事件以内部类形式实现。
//继承自 Controller 类
public class GreenhouseControls extends Controller {
private boolean light = false;//表示灯光开关
private int humidity = 50;//表示湿度
private int temperature = 37;//表示温度
public class LightOn extends Event {
public LightOn(long waitTime) {
super(waitTime);
}
public void action() {
light = true;
}
public String toString() {
return "开灯";
}
}
public class LightOff extends Event {
public LightOff(long waitTime) {
super(waitTime);
}
public void action() {
light = false;
}
public String toString() {
return "关灯";
}
}
public class humidityUp extends Event {
public humidityUp(long waitTime) {
super(waitTime);
}
public void action() {
humidity++;
}
public String toString() {
return "湿度调高";
}
}
public class humidityDown extends Event {
public humidityDown(long waitTime) {
super(waitTime);
}
public void action() {
humidity--;
}
public String toString() {
return "湿度调低";
}
}
public class temperatureUp extends Event {
public temperatureUp(long waitTime) {
super(waitTime);
}
public void action() {
temperature++;
}
public String toString() {
return "温度调高";
}
}
public class temperatureDown extends Event {
public temperatureDown(long waitTime) {
super(waitTime);
}
public void action() {
temperature--;
}
public String toString() {
return "温度调低";
}
}
public class restart extends Event {
private Event[] eventList;
public restart(long waitTime, Event[] eventList) {
super(waitTime);
this.eventList = eventList;
for(Event e : eventList) {// 将事件组放入到控制器的事件队列中
addEvent(e);
}
}
public void action() {
for(Event e : eventList) {
e.start();
addEvent(e);
}
start();
addEvent(this);//将重启事件本身也放进控制器队列,也就是定时重启
}
public String toString() {
return "重启系统";
}
}
}
下面,我们来使用这个温室调节系统:
public class GreenHouseController {
public static void main(String [] args) {
GreenhouseControls ghc = new GreenhouseControls();
ghc.addEvent(ghc.new humidityDown(900));
Event[] eventList = {
ghc.new LightOn(200);
ghc.new LightOff(1000);
ghc.new temperatureDown(2000);
ghc.new temperatureUp(600);
}
ghc.addEvent(ghc.new restart(100, eventList));
ghc.run();
}
}
上述例子更加深刻的显示了内部类的使用价值。
内部类的继承与重写
内部类是可以被继承的,但是由于成员内部类对象都有一个外部类对象的引用,因此需要通过特殊语法来处理这个特性:
class outer {
class inner {}
}
class test extends outer.inner {
test(outer out) {
out.super();
}
}
在内部类的子类的构造器中,需要显示调用外部类的构造,使得该子类能够正确初始化。
对于重写,实际上父类与子类具有相同的内部类时,这两个内部类并没有关联,当然也不具有多态的特征了。
内部类标识
内部类编译后也会产生一个.class
文件,命名规则是外围类名称加上$
再加上内部类名称,匿名类则只有数字作为标识。局部内部类由于作用域比较小,可能存在同名类的情况,因此为了区分,局部内部类产生的.class
文件会在$
和内部类名之间标记数字。
interface Find{}
public class Out{
public class Inner1{
}
public class Inner2{
}
public static class Inner3{
}
public void method(){
class FindImpl implements Find{
}
}
public void method2(){
class FindImpl implements Find{
}
}
public void method(){
new Find(){
};
}
public void method2(){
new Find(){
};
}
}
上例代码中,产生的.class
文件分别是:
- Out.class
- Out$Inner1.class
- Out$Inner2.class
- Out$Inner3.class
- Find.class
- Out$1FindImpl.class
- Out$2FindImpl.class
- Out$1.class
- Out$2.class