使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day044 内部类(由外部方法访问变量、匿名内部类、静态内部类)
1.由外部方法访问变量
与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须事实上为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 = newTimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
请注意,TalkingClock类不再需要存储实例变量beep了,它只是引用start方法中的beep参数变量。
这看起来好像没什么值得大惊小怪的。程序行
if(beep)...
毕竟在start方法内部,为什么不能访问beep变量的值呢?
为了能够清楚地看到内部的问题,让我们仔细地考査一下控制流程。
1)调用start方法。
2)调用内部类TimePrinter的构造器,以便初始化对象变量listener。
3)将listener引用传递给Timer构造器,定时器开始计时,start方法结束。此时,start方法的beep参数变量不复存在。
4)然后,actionPerformed方法执行if(beep)...。
为了能够让actionPerformed方法工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。实际上也是这样做的。在我们列举的例子中,编译器为局部内部类构造了名字TalkingClock$TimePrinter。如果再次运行ReflectionTest程序,查看TalkingClock$Time-Printer类,就会看到下列结果:
class TalkingClock$1TimePrinter
{
TalkingClock$1TimePrinter(TalkingClock, boolean);
public void actionPerformed(java.awt.event.ActionEvent);
final boolean val$beep;
final TalkingClock this$O;
}
请注意构造器的boolean参数和val$beep实例变量。当创建一个对象的时候,beep就会被传递给构造器,并存储在valSbeep域中。编译器必须检测对局部变量的访问,为每一个变量建立相应的数据域,并将局部变量拷贝到构造器中,以便将这些数据域初始化为局部变量的副本。
从程序员的角度看,局部变量的访问非常容易。它减少了需要显式编写的实例域,从而使得内部类更加简单。
前面曾经提到,局部类的方法只可以引用定义为final的局部变量。鉴于此情况,在列举的示例中,将beep参数声明为final,对它进行初始化后不能够再进行修改。因此,就使得局部变量与在局部类内建立的拷贝保持一致。
有时,final限制显得并不太方便。例如,假设想更新在一个封闭作用域内的计数器。这里想要统计一下在排序过程中调用compareTo方法的次数。
int counter = 0;
Date[] dates = new Date[100];
for (int i = 0; i < dates.length; i++)
dates[i] = new Date()
{
public int compareTo(Date other)
{
counter++; // Error
return super.conpareTo(other);
}
};
Arrays.sort(dates);
System.out.println(counter + " comparisons.");
由于清楚地知道counter需要更新,所以不能将counter声明为final。由于Integer对象是不可变的,所以也不能用Integer代替它。补救的方法是使用一个长度为1的数组:
int[] counter = new int[l];
for (int i = 0; i < dates.length; i++)
dates[i] = new Date()
{
public int compareTo(Date other)
{
counter[0]++;
return super.compareTo(other);
}
};
内部类被首次提出时,原型编译器对内部类中修改的局部变量自动地进行转换。不过,后来这种做法被废弃。毕竟,这里存在一个危险。同时在多个线程中执行内部类中的代码时,这种并发更新会导致竞态条件。
2.匿名内部类
将局部内部类的使用再深人一步。假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(anonymousinnerclass)。
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 Date());
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这样的接口,于是内部类就要实现这个接口。SuperType也可以是一个类,于是内部类就要扩展它。
由于构造器的名字必须与类名相同,而匿名类没有类名,所以,匿名类不能有构造器。取而代之的是,将构造器参数传递给超类(superclass)构造器。尤其是在内部类实现接口的时候,不能有任何构造参数。不仅如此,还要像下面这样提供一组括号:
new InterfaceType()
{
methods and data
}
请仔细研究一下,看看构造一个类的新对象与构造一个扩展了那个类的匿名内部类的对象之间有什么差别。
Person queen = new Person("Mary");
//a Person object
Person count = new Person("Dracula"){...};
//an object of an inner class extending Person
如果构造参数的闭小括号后面跟一个开大括号,正在定义的就是匿名内部类。
下面的程序包含了用匿名内部类实现语音时钟程序的全部源代码。将这个程序与最后面的程序相比较就会发现使用匿名内部类的解决方案比较简短、更切实际、更易于理解。
多年来,Java程序员习惯的做法是用匿名内部类实现事件监听器和其他回调。如今最好还是使用lambda表达式。例如,这一节前面给出的start方法用lambda表达式来写会简洁得多,如下所示:
public void start(int interval, boolean beep)
{
Timer t = new Timer(interval, event ->
{
Systea.out.println("At the tone, the time is"+new Date());
if (beep) Toolkit.getDefaultToolkit().beep();
});
t.start();
}
程序代码如下:
/**
*@author zzehao
*/
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class AnonymousInnerClassTest
{
public static void main(String[] args)
{
TalkingClock clock = new TalkingClock();
clock.start(1000, true);
//keep program running until user selects "0k"
JOptionPane.showMessageDialog(null, "Quitprogram?");
System.exit(0);
}
}
/**
*A clock that prints the time in regular intervals.
*/
class TalkingClock
{
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 Date());
if (beep)
Toolkit.getDefaultToolkit().beep();
}
};
Timer t = new Timer(interval, listener);
t.start();
}
}
运行的结果如下:
3.静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。
下面是一个使用静态内部类的典型例子。考虑一下计算数组中最小值和最大值的问题。当然,可以编写两个方法,一个方法用于计算最小值,另一个方法用于计算最大值。在调用这两个方法的时候,数组被遍历两次。如果只遍历数组一次,并能够同时计算出最小值和最大值,那么就可以大大地提高效率了。
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for (double v : values)
{
if (min > v)
min = v;
if (max < v)
max = v;
}
然而,这个方法必须返冋两个数值,为此,可以定义一个包含两个值的类Pair:
class Pair
{
private double first;
private double second;
public Pair(double f, double s)
{
first = f;
second = s;
}
private double getFirst() {return first;}
private double getSecond(); {return second;}
}
minmax方法可以返回一个Pair类型的对象。
class ArrayAlg
{
public static Pair minmax(doublet[] values)
{
...
return new Pair(min, max);
}
}
这个方法的调用者可以使用getFirst和getSecond方法获得答案:
Pair p = ArrayAlg.minmax(d);
System.out.println("min = " +p.getFirst());
System.out.println("max = " +p.getSecond());
当然,Pair是一个十分大众化的名字。在大型项目中,除了定义包含一对字符串的Pair类之外,其他程序员也很可能使用这个名字。这样就会产生名字冲突。解决这个问题的办法是将Pair定义为ArrayAlg的内部公有类。此后,通过ArrayAlg.Pair访问它:
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,那么编译器将会给出错误报告:没有可用的隐式ArrayAIg类型对象初始化内部类对象。
下面是程序的代码:
/**
*@author zzehao
*/
public class StaticInnerClassTest
{
public static void main(String[] args)
{
double[] d = new double[20];
for (int i = 0; i < d.length; i++)
d[i] = 100*Math.random();
ArrayAlg.Pair p = ArrayAlg.minmax(d);
System.out.println("min = " +p.getFirst());
System.out.println("max = " +p.getSecond());
}
}
class ArrayAlg
{
/**
*A pair of floating-point numbers
*/
public static class Pair
{
private double first;
private double second;
public Pair(double f, double s)
{
first = f;
second = s;
}
/**
* Returns the fi rst number of the pair
* return the first number
*/
public double getFirst()
{
return first;
}
/**
* Returns the second number of the pair
* @return the second number
*/
public double getSecond()
{
return second;
}
}
public static Pair minmax(double[] values)
{
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for (double v : values)
{
if (min > v)
min = v;
if (max < v)
max = v;
}
return new Pair(min, max);
}
}
运行的结果是: