本文共 7758 字,大约阅读时间需要 25 分钟。
内部类(inner class)是定义在另一个类中的类。
使用内部类的主要原因: (1)内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。 (2)内部类可以对同一个包中的其它类隐藏起来。 (3)当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。 1.使用内部类访问对象状态 内部类既可以访问自身的属性,也可以访问创建它的外围对象的属性。 内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。这个引用在内部类的定义中是不可见的。 只有内部类可以声明为似有的,这样就只有它的外部类对象才能够生成内部类对象。常规类只可以具有包可见性或共有可见性。import java.awt.Toolkit;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.util.Date;import javax.swing.JOptionPane;import javax.swing.Timer;public class InnerClassTest { public static void main(String[] args) { TalkingClock clock = new TalkingClock(1000, true); clock.start(); JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); }}class TalkingClock{ private int interval; private boolean beep; public TalkingClock(int interval, boolean beep){ this.interval = interval; this.beep = beep; } public void start(){ ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); } private class TimePrinter implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if(beep){ Toolkit.getDefaultToolkit().beep(); } } }}
2.内部类的特殊语法规则
(1)内部类对外围类的引用表达式: OuterClass.this 例public void actionPerformed(ActionEvent e) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if(TalkingClock.this.beep){ Toolkit.getDefaultToolkit().beep(); } }
(3)反过来,可以采用下列语法格式更加明确的编写内部对象的构造器: outerObject.new InnerClass(construction parameters); 例
ActionListener listener = this.new TimePrinter();
在这里,最新构造的TimePrinter对象的外围类引用被设置为创建内部类对象的方法中的this引用。 通常,this限定词是多余的。不过可以通过显式的命名将外围类引用设置为其他的对象。 例如,如果TimePrinter是一个共有内部类,对于任何的语音始终都可以构造一个TimePrinter:
TalkingClock jabberer = new TalkingClock(1000, true); TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
或
TimePrinter listener = new TalkingClock(1000, true).new TimePrinter();
(4)在外围类的作用域之外,可以这样引用内部类: OutClass.InnerClass(外部类名.内部类名) 3.内部类是否有用、必要和安全 (1)内部类是一种编译器现象,与虚拟机无关 编译器将会把内部类编译成用$(美元符号)分割外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。 例如,在TalkingClock类的内部的TimePrinter类将被翻译成类文件TalkingClock$TimePrinter.class 通过反射,或通过命令(javap -private ClassName)可以看到编译器为了引用外围类,生成了一个附件的属性this$0(名字this$0是由编译器合成的,在自己编写的代码中不能够引用它),并在构造器中传入了外围类的参数。 例:通过javap命令查看内部类 \>javap -private TalkingClock$TimePrinter Compiled from "InnerClassTest.java" class TalkingClock$TimePrinter extends java.lang.Object implements java.awt.event.ActionListener{ final TalkingClock this$0; TalkingClock$TimePrinter(TalkingClock); public void actionPerformed(java.awt.event.ActionEvent); } (2)因为内部类可以访问外围类的私有数据,比如私有属性,私有方法等访问特权,所以与常规类比较起来功能更加强大。 (3)内部类如何管理那些额外的访问特权 通过反射或javap来查看TalkingClock类 \>javap -private TalkingClock Compiled from "InnerClassTest.java" class TalkingClock extends java.lang.Object{ private int interval; private boolean beep; public com.shaogq.inclass.innter.TalkingClock(int, boolean); public void start() throws java.lang.Exception; static boolean access$0(TalkingClock); } 发现编译器在外围类添加静态方法access$0(如果内部类调用另一个属性,将再生成静态方法access$1,以此类推)将返回座位参数传递给它的属性beep。 内部类方法将会调用生成的这个静态方法 if(beep) 将会提高下列类调用的效率 if(access$0(outer)); (4)是否存在风险,因为任何人都可以通过调用access$0方法很容易地读取到私有属性beep,虽然access$0不是Java的合法方法名,但熟悉类文件结果的情况下,使用十六进制表一起可以创建一个虚拟机指令调用那个方法的类文件。由于隐藏地访问方法需要有用包可见性,所以攻击代码需要与被攻击类放到同一个包中。 总而言之,如果内部类访问了私有数据域,就有可能通过附加在外围类所在包中的其他类访问他们。但必须可以的构建或修改类文件才可能达到这个目的。 4. 局部内部类 在上例中,TimePrinter这个类名字只在start方法中构建这个类型的对象时使用了一次,当遇到这类情况时,可以在一个方法中定义局部类。
public void start() throws Exception{ class TimePrinter implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if(TalkingClock.this.beep){ Toolkit.getDefaultToolkit().beep(); } } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); }
局部类不能用 public 或 private 访问说明符进行声明。它的作用域被限定在声明这个局部类的快中。
局部类有一个优势,即对外部世界可以完全隐藏起来。即使TalkingClock类中的其他代码也不能访问它。除了start方法外,没有任何方法指导TimePrinter类的存在。 5. 由内部方法方位final变量 与其他内部类相比较,局部内部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须被声明为final。 例:public void start() throws Exception{ class TimePrinter implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if(TalkingClock.this.beep){ Toolkit.getDefaultToolkit().beep(); } } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); }
上例中的控制流程
1)调用start方法。 2)调用内部类TimePrinter的构造器,以便初始化对象变量listener。 3)将listener引用传递给Timer构造器,定时器开始计时,start方法结束,此时,start方法的beep参数变量不复存在。 4) 然后,actionPerformed方法执行if(beep)... 为了能够让actionPerformed方法工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。 在例子中,编译器为局部边内部类构造了名字TilkingClock$TimePrinter(也可能类似于TalkingClock$1TimePrinter这种名字)。查看TilkingClock$TimePrinter类,结果如下 :\>javap -private TalkingClock$1TimePrinter Compiled from "TalkingClock.java" class TalkingClock$1TimePrinter extends java.lang.Object implements java.awt.event.ActionListener{ final innterclass.TalkingClock this$0; private final boolean val$beep; innterclass.TalkingClock$1TimePrinter(innterclass.TalkingClock, boolean); public void actionPerformed(java.awt.event.ActionEvent); } 构造器的boolean参数和val$beep实例变量,当创建一个对象的时候,beep就会被传递给构造器,并存储在val$beep域中。编译器必须检测对局部变量的访问,为每一个变量建立相应的属性,并将局部变量拷贝到构造器中,以便将这些属性初始化为局部变量的副本。 从程序员的角度看,局部变量的访问非常容易。它减少了需要显示编写的属性,从而使内部类更加简单。 注意,局部类的方法只可以引用定义为final的局部变量,比如在上例中beep参数声明为final,对它进行初始化后不能够在进行修改。因此,就使得局部变量与在局部类内建立的拷贝一致。 在定义final变量的时候,不必进行初始化。定义时没有初始化的final变量通常称为空final(blank final)变量。 如果需要修改局部变量的值,可以通过使用一个长度为1的数组,数组变量被声明为final,但是这仅仅表示不可以让它引用另外一个数组,数组中的数据元素可以自由地变更。 6. 匿名内部类 (1)将局部内部类的使用再深入一步,只创建这个类的一个对象,而且不需要命名。这种类被称为匿名内部类(anonymous inner class)。 例public void start(int interval, final boolean beep){ ActionListener listener = new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if(beep){ Toolkit.getDefaultToolkit().beep(); } } }; Timer t = new Timer(interval, listener); t.start(); }它的含义是:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。 (2)用于构造对象的任何参数都要被放在超类后面的括号{}内,通常的语法格式为:
new SuperType(construction parameters){ inner class methods and data }
其中,SuperType可以是ActionListener这样的接口,于是内部类就要实现这个接口。SuperType也可以是一个类,于是内部类就要扩展它。
(3)由于构造器的名字必须与类名相同,而匿名内部类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类(superclass)构造器,尤其是在内部类实现接口的时候,不能有任何构造参数。不仅如此,还要向下面这样提供一组括号new InterFaceType(){ methods and data }
(4)构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象之间的差别
Person queen = new Person("Mary"); Person count = new Person("Dracula"){...};
如果构造参数的小括号跟一个大括号,正在定义的就是匿名内部类。
如果内部类的代码比较简单,只有几行简单的代码,匿名内部类就可以节省一些录入的时间。 7. 静态内部类 有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类的对象。为此,可以将内部类声明为static,以便取消产生的引用。 只有内部类可以声明为static,静态内部类对象中不需要引用任何其他的对象,静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。 声明在接口中的内部类自动成为static和public的。 DEMO:在下面的例子中,必须使用静态内部类,是由于内部类对象是在静态方法中构造的。如果没有将Pair类声明为static,那么编译器将会给出错误报告:没有可用的隐式ArrayAlg类型对象初始化内部类对象。public class StaticInnerClassTest { public static void main(String[] args) { double[] d = new double[20]; for(int i=0;i
转载地址:http://woyci.baihongyu.com/