黑马程序员_内部类

-------android培训、java培训、期待与您交流! ----------

接口与回调

      回调(callback)是一个常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。

      在java.swing包中有一个Timer类,可以使用它在到达给定的时间间隔时发出通告。

      在构造定时器时,需要设置一个时间间隔,并告知定时器,当到达时间间隔时需要做什么操作。

      在Java标准类库中的类采用的是面对对象方法,它将某个类的对象传递给定时器,然后,定时器调用这个对象的方法,对象可以携带一些附加的信息。

      当然,定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现java.awt.event包中的ActionListener接口:

public interface ActionListener
{
	void actionPerformed(ActionEvent event);
}

当到达指定的时间间隔时,定时器就调用actionPerformed方法。

接下来,构造这个类的一个对象,并将它传递给Timer构造器。

ActionListener listener = newTimePrinter();
Timer t = new Timer(10000,listener);

Timer构造器的第一个参数是发出通告的时间间隔,它的单位是毫秒。第二个参数是监听器对象。

      最后,启动定时器:

t.start();

PS:这个程序除了导入导入javax.swing.*和java.util.*外,还可以通过类名导入javax.swing.Timer。这就消除了javax.swing.Timer与java.util.Timer之间产生的二义性。

 

内部类

      内部类(inner class)是定义在一个类中的类。为什么要使用内部类?其主要原因有以下三点:

1)     内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。

2)     内部类可以对同一个包中的其他类隐藏起来

3)     当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。

 

使用内部类访问对象状态

      内部类的语法比较复杂。鉴于此情况,我们选择一个简单但不太实用的例子说明内部类的使用方式。下面将进一步分析TimerTest示例,并抽象出一个TalkingClock类,构造一个语音时钟需要提供两个参数:发布通告的间隔和开关铃声的标志。

public class TalkingClock
{
	public TalkingClock(intinterval,boolean beep){...}
	public void start(){...}

	private int interval;
	private boolean beep;

	public class TimerPrinterimplements ActionListener
	//an inner class
	{
		//代码省略
	}
}

需要注意,这里的TimePrinter类位于TalkingClock类内部,这并不意味着每个TalkingClock都有一个TimePrinter实例域。如前所示,TimePrinter对象是由TalkingClock类的方法构造。

 

下面是TimePrinter类的详细内容,需要注意一点,actionPerformed方法在发出铃声之前检查了beep标志。

publicclass TimePrinter implements ActionListener
{
	public voidactionPerformed(ActionEvent event)
	{
		Date now = new Date();
		System.out.println("Atthis time,the time is " + now);
		if(beep).Toolkit.getDefaultToolkit().beep();
	}
}

TimePrinter类没有实例域或者名为beep的变量,取而代之的是beep引用了创建TimePrinter的TalkingClock类的域。从传统意义上讲,一个方法可以引用调用这个方法对象数据域。内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。

为了能够运行这个对象,内部类总有一个隐式引用,它指向了创建它的外部类对象。

这个引用在内部类的定义中是不可以见的。然而,为了更好的说明这个概念,我们将外围类的引用称为outer。于是actionPerformed的方法等价于下列形式:

if(outer.beep)Toolkit.getDefaultToolkit().beep(); 

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

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

      需要声明,outer不是Java关键字,我们只是用它说明内部类中的机制。

      当在start方法中创建了TimePrinter对象后,编译器就会将this引用传递给当前的语音时钟的构造器:

ActionListener listener = newTimePrinter(this);
//parameter automatically added

内部类的特殊语法规则

      在上一节中,已经讲述了内部类有一个外围类的引用outer。事实上,使用外围类引用的正规语法还要复杂一些。表达式:

       OuterClass.this

      表示外围类的引用。例如,可以像下面这样编写TimePrinter内部类的actionPerformed方法:

public void actionPerformed(ActionEventevent)
{
	//部分代码省略
	if(TalkingClock.this.beep)Toolkit.getDefaultToolkit().beep();
}

反过来,可以采用下列如法格式更加明确地编写内部类对象的构造器:

outerObject.new InnerClass(construction parameters)

例如:

ActionListener listener = this.newTimePrinter();

      在这里,最新构造的TimePrinter对象的外部类引用被设置为创造内部类对象的方法中的this引用。这是一种很常见的情况。这是一种很常见的情况。通常,this限定词是多余的。不过,可以通过显示的命名将外围类引用设置为其他的对象。例如,如果TimePrinter是一个公有内部类,对于任意的语音始终都可以构造一个TimePrinter:

TalkingClock jabber = newTalkingClock(1000,true);
TalkingClock.TimePrinter listener =jabber.new TimePrinter();

需要注意,在外围类的作用域之外,可以这样引用内部类:

       OuterClass.InnerClass

 

局部内部类

      如果仔细的阅读一下TalkingClock示例的代码就会发现,TimePrinter这个类名字只在start方法中创建这个类的对象使用了一次。

      当遇到这类情况时,可以在一个方法中定义局部类。

public void start()
{
	class TimePrinter implementsActionListener
	{
		Date now = new Date();
		System.out.println("At thetone, the time is " + now);
		if(beep)Toolkit.getDefaultToolkit().beep();
	}

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

      局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

      局部类有一个优势,即对外部可以完全的隐藏起来。即使TalkingClock类中的其他代码也不能访问它。除了start方法之外,没有任何方法知道TimePrinter类的存在。

 

由外部方法访问final变量

      与其他内部类比较,局部类还有一个优点,它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,这个变量必须声明为final。下面是一个典型的示例。这里,将TalkingClock构造器的参数interval和beep移至start方法中。

public void start(int interval,final boolean beep)
{
	class TimePrinter implementsActionListener
	{
		public voidactionPerformed(ActionEvent event)
		{
			Date now = new Date();
			System.out.println("Atthe tone,the time is " + now);
			if(beep)Toolkit.getDefaultToolkit().beep();
		}
	}

	ActionListener listener = newTimePrinter();
	Timer t = new Timer(1000,listener);
	t.start();
}

      请注意,TalkingClock类不再需要存储实例变量beep了,它只是引用start方法中的beep参数变量。

PS:final关键字可以应用于局部变量、实例变量和静态变量。在所有这些情况下,它们的含义都是:在创建这个变量之后,只能够为之赋值一次。此后,再也不能修改它的值了。这就是final。

      不过,在定义final变量的时候,不必进行初始化。例如,当调用start方法,final参数变量beep只能够在创建之后被初始化一次(如果这个方法被调用多次,那么每次调用都有一个新创建的beep参数)。定义时没有初始话的final变量通常被称为空final(blank final)常量。

 

      假设想更新在一个封闭作用域内的计数器(counter),这里要想统计一下在排序 过程中调用的compareTo方法的次数,由于清楚的知道counter需要更新,所以不能将counter声明为final。由于Integer对象是不可变的,所以也不能用Integer代替它。补救的方法是一个长度为1的数组。

final int[] counter = new int[1];
Datedates = new Date[100];
for(inti = 0,i < date.length,i++)
	dates[i] = new Date()
	{
		public int compareTo(Date other)
		{
			 counter[0]++;
			 returnsuper.compareTo(other);
		}
	};
Array.sort(dates);

      数组变量仍被声明为final,但是这仅仅表示不可以让它引用另外一个数组。数组的数据可以自由的更新。

 

匿名内部类

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

public void start(int interval,final boolean beep)
{
	ActionListener listener = newActionListener()
		{
			public voidactionPerformed(ActionEvent event)
			{
				Date now = newDate();
				System.out.println("Atthe tone,the time is " + now);
				if(beep)Toolkit.getDefaultToolkit().beep();
			}
		};

	Timer t = new Timer(1000,listener);
	t.start();
}

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

      用于构造对象的任何参数都要被放在超类名后面的括号()内。通常的语法格式为:

newSuperType(Construction parameters)
	{
		  inner clas methods and data
	}

      其中,SuperType可以是ActionListener这样的接口,于是内部类就要实现这个接口,SuperType也可以是一个类,于是内部类就要扩展它。

 

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

new Interface()
{
	methodsand date
}

构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象之间的差别:

Person queen = new Person(“Marry”);
//a Person object
Person count = new Person(“Dracula”){…};
//a object of an inner class extending Person

如果构造参数的闭圆括号后面跟一个开花括号,正在定义的就是匿名内部类。

 

静态内部类

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

      下面是一个使用静态内部类的典型例子。考虑一下计数数组中的最小值和最大值问题。

double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
for(double v : values)
{
	if(v < min) min = v;
	if(v > max) max = v;
}

      然而,这个方法必须返回两个数值,为此,可以定义一个包含两个值的类Pair:

classPair
{
	public Pair(double first,double second)
	{
		this.first = first;
		this.second =second;
	}

	public double getFirst(){returnthis.first;}
	public double getSecond(){returnthis.second;}

	private double first;
	private double second;
}

      minmax方法可以返回一个Pair类型的对象:

classPairAlg
{
	public static Pair minmax(double[]values)
	{
		//省略部分代码
		return new Pair(min,max);
	}
}

Pair是一个十分大众化的名字。大型项目中,除了定义一对字符串的Pair类之外,其他程序员也很可能使用这个名字,这样就会产生名字冲突。解决这个问题的办法是将Pair定义在ArrayAlg的内部公有类。此后,通过ArrayAlg访问它。

ArrayAlg.Pair p = ArrayAlg.minmax(d);

      与前面例子中使用内部类不同,在Pair对象中不需要引用任何其他的对象。为此,可以将这个类声明为static。

 

      当然,只有内部类可以声明为static,静态内部类的对象除了没有生成它的外围类的引用特权之外,与其他的所有内部类完全一样。在示例中,必须使用静态内部类,这是由于内部类的对象是在静态方法构造的。

publicstatic Pair minmax(double[] d)
{
	//省略部分代码
	return new Pair(min,max);
}

      如果没有将Pair类声明为static,那么编译器会给出错误报告。

      PS:声明在接口中的内部类自动成为static和public。

你可能感兴趣的:(黑马程序员_内部类)