今天来说说一个非常特殊的类——内部类。
在实际的开发中,比较常用到的,一方面是Java图形开发的事件处理中,另一方面,在Spring应用开发中,会大量用到,在Spring框架本身,就大量使用了匿名内部类,通过回调方法实现了一些模板应用。
下面来具体看看内部类的内容。
所谓内部类,就是定义在一个类内部的类。
内部类的概念是在JDK1.1中被引入的。引入内部类,主要有以下一些考虑:
内部类对象能访问它所处类的私有数据;
内部类能够隐藏起来不为同一个包中的其他类访问;
匿名内部类可以方便的用在回调方法(callback method)中,典型应用是图形编程中的事件处理。
1 内部类定义
内部类有时也称为“嵌套类(nested class)”,是定义在一个类内部的类。
这里所谓的“类的内部”,指的是在类定义的两个大括号之间,如下:
public class OuterClass{
//可以在这个类的内部的任何地方定义内部类
}
在这个“类的内部”,包括类中的任何位置,甚至方法体中也可以定义内部类。注意,下面的放在同一个程序文件A.java中的类B不是类A的内部类,这是刚开始接触内部类的程序员比较容易引起混乱的地方:
public class A{
//statements
}
class B{
//statements
}
这两个类虽然写在用一个文件中,但它们之间是两个独立的类,它们之间并没有什么联系。将这个类文件编译之后,会产生两个class文件:A.class和B.class。
可以将一个内部类定义成一个静态的内部类,只需要在内部类定义前面加上static关键字。
为方便表述,我们将封装类相对于“内部类”称为“外部类”。
public class Outer1 {
private int size;
// 定义一个内部类,名为 "Inner"
public class Inner {
public void doStuff() {
// 内部类可以访问外部类的私有属性
size++;
}
}
public void testTheInner() {
Inner i = new Inner();
i.doStuff();
}
}
在这个Outer1类中,定义了一个内部类,这个内部类可以访问外部类的私有属性。
编译这个Outer1.java,将会产生两个class文件:Outer1.clas和Outer1$Inner1.class,前面一个类文件是Outer1类文件,后面一个是内部类类文件,它用“$”来分割开内部类和外部类。
在Outer类范围以外的其他应用中,实例化内部类必须首先有一个外部类实例存在,然后通过外部类实例来实例化内部类,实例化内部类的两种方法:
法一:
Outer.Inner in=new Outer().new Inner();
法二:
Outer o=new Outer();
Outer.Inner I= o.new Inner();
当然,如果内部类是static的,也可以用下面方法:
Outer.Inner in=new Outer.Inner();
我们来看一下,假设我们现在需要在另一个应用程序中使用刚才定义的类Outer1的内部类Inner,我们该如何来实现。为了更好的演示它,我们对上面的Outer类稍作修改,加入了一个用于获得Outer1类的私有属性size的方法getOuterSize():
public class Outer1 {
private int size;
// 定义一个内部类,名为 "Inner"
public class Inner {
public void doStuff() {
// 内部类可以访问外部类的私有属性
size++;
}
public int getOuterSize() {
return size;
}
}
public void testTheInner() {
Inner i = new Inner();
i.doStuff();
}
}
然后我们再来看如何在另外一个类中使用Outer1的内部类:
public class TestInner {
public static void main(String[] args) {
Outer1.Inner oi = new Outer1().new Inner();
oi.doStuff();
System.out.println(oi.getOuterSize());
}
}
编译
并运行这个程序,将在控制台上打印出size的值:1。
和一般的类不同,内部类可以使用protected和private来修饰,以限制在它的外部类以外的地方对它的访问,比如,上面的Outer1中的Inner内部类就可以定义成如下的样子:
public class Outer1 {
private int size;
/* 定义一个内部类,名为 "Inner" */
private class Inner {
public void doStuff() {
// 内部类可以访问外部类的私有属性
size++;
}
public int getOuterSize()
{
return size;
}
}
public void testTheInner() {
Inner i = new Inner();
i.doStuff();
}
}
此时,就不能在Outer1的范围之外使用Inner类了。
2 局部内部类
类的名字只能在定义的范围内使用,除非使用有效的全名。
Inner类也可以定义在方法的内部,或者类的一个自由块中,此时,内部类是一个局部的内部类,只能在方法体或者自由块中使用。
如果内部类被定义在方法中,那么方法中final类型的局部变量,都可以被Inner类的方法访问。定义在方法中的内部类只能在方法内被使用。
例如:
public class Outer {
public void test(int i) {
class LocalClass {
public void localTest() {
System.out.println("局部内部类的方法被调用");
// System.out.println("i="+i); //错
// 如果需要使用局部变量i,必须将它声明为final
}
}
LocalClass lc = new LocalClass();
lc.localTest();
}
public static void main(String[] args) {
Outer o = new Outer();
o.test(1);
}
}
在这个类Outer中,定义了一个方法test(),在它的方法体内定义了一个局部内部类,这个内部类只能在这个方法体内使用。
如果在内部类中需要使用方法的局部变量(如方法的参数i),那么,需要将这个局部变量定义final的,否则,将会出现类似下面的错误(将i++前的注释去掉后的错误):
Outer.java:10: local variable i is accessed from within inner class; needs to be declared final
System.out.println("i="+i);;
^
1 error
如果将方法test()的参数i加上final就可以了。
3 匿名内部类
内部类的另外一个奇特的地方就是,可以不用给它指定一个类名,就可以直接拿来使用,这个用法经常用在图形界面的事件处理中。
4 内部类特性
内部类是一个复杂的应用,除了上面提到的一些特点外,它还有以下一些特点:
Inner class可以声明为抽象类 ,因此可以被其它的内部类继承。也可以声明为final的。
和外层类不同,Inner class可以声明为private或protected。
Inner class 可以声明为static的,但此时就不能再使用外层封装类的非static的成员变量。
非static的内部类中的成员不能声明为static的,只有在顶层类或static的内部类中才可声明static成员。