在类别中您还可以定义类别,称之为内部类别(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"的情况较多,下面这个程序简单示范成员内部类别的使用:
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类别的存在,例如:
public class UseInnerClass { public static void main(String[] args) { OutClass out = new OutClass(10); out.showPoints(); } }
区域内部类别的使用与成员内部类别类似,区域内部类别定义于一个方法中,类别的可视范围与生成之对象仅止于该方法之中,区域内部类别的应用一般较为少见。
内部匿名类别可以不宣告类别名称,而使用new直接产生一个对象,该对象可以是继承某个类别或是实作某个接口,内部匿名类别的宣告方式如下:
new [类别或接口()] { // 实作 }
一个使用内部匿名类别的例子如下所示,您直接继承Object类别来生成一个对象,并改写其toString()方法:
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()中要使用某个内部类别时,例如:
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.....,看它是外部类别中的第几个匿名类别。