ITEEDU

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.....,看它是外部类别中的第几个匿名类别。