内部类TimePrinter中访问了外部类TalkingClock对象中的beep变量,这里实际上可以表示为;
TalkingClock.this.beep在内部类中存在一个对外围类的引用outer(TalkingClock.this),同样,外围类的作用域之外,可以通过OuterClass.InnerClass引用内部类。
内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成用$符号分割外部类名与内部类名的常规类文件,而虚拟机不知道这些。这一点可以通过反射来测试:4.5 通过反射打印类的信息。 编译器为了让内部类引用外围类,生成了一个附加实例域tihis$0,同时还能看到构造器中的TalkingClock参数。通过this$0可以访问外围类的私有数据。同时编译器根据内部类对外围类变量的引用,在外围类添加静态方法,使用上面的反射程序查看TalkingClock的类信息:
这里编译器添加了access$000静态方法,返回布尔类型值,即对应了原程序中内部类对外围类beep变量的引用。这样做其实有一定的风险,beep变量在TalkingClock中是私有变量,而内部类TimePrinter类是公有状态,可以在类外访问,这样任何人都可以调用access$000方法轻易地读取到私有域beep。
上面示例中,TimePrinter类仅在start方法中创建listener对象时使用了一次,对于这种情况,可以将TimePrinter定义为局部内部类,将其作用域限定在start方法中。
局部类声明在某个块中,不能用public或private访问说明符进行声明,它的作用域声明在这个局部类的块中。局部类的优势在于,可以对外界完全隐藏起来,除了start方法之外,没有任何方法知道TimePrinter类的存在:
public void start(int interval, boolean beep) { class TimePrinter implements ActionListener { @Override public void actionPerformed(ActionEvent e) { System.out.println("At the tone, the time is " + new Date()); if (beep) Toolkit.getDefaultToolkit().beep(); } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval,listener); t.start(); }这里将interval和beep变量从构造器参数移到start方法参数中,这样局部内部类访问的就不是内部类的变量,而是start方法参数引用的局部变量,为了能够让actionPerformed方法工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。通过上面的反射程序再次查看TimePrinter类的信息: 可以看到,编译器在TimePrinter类中生成了一个val$beep变量,并且在构造器参数中有一个布尔类型参数,当创建一个TimePrinter对象时,beep就会被传递给构造器,并存储在val$beep域中。注意,局部变量引用到内部类后被声明为final类型,初始化后便不能修改。
假如只创建内部类的一个对象,就可以不用命名了,这种类被称为匿名内部类(anonymous inner class),即创建一个实现ActionListener接口的新对象,需要实现的方法定义在中括号{}内:
public void start(int interval, boolean beep) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("At the tone, the time is " + new Date()); if (beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(interval,listener); t.start(); }由于没有类名,所以匿名类不能有构造器,取而代之的是,将构造器参数传递给超类构造器。匿名内部类一般用于实现事件监听器和其他回调,这一点目前主要改为使用lambda表达式实现。
public void start(int interval, boolean beep) { Timer t = new Timer(interval, event -> { System.out.println("At the tone, the time is " + new Date()); if (beep) Toolkit.getDefaultToolkit().beep(); }); t.start(); }当需要构造一个只使用一次的列表,可以将其作为一个匿名列表,采用双括号初始化(double brace initialization)方法:
ArrayList<String> friends = new ArrayList<>(); friends.add("Harry"); friends.add("Tony"); invite(friends); //double brace initialization invite(new ArrayList<String>() {{add("Harry");add("Tony");}});外层括号建立了ArrayList的一个匿名子类,内层括号则是一个对象构造块。
有时,使用内部类只是为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。下面的例子中,minmax函数需要同时返回数组中的最小值和最大值,为此需要定义Pair类描述这种返回类型,为了防止产生名字冲突,将其定义为ArrayAlg的内部公有类,通过ArrayAlg.Pair访问它:
public class StaticInnerClassTest { public static void main(String[] args) { double[] d = new double[20]; for (int i = 0; i < 20; i++) { d[i] = 100 * Math.random(); } ArrayAlg.Pair p = ArrayAlg.minmax(d); System.out.println("min = " + p.getFirst()); System.out.println("max = " + p.getSecond()); } } class ArrayAlg { /** * A pair of floating-point numbers */ public static class Pair { private double first; private double second; /** * 构造一对浮点数 * @param f first number * @param s second number */ public Pair(double f,double s) { first = f; second = s; } /** * 返回第一个数字 * @return first number */ public double getFirst() { return first; } /** * 返回第二个数组 * @return second number */ public double getSecond() { return second; } } /** * 同时计算数组的最小值和最大值 */ public static Pair minmax(double[] values) { double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; for (double v : values) { min = Math.min(min, v); max = Math.max(max, v); } return new Pair(min,max); } }只有内部类可以声明为static,与常规内部类不同,只有静态内部类可以用于静态域和静态方法,而声明在接口中的内部类自动成为static和public类。