大可制作:QQ群:31564239(asp|jsp|php|mysql)

Java Gossip: 内部类(Inner class)

在类中您还可以定义类,称之为内部类(Inner class)“巢状类”(Nested class)。非"static"的内部类可以分为三种:成员内部类(Member inner class)区域内部类(Local inner class)匿名内部类(Anonymous inner class)

使用内部类的好处在于可以直接存取外部类的私用(private)成员,举个例子来说,在视窗程序中,您可以使用内部类来实现一个事件倾听者类,这个视窗倾听者类可以直接存取视窗组件,而不用透过参数传递。

另一个好处是,当某个Slave类完全只服务于一个Master类时,我们可以将之设定为内部类,如此使用Master类的人就不用知道 Slave的存在。

成员内部类是直接声明类为成员,例如:
public class OuterClass {
    // ....
 
    // 内部类
    private class InnerClass {
        // ....
    }
}
 
内部类同样也可以使用"public"、"protected"或"private"来修饰,通常声明为"private"的情况较多,下面这个程序简单示范成员内部类的使用:

  • OutClass.java
public class OutClass { 
// 内部类
private class Point {
private int x, y;

public Point() {
x = 0; y = 0;
}

public void setPoint(int x, int y) {
this.x = x;
this.y = y;
}

public int getX() {
return x;
}

public int getY() {
return y;
}
}

private Point[] points;

public OutClass(int length) {
points = new Point[length];

for(int i = 0; i < points.length; i++) {
points[i] = new Point();
points[i].setPoint(i*5, i*5);
}
}

public void showPoints() {
for(int i = 0; i < points.length; i++) {
System.out.printf("Point[%d]: x = %d, y = %d%n",
i, points[i].getX(), points[i].getY());
}
}
}

上面的程序假设Point类只服务于OutClass类,所以使用OutClass时,不必知道Point类的存在,例如:
  • UseInnerClass.java
public class UseInnerClass { 
public static void main(String[] args) {
OutClass out = new OutClass(10);

out.showPoints();
}
}

区域内部类的使用与成员内部类类似,区域内部类定义于一个方法中,类的可视范围与生成之对象仅止于该方法之中,区域内部类的应用一般较为少见。

内部匿名类可以不声明类名称,而使用new直接产生一个对象,该对象可以是继承某个类或是实现某个接口,内部匿名类的声明方式如下:
new [类或接口()] {
// 实现
}

一个使用内部匿名类的例子如下所示,您直接继承Object类来生成一个对象,并改写其toString()方法:

  • UseInnerClass.java
public class UseInnerClass { 
public static void main(String[] args) {
Object obj = new Object() {
public String toString() {
return "匿名类对象";
}
};
System.out.println(obj.toString());
}
}

执行结果:
匿名类对象

注意如果要在内部匿名类中使用某个方法中的变量,它必须声明为"final",例如下面是无法通过编译的:
 ....
    public void someMethod() {
        int x = 10;
        Object obj = new Object() {
                             public String toString() {
                                 return "" + x;
                             }
                         };
        System.out.println(obj.toString());
    }
 

编译器会回报以下的错误:
local variable x is accessed from within inner class; needs to be declared final

您要在 x 声明时加上final才可以通过编译:
....
    public void someMethod() {
        final int x = 10;
        Object obj = new Object() {
                             public String toString() {
                                 return "" + x;
                             }
                         };
        System.out.println(obj.toString());
    }
 
究其原因,在于 区域变量 x 并不是真正被拿来于内部匿名类中使用,而是在内部匿名类中复制一份,作为field成员来使用,由于是复本,即便您在内部匿名类中对 x 作了修改,会不会影响真正的区域变量 x,事实上您也通不过编译器的检查,因为编译器要求您加上"final"关键字,这样您就知道您不能在内部匿名类中改变 x 的值。

内部类还可以被声明为"static",不过由于是"static",它不能存取外部类的方法,而必须透过外部类所生成的对象来进行调用,一般来说较少使 用,一种情况是在main()中要使用某个内部类时,例如:

  • UseInnerClass.java
public class UseInnerClass { 
private static class Point {
private int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}

public int getX() {
return x;
}

public int getY() {
return y;
}
}

public static void main(String[] args) {
Point p = new Point(10, 20);

System.out.printf("x = %d, y = %d%n",
p.getX(), p.getY());
}
}

由于main()方法是"static",为了要能使用Point类,该类也必须被声明为"static"。

被声明为static的内部类,事实上也可以看作是另一种名称空间的管理方式,例如:
public class Outer {
    public static class Inner {
        ....
    }
    ....
}

您可以如以下的方式来使用Inner类:
Outer.Inner inner = new Outer.Inner();

在文件管理方面,内部类在编译完成之后,所产生的文件名称为“外部类名称$内部类名称.class”,而内部匿名类则在编译完成之后产生“外部类名称$编号.class”,编号为1、2、3.....,看它是外部类中的第几个匿名类。