JAVA学习笔记——内部类

基本概念

内部类 (inner class) 是定义在另一个类中的类。主要特点有:

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
  • 内部类可以对同一个包中的其他类隐藏起来。
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名 (anonymous) 内部类比较便捷。

使用内部类访问对象状态

public class TalkingClock
{
	private int interval:
	private boolean beep;

	public TalkingClock(int interval, boolean beep) { . . . }
	public void start() { . . . }

	public class TimePrinter implements ActionListener
	// an inner class
	{
		public void actionPerformed(ActionEvent event)
		{
			System.out.println("At the tone, the time is " + new Date());
			if (beep) Toolkit.getDefaultToolkit().beep();
		}
	}

	public void start()
	{
		ActionListener listener = new TimePrinter();
		Timer t = new Timer(interval, listener);
		t.start();
	}
}

上例中,首先定义了一个 TalkingClock 类,然后在该类内部定义了一个 TimePrinter 类,TimePrinter 就称为 TalkingClock 的一个内部类。

在内部类 TimePrinter 中只定义了一个方法 actionPerformed,可以发现,在方法内部有一个 beep 变量,它既不是传入参数,也不是方法内部定义的变量,事实上它就是外围类 TalkingClock 的实例域中的 beep内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域(包括私有数据域)。

内部类对象总是有一个隐式引用,它指向了创建它的外围类对象。这个引用在内部类的定义中是不可见的,我们将外围类对象的引用称为 outer,上例中的 beep 等价于 outer.beep

outer

图1 内部类对象拥有一个对外围类对象的引用

外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。因为 TimePrinter 类没有定义构造器,所以编译器为这个类生成了一个默认的构造器:

public TimePrinter(TalkingClock clock) // automatically generated code
{
	outer = clock;
}

注意outer 不是 Java 关键字,而是内部类中的一个特殊机制。

内部类的特殊语法规则

// 使用外围类引用
// OuterClass.this
TimeClock.this

// 内部类构造器
// outerObject.new InnerClass(construction parameters)
ActionListener listener = this.new TimePrinter();
// 此处 this 指向当前外围类对象

// 在外围类作用域外引用内部类
// OuterClass.InnerClass

注意

  1. 内部类中声明的所有静态域都必须是 final。我们希望一个静态域只有一个实例,不过对于每个外部对象,会分别有一个单独的内部类实例。
  2. 内部类不能有 static 方法。

局部内部类

在上例中,内部类 TimePrinter 只在 start 中被使用,这种情况下,可以考虑使用局部内部类

public void start()
{
	class TimePrinter implements ActionListener
	{
		public void actionPerformed(ActionEvent event)
		{
			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();
}

局部类不能用 publicprivate 访问说明符进行声明,它的作用域被限定在声明这个局部类的块中,当 start 方法结束后,这个类也就不存在了。局部类有一个优势,即对外部世界可以完全地隐藏起来。即使 TalkingClock 类中的其他代码也不能访问它。除 start 方法之外,没有任何方法知道 TimePrinter 类的存在。

访问外部变量

与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须事实上为 final。这说明,它们一旦赋值就绝不会改变。

public void start(int interval, boolean beep)
{
	class TimePrinter implements ActionListener
	{
		public void actionPerformed(ActionEvent event)
		{
			Systea.out.println("At the tone, the tiie is " + new Date())if (beep) Toolkit.getDefaultToolkit().beep();
		}
	}

	ActionListener listener = new TimePrinter();
	Timer t = new Timer(interval, listener);
	t.start();
}

上例中,在计时器运行到 if (beep) 时,start 方法已经结束,此时的 beep 参数已不存在,从而产生错误。

为了能够让 actionPerformed 方法工作,TimePrinter 类在 beep 域释放之前将 beep 域用 start 方法的局部变量进行备份。局部类的方法只可以引用定义为 final 的局部变量。鉴于此情况,在列举的示例中,将 beep 参数声明为 final,对它进行初始化后不能够再进行修改。

public void start(int interval, final boolean beep)

匿名内部类

将局部内部类的使用再深人一步。假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类 (anonymous inner class)。

/*
new SuperType(construction parameters)
{
	inner class methods and data
}
*/
public void start(int interval, boolean beep)
{
	ActionListener listener = new ActionListener()
	{
		public void actionPerformed(ActionEvent event)
		{
			System.out.println("At the tone, the time is " + new DateO)if (beep) Toolkit.getDefaultToolkit().beep();
		}
	};
	
	Timer t = new Timer(interval, listener);
	t.start();
}

这种语法的含义是:创建一个实现 ActionListener 接口的类的新对象,需要实现的方法 actionPerformed 定义在 {} 括号内。

由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类 (super class) 构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。不仅如此,还要像下面这样提供一组括号:

new InterfaceType()
{
	methods and data
}

如果构造参数的闭小括号后面跟一个开大括号,正在定义的就是匿名内部类。多年来,Java 程序员习惯的做法是用匿名内部类实现事件监听器和其他回调。如今最好还是使用 lambda 表达式。

// 此程序内部类访问 beep 仍要添加 final 修饰
public void start(int interval, final 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();
}

静态内部类

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为 static,以便取消产生的引用。

class ArrayAlg
{
	public static class Pair
	{
		private double first;
		private double second;
		
		public Pair(double f, double s)
		{
			first = f;
			second = s;
		}
		
		public static Pair(double[] array)
		{
			double min = Double.POSITIVE_INFINITY;
			double max = Double.NECATIVE.INFINITY;
			for (double v : array)
			{
				if (min > v) min = v;
				if (max < v) max = v;
			}
			return new Pair(min, max)
		}
		
		public double getFirst() { return first; }
		
		public double getSecond() { return second; }
	}
	...
}

上例中,在类 ArrayAlg 中定义了一个静态公有内部类 Pair,在外部可以通过 ArrayAlg.Pair 来访问它。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所冇内部类完全一样。而此例中,必须使用静态内部类,这是由于内部类对象是在静态方法中构造的。

注释

  • 在内部类不需要访问外围类对象的时候,应该使用静态内部类。有些程序员用嵌套类 (nested class) 表示静态内部类。
  • 与常规内部类不同,静态内部类可以有静态域和方法。
  • 声明在接口中的内部类自动成为 staticpublic 类。

参考资料

  1. 《Java核心技术 卷1 基础知识》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值