Java学习笔记(4):接口与内部类

接口
接口是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
如果类遵从某个特定接口,那么就履行这项服务。
如,Array类中的sort方法承诺可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了Comparable接口:
public interface Comparable
{
	int compareTo(Object other);
}
任何实现Comparable接口的类都需要包含compareTo方法。

注:Java SE 5.0中,Comparable接口已经改进为泛型类型:
public interface Comparable<T>
{
	int compareTo(T other);
}

接口中的方法自动地属于public。

接口中可以包含多个方法,可以定义常量,但不能含有实例域,不能在接口中实现方法。

为了让类实现一个接口,通常需要两个步骤:
1. 将类声明为实现给定的接口
2. 对接口中的所有方法进行定义
将类声明为实现某个接口,需使用关键字implements:
class Employee implements Comparable
Employee类需要提供compareTo方法:
public int compareTo(Object otherObject)
{
	Employee other = (Employee)otherObject;
	if (salary < other.salary) return -1;
	if (salary > other.salary) return 1;
	return 0;
}

注:接口声明中,可不将compareTo方法声明为public,但在实现接口中,必须声明为public。

接口的特性
接口不是类,不能使用new运算符实例化:
x = new Comparable(...) // ERROR
但是能声明接口的变量:
Comparable x; // OK
接口变量必须引用实现了接口的类对象:
x = new Employee(...); // OK provided Employee implements Comparable
可以使用instanceof检查一个对象是否实现了某个特定的接口:
if (anObject instanceof Comparable) {...}
与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链。例如,假设有一个称为Moveable的接口:
public interface Moveable
{
	void move(double x, double y);
}
然后,可以以它为基础扩展一个叫作Powered的接口:
public interface Powered extends Moveable
{
	double milesPerGallon();
}
接口中不能包含实例域或静态方法,但可以包含常量:
public interface Powered extends Moveable
{
	double milesPerGallon();
	double SPEED_LIMIT = 95; // a public static final constant
}
与接口中的方法自动被设置为public一样,接口中的域被自动设为public static final。
一个类可以实现多个接口:
class Employee implements Cloneable, Comparable

对象克隆
当拷贝一个变量时,原始变量与拷贝变量引用同一个对象:
Employee original = new Employee("John Public", 50000);
Employee copy = original;
copy.raiseSalary(10); // oops--also changed original
如果需要创建一个独立的copy,需使用clone方法:
Employee copy = original.clone();
copy.raiseSalary(10); // OK--original unchanged
但是,clone方法是Object类的一个protected方法,即,用户编写的代码中不能直接调用它。只有Employee类才能够克隆Employee对象。
默认的克隆操作是浅拷贝,它并没有克隆包含在对象中的内部对象。如果原是对象与克隆对象的子对象可变,则必须重新定义clone方法,以便实现克隆子对象的深拷贝。
因此,对于每一个类,都需要做出下列判断:
1. 默认的clone方法是否满足要求
2. 默认的clone方法是否能够通过调用可变子对象的clone得到修补
3. 是否不应该使用clone
如果选择1或2,类必须:
1. 实现Cloneable接口
2. 使用public访问修饰符重新定义clone方法
即使clone的默认实现(浅拷贝)能够满足要求,也应该实现Cloneable接口,将clone重定义为public,并调用super.clone()。例:
class Employee implements Cloneable
{
	public Employee clone() throws CloneNotSupportedException
	{
		return (Employee)super.clone();
	}
	...
}
为了实现深拷贝,必须克隆所有可变的实例域,例:
class Employee implements Cloneable
{
	...
	public Employee clone() CloneNotSupportedException
	{
		Employee cloned = (Employee)super.clone();

		cloned.hireDay = (Date)hireDay.clone();

		return cloned; 
	}
}
必须谨慎地实现子类的克隆。一旦Employee类定义了clone方法,任何人都可以利用它克隆Manager对象。但是,在Manager类中有可能存在一些需要深拷贝的域,或者包含一些没有实现Cloneable接口的域。没有人能够保证子类实现的clone一定正确。因此,应该将Object类中的clone方法声明为protected。但是,如果想让用户调用clone方法,就不能这样做。
注:所有的数组类型均包含一个clone方法,这个方法被设为public:
int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 };
int[] cloned = (int[])luckyNumbers.clone();
cloned[5] = 12; // doesn't change luckyNumbers[5]

接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。
在java.swing包中有一个Timer类,可以使用它在到达给定时间间隔时发出通告。例如,假如程序中有一个时钟,就可以请求每秒钟获得一个通告,以便更新时钟的画面。
在构造定时器时,需要设置一个时间间隔,并告之定时器,当到达时间间隔时需要做些什么操作。
如何告之定时器做什么?Java标准类库中的类将某个类的对象传递给定时器,然后,定时器调用这个对象的方法。
定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现了java.swing.event包的ActionListener接口。下面是这个接口:
public interface ActionListener
{
	void actionPerformed(ActionEvent event);
}
当到达指定的时间间隔时,定时器就调用actionPerformed方法。
假设希望每隔10秒钟打印一条信息“At the tone, the time is ...”,然后响一声,就应该定义一个实现ActionListener接口的类,然后将需要执行的语句放在actionPerformed方法中:
class TimePrinter implements ActionListener
{
	public void actionPerformed(ActionEvent event)
	{
		Date now = new Date();
		System.out.println("At the tone, the time is " + now);
		Toolkit.getDefaultToolkit().beep();
	}
}
注意ActionEvent参数,这个参数提供了事件的相关信息,例如,产生这个事件的源对象。
接下来,构造这个类的一个对象,并将它传递给Timer构造器:
ActionListener listener = new TimePrinter();
Timer t = new Timer(10000, listener);
Timer构造器的第一个参数是发出通告的时间间隔(单位:毫秒),第二个参数是监听器对象。
最后,启动定时器:
t.start();


内部类

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

使用内部类访问对象状态
进一步分析TimerTest示例,并抽象出一个TalkingClock类。构造一个语音时钟需要提供两个参数:发布通告的间隔和开关铃声的标志:
public class TalkingClock
{
	public TalkingClock(int interval, boolean beep) {...}
	public void start() {...}

	private int interval;
	private boolean beep;

	public class TimerPrinter implements ActionListener
		// an inner class
	{
		...
	}
}
注意,TimePrinter类是TalkingClock的内部类不代表每个TalkingClock都有一个TimePrinter实例域。
下面是TimePrinter类的详细内容:
private class TimePrinterimplements ActionListener
{
	public void actionPerformed(ActionEvent event)
	{
		Date now = new Date();
		System.out.println("At the tone, the time is " + now);
		if (beep) Toolkit.getDefaultToolkit.beep();
	}
}
注意,beep引用了创建TimePrinter的TalkingClock对象的域。
为了能够运行这个程序,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。
外围类的引用在构造器中设置,编译器修改了所有内部类的构造器,添加一个外围类引用的参数。

内部类的特殊语法规则
使用外围类引用的正规语法是:
OuterClass.this
例如,可以这样编写TimePrinter内部类的actionPerformed方法:
public void actionPerformed(ActionEvent event)
{
	...
	if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}
反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:
outerObject.new InnerClass(construction parameters)
例如:
ActionListener listener = this.new TimePrinter();
在这里,最新构造的TimePrinter对象的外围类引用被设置为创建内部类对象方法中的this引用。通常,this限定词是多余的。
不过,可以通过显式地命名将外围类引用设置为其他的对象,例如,如果TimerPrinter是一个公有内部类,对于任意的语音时钟都可以构造一个TimePrinter:
TalkingClock jabberer = new TalkingClock(1000, true);
TalkingClock.TimePrinter listener = jabberer.new TimePrinter();
需要注意,在外围类的作用域之外,可以这样引用内部类:
OuterClass.InnerClass

局部内部类
在TalkingClock示例代码中,TimePrinter这个类名字只在start方法中创建这个类型的对象时使用了一次。
当遇到这类情况时,可以在一个方法中定义局部类:
public void start()
{
	class TimePrinter implements ActionListener
	{
		public void actionPerformed(ActionEvent event)
		{
			Date now = new Date();
			System.out.println("At the tone, the time is " + now);
			if (beep) Toolkit.getDefaultToolkit().beep();
		}
	}
	
	ActionListener listener = new TimePrinter();
	Timer t = new Timer(interval, listener);
	t.start();
}
局部类不能用public或private访问说明符进行说明,它的作用域被限定在声明这个局部类的块中。
除了start方法外,没有任何方法知道TimePrinter类的存在。

由外部方法访问final变量
局部类不仅能够访问包含它们的外部类,还可以访问被声明为final的局部变量,例:
public void start(int interval, final boolean beep)
{
	class TimePrinter implements ActionListener
	{
		public void actionPerformed(ActionEvent event)
		{
			Date now = new Date();
			System.out.println("At the tone, the time is " + now);
			if (beep) Toolkit.getDefaultToolkit().beep();
		}
	}
	
	ActionListener listener = new TimePrinter();
	Time t = new Timer(interval, listener);
	t.start();
}
将beep参数声明为final,对它进行初始化后不能够再进行修改。因此,就时的局部变量与在局部类内建立的拷贝保持一致。



匿名内部类
假如只创建局部内部类的一个对象,就不必命名了:
public void start(int interval, final boolean beep)
{
	ActionListener listener = new ActionListener()
		{
			public void actionPerformed(ActionEvent event)
			{
				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定义在括号{}内。
用于构造对象的任何参数都要被放在超类名后面的括号()内。通常的语法格式为:
new SuperType(construction parameters)
	{
		inner class methods and data
	}
其中,SuperType可以是ActionListener这样的接口,也可以是一个类。
匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。

静态内部类
有时候,使用内部类只是为了把一个类隐藏在另一个类内部,并不需要内部类引用外围类对象,为此,可将内部类声明为static,以便取消产生的引用。
例:考虑计算数组中最小值和最大值的问题。可以编写两个方法,一个方法计算最小值,一个方法计算最大值。在调用这两个方法时,数组被遍历两次,如果只遍历数组一次,并同时计算出最小值和最大值,就可以大大提高效率:
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
for (double v : values)
{
	if (min > v) min = v;
	if (min < v) max = v;
}
这个方法必须返回两个数值,为此可以定义一个包含两个值的类Pair:
class Pair
{
	public Pair(double f, double s)
	{
		first = f;
		second = s;
	}
	public double getFirst() { return first; }
	public double getSecond() { return second; }
	
	private double first;
	private double second;
}
minmax方法可以返回一个Pair类型的对象:
class ArrayAlg
{
	public static Pair minmax(double[] values)
	{
		...
		return new Pair(min, max);
	}
}
Pair是一个十分大众化的名字,极易产生名字冲突。解决这个问题的办法是将Pair定义为ArrayAlg的内部公有类。此后,通过Array.Alg访问它:
ArrayAlg.Pair p = ArrayAlg.minmax(d);
与前面例子所使用的内部类不同,在Pair对象中不需要引用任何其他的对象,为此,可以将这个内部类声明为static:
class ArrayAlg
{
	public static class Pair
	{
		...
	}
	...
}
只有内部类可以声明为static。静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。
在上面的示例中,必须使用静态内部类,这是由于内部类对象是在静态方法中构造的:
public static Pair minmax(double[] d)
{
	...
	return new Pair(min, max);
}
如果没有将Pair类声明为static,编译器会报错。
注:声明在接口中的内部类自动成为static和public。

猜你喜欢

转载自blog.csdn.net/angel_lys/article/details/8587862