【Java学习笔记】内部类
作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/
1.定义:
顾名思义就是在类中定义的类,分为两类,static 的 和 non-static 的。内部类实际上是一个编译器的现象。编译器将内部类转化为带着一些奇怪名字的普通类文件,虚拟机并不区分一般类和内部类的类中的文件。
2.使用内部类的第一个好处——封装性和多继承
我们进行“第三种关系”分析:
继承结构中无外乎"is a" 和 "has a"两种关系。我们举一个Employee/Manager的is-a的例子:
class Employee {
private String name;
private Date dateHire;
.....
.....
public float getSalary() {
}
...
}
Class Manager extends Employee {
private Employee[ ] subOrdinates;
....
public Employee[ ] getSubOrdinates() {
return subOrdinates;
}
} 而另一种关系是has-a,class Company {
private Employee[ ] allEmployees;
private Manager[ ] allManagers;
....
public Manager[ ] getAllManagers() {
return allManagers();
}
public void printOrganizationTree() {
}
}
然而还有第三种关系,虽不明显但是很常用,称作“平面关系”。我们举一个汽车的例子:一个汽车(类Automobile)可以有许多组件组成,比如引擎、传动系统、排气系统等等。当然其中还可能有一个部件——车载空调(auto AC)。这个auto AC可能从一个通用的类AirConditioner中得到,进行改造后适用于汽车。但是它不能独立存在,汽车必须与其有交互和控制。注意,这里Automobile这个类不可以去实现一个叫做AutoAC的接口,因为汽车不是一个空调,它也不应该有空调应该有的属性。车载空调的属性应该放在Automobile类中。这里我们使用内部类来描述和解决。
/** The general AirConditioner class */
class AirConditioner {
....
public float getTemperatureMin() {
}
public float getTemperatureMax() {
}
}
/** The Automobile class which has an inner class which is an AC */
class Automobile {
private Engine engine;
private GearBox gearBox;
private AutoAirConditioner autoAC;
.....
private class AutoAirConditioner extends AirConditioner {
...
public float getBeltPerimeter () {
float adjusted Volume = engine.getVolume() * 1.1;
......
}
}
public AirConditioner getAirConditioner () {
}
}
AutoAirConditioner类的对象包含在 Automobile类之中,这颇有点像一个包含的继承关系(has-a),但其中有一点不同:AutoAirConditioner对象的行为对外界的开放程度受限于AirConditioner类中定义的接口。并且对于这个类的扩展也仅仅限于Automobile类。
另外,AutoAirConditioner这个内部类可以访问Automobile类中的所有成员,这就不用在Automobile类中定义特定的访问方法了。我们在Automobile类中的AirConditioner提供了一些行为,却没有使用继承或者把Automobile类通过任何public方法进行暴露。这使得接口看上去更加整齐,继承关系更加简单。
另一个例子是说,假设一个汽车设计项目中需要在汽车上使用不同种类的螺栓,我们可能有许多种,所以在什么地方用哪种型号的螺栓我们需要在每一处一个一个的试,那么我们在螺栓上就需要一个遍历时所用的迭代器,iterator。此时,内部类就特别合适这种需求,我们在Automobile类中定义一个内部类Iterator,并且通过一个public方法bolIterator返回一个引用。更棒的是,我们可以创建一个内部匿名类,因为那个类名字只使用一次,我们就干脆把它搞成匿名的,看着简洁。 方便一个类对外提供一个公共接口的实现是内部类的典型应用。
import java.util.Iterator;
class Automobile {
Bolt[ ] boltArray;
......
......
public Iterator boltIterator() {
return new Iterator () {
int count;
public boolean hasNext() {
return (count < boltArray.length) ;
}
public Object next() {
return boltArray[ count ++ ] ;
}
} ;
} // method iterator
}
next() 和 hasNext()方法可以直接访问Automobile类的private成员。注意到Automobile类的行为与Iterator的行为没有任何实际意义上的关联,它们只是一个平面关系。 一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!试想,如果没有内部类机制,只有在Automobile 之外实现一个类Itr,该类有一个Automobile实例成员automobile,为了Itr能获取automobile的内部信息以便实现遍历,Automobile必然要向Itr开放额外的访问接口。
3.内部类探秘
内部类可以访问类中的private成员,这是编译器的一个小把戏,比如内部类AutoAirConditioner 的类文件可能为Automobile$AutoAirConditioner.class。在类文件中,有一个private final的引用,称作this$0指向外部类,它是由编译器综合生成的,通过这个引用,内部类就可以访问外部类的成员了。然而这并不够,我们在外部类中的一些访问方法也要被编译器综合生成。比如,AutoAirConditioner这个内部类可以直接访问Automobile类中的私有变量engine,那么Automobile类的类文件中就会包含一个方法static Engine access$0(Automobile) 。当然这些都是在你无察觉的情况下进行的。
如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽时,Java里用如下格式表达外部类的引用:outerClass.this.
注意:在任何非静态内部类中,不能有静态数据,静态方法或者一个静态内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切。
4.使用内部类的第二个作用——限定操作
假设我们有个类DatabaseService,维持了一个数据库连接池,其中有一个execute(String query)方法从池中取出一个连接,执行查询语句,然后返回。我们现在有个程序需要直接获取到这个连接的句柄,但是我们又不想让它任意的使用Connection对象,我们只想给它有限的一些操作。
class DatabaseService {
private Connection[ ] pool;
.....
public ResultSet execute (String sqlstmt) {
..........
}
private Connection getFreeConnectionFromPool() {
......
}
private void checkinFreeConnection (Connection conn) {
.......
}
public ControlledConnection getControlledConnection ( ) {
return new ControlledConnectionImpl();
}
.....
// inner class to encapsulate one Connection object and keep it under control
class ControlledConnectionImpl implements ControlledConnection {
private Connection myConnection;
private int operationCount;
private ControlledConnection() {
//access private method of class DatabaseService
myConnection = getFreeConnectionFromPool();
}
public Object operateOnConnection (String operation, Object[ ] args) {
operationCount ++
if (operation.equals("createStatement")){
}
else if (operation.equals("prepareStatement")) {
}
else if (operation.equals("commit")) {
}
......
}
}
}
内部类ControlledConnectionImpl仅仅有一个可以供外部程序使用的方法,operateOnConnection()。我们注意到这个内部类的构造函数的是private的,也就是说只有DatabaseService的对象实例才能创建这个对象。外部程序只有通过
getControlledConnection()方法才能取得这个实例。
我们注意到在类DatabaseService中的
getFreeeConnectionFromPool()
方法是private的,所以外部程序是不可用的。现在内部类控制权限有两种方法:第一种方法就是提供一些接口给外部,第二种方法是提供一个类似operateOnConnection()
的API,进行一些操作上的判断,在这个例子中你可以约定一个连接只能进行三步操作,使用operationCount变量。
6.外部类作用范围之外得到内部类对象的方法,
利用其外部类提供的方法创建并返回。public XXXX getXXX(){
return new XXXX();
}
利用基本语法:
outerObject=new outerClass(Constructor Parameters);
outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);
注意:创建非静态内部类对象时,一定要先创建起相应的外部类对象。
8.静态内部类
静态内部类没有了指向外部的引用,只能访问外部类的静态成员,对于非静态成员须通过创建外部类的对象进行访问。使用静态内部类的目的和使用内部类相同。如果一个内部类不依赖于其外部类的实例变量,或与实例变量无关,则选择应用静态内部类。另外一种说法是:B为A的辅助类,且只为A所用时,可将B定义为A的静态内部类。
9.匿名内部类:
匿名内部类是在抽象类及接口的概念之上发展起来的。主要目的就是在继承或实现接口的某一个类只被使用一次的时候来减少类的定义。所谓的匿名就是该类连名字都没有,匿名内部类不可以有构造器,并且它能够访问外部内的一切成员变量和方法,包括私有的,而实现接口或继承类做不到。同样,如果要想从方法中定义的内部类访问方法中的参数,则方法参数的声明处必须加“final”,这是为了将它们传给内部类时保证调用的一致性:有很多匿名内部类对象的生存范围比一次方法调用什么的长多了, 比如监听器,线程什么的. 所以只有把这些引用拷贝一份, 如果不是final,这些还是可以随意赋值的话,那样内部类里面拷贝的那一份就过期了。
10.注意事项:
1)定义在一个方法内的类,又被成为局部内部类,局部内部类和实例内部类一样,可以访问所有外部类的所有成员。此外,局部内部类还可以访问所在方法内部的final类型的参数和变量。
2)内部类可以被定义为抽象类。