八、内部类
内部类(inner class)定义
内部类是定义在另一个类中的类。
需要内部类的原因:
- 内部类方法可以访问该类定义所在的域中的数据,包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来。
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
四种内部类:
- 普通内部类
class TalkClock {
private int interval;
private boolean beep;
public TalkClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
}
public void start() {
ActionListener listener = new TimePrinter();
Timer timer = new Timer(interval, listener);
timer.start();
}
public 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();
}
}
}
}
示例中是最简单的内部类,定义在另一个类内部的类。
由类TimePrinter的方法体,可以看到内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域(外围类TalkClock的域beep),此处可访问的包括private域和静态域。
因为内部类的对象总有一个隐式引用,指向了创建它的外部类对象。
这个引用再内部类的定义中是不可见的,此处为了便于理解,将外围类对象的引用称为, actionPerformed() 等价于:
public class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if(outer.beep){
Toolkit.getDefaultToolkit().beep();
}
}
}
外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加了一个外围类引用的参数。此处因为TimePrinter类没有定义构造器,所以编译器为这个类生成了一个默认的构造器:
public TimePrinter(TalkClock clock){
outer = clock;
}
再次强调不是Java的关键字,只是为了便于理解。
内部类的语法规则
上面虚拟了内部类的一个外围类的引用outer,而使用外围类引用的正规语法如下:
OuterClass.this
如上面actionPerformed方法的如下编写:
public class TimePrinter implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if(TalkClock.this.beep){
Toolkit.getDefaultToolkit().beep();
}
}
}
反过来,可以明确的编写内部对象的构造器:
outerObject.new InnerClass(参数);
当普通内部类拥有和外围类同名的域或者方法时,会发生隐藏现象,即默认情况下访问的是内部类的域。如果要访问外围类的同名域,需要以下面的形式进行访问:
外部类.this.域名
外部类.this.方法
内部类可以无条件地访问外围类的域或方法,而外围类中如果要访问内部类的域或方法,必须先创建一个内部类的对象,再通过指向这个对象的引用来进行访问。
class TalkClock {
private int interval;
private boolean beep;
public TalkClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
getTimePrinterInstance().printTest();
}
private TimePrinter getTimePrinterInstance() {
return new TimePrinter();
}
public 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();
}
}
public void printTest() {
System.out.println(interval);
}
}
}
内部类是依附外围类而存在的,也就是说,如果要创建内部类的对象,前提是必须存在一个外围类的对象。创建内部类对象的一般方式如下:
TalkClock clock = new TalkClock(1000, true);
//第一种方式:
TalkClock.TimePrinter inner = clock.new TimePrinter();
//第二种方式:
TalkClock.TimePrinter inner1 = clock.getTimePrinterInstance();
- 局部内部类
如上面普通内部类的示例代码中,TimePrinter类名只在start方法中创建这个类型时使用了一次,所以可以改造成在一个方法中定义局部内部类。
class TalkClock {
private int interval;
private boolean beep;
public TalkClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
}
public void start() {
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 timer = new Timer(interval, listener);
timer.start();
}
}
局部内部类是定义在一个方法或者一个作用域里面的类,局部内部类不能使用private或者public访问说明符进行声明,它和普通内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部类有一个优势,对外部世界可以完全的隐藏起来,即使是TalkClock类中的其他代码也不能访问它,除了start方法以外,没有任何方法知道TimePrinter的存在。
- 匿名内部类
将局部内部类的使用再深入一步,假如只创建这个类的一个对象,就不必命名了,这种类被称为匿名内部类。
class TalkClock {
private int interval;
private boolean beep;
public TalkClock(int interval, boolean beep) {
this.interval = interval;
this.beep = beep;
}
public void start() {
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("At the tone, the time is " + new Date());
if (beep) {
Toolkit.getDefaultToolkit().beep();
}
}
}
Timer timer = new Timer(interval, listener);
timer.start();
}
}
匿名内部类的语法格式:
new SuperType(参数) {
inner class method and data;
}
其中SuperType可以是接口,那么内部类就需要实现这个接口;也可以是一个类,那么内部类就要扩展它。
构造一个类的新对象,与构造一个扩展了该类的匿名内部类的区别:
//构造了一个Penson对象
Person queen = new Person("Mary");
//构造了匿名内部类对象
Person count = new Penson("Dracula"){
方法语句;
...
}
再参数的闭小括号后跟一个开大括号,正在定义的就是匿名内部类,匿名内部类不能有修饰符。
- 静态内部类
有时候使用内部类只是为了把一个类隐藏再另外一个类的内部,并不需要内部类引用外围类的对象,此时就可以将内部类声明为static,以便取消产生的引用。
- 在内部类不需要访问外围类对象的时候,应该使用内部类。
- 与常规内部类不同,静态内部类可以有静态域和方法。
- 声明在接口中的内部类自动称为static和public类。
对于普通内部类,必须先产生外围类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外围类的实例化对象即可产生内部类的实例化对象。
- 创建成员内部类对象的一般形式为:
外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名();
- 创建静态内部类对象的一般形式为:
外围类类名.内部类类名 xxx = new 外部类类名.内部类类名();