内部类学习
所谓内部类(Inner Class),顾名思义,就是指定义在另外一个类中的类,我们为什么要这么做呢?为什么不直接定义它而要在别的类中定义一个内部类呢?这样做主要有如下三个原因:
1. 内部类的方法可以访问它所在的外部类中的所有域,包括私有型别的;
2. 对于同一个包中的其它类它是隐藏的;
3. 匿名的内部类可以让我们很方便的定义事件响应(call back),这在GUI编程中很常见;
一.内部类(inner class)如何访问外部类(outer class)中的域
因为安全机制的原因,内部类通常声明为private类别,因此,只有在内部类所在的外部中才能够创建内部类的对象,对其它类而言它是隐藏的。另外,只有内部类才会用到private修饰符,一般的类如果用private修饰符则会报错。
下面看如下的代码:
package cn.edu.hust.cm.test;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Toolkit;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class InnerClassTest {
public InnerClassTest() {
super();
// TODO Auto-generated constructor stub
}
public static void main(String[] args) {
Court court=new Court(10000,true);
court.start();
JOptionPane.showMessageDialog(null,"停止么,CMTobby?");
System.exit(0);
}
}
class Court{
public Court(int interval,boolean beep){
this.interval=interval;
this.beep=beep;
}
public void start(){
TimerPrinter action=new TimerPrinter();
Timer t=new Timer(interval,action);
t.start();
}
private int interval;
private boolean beep;
private class TimerPrinter implements ActionListener{
public void actionPerformed(ActionEvent e){
System.out.println("Cindyelf,would you be my mm?");
if(beep) Toolkit.getDefaultToolkit().beep();
}
}
}
注意上面红色加粗部分的代码,如你所见beep这个变量在内部类TimerPrinter中我们并没有声明,那么它引用自何处呢?显然是来自于外部类。一般来说,一个方法可直接refer to调用它的对象中的所有域,而一个内部类的方法则可以直接refer to它所在类以及创建它的外部类中的所有域,如上例所示。
事实上,在每一个内部类中都存在一个默认的隐式的reference,它指向创建了这个内部类的实例的那个对象,我们假设它叫outer,这样上面红色加粗部分就相当于:
if(outer.beep) Toolkit.getDefaultToolkit().beep();。Ok,既然存在这样一个reference,那么outer的值又是如何设置的?实际上编译器会合成一个构造方法来设置它,如下面代码所示:
public TimePrinter(Court court) {
outer = clock;
}
这段代码是编译时自动产生的,就像前面我们讨论的自动拆箱装箱中一样,编译器自
己会添加一些代码。然后当我们在Court类的start()方法中创建TimerPrinter实例的时
候,编译器会自动把this作为参数传递过去,效果如下面代码所示:
public void start(){
TimerPrinter action=new TimerPrinter(this);//编译器自动加上的
Timer t=new Timer (interval,action);
t.start ();
}
参数是编译器自动给加上的,不用我们来管。
二.内部类的一些特殊语法规则
前面我们说在每一个内部类中都存在一个默认的隐式的reference,它指向创建了这个内
部类的实例的那个对象,并且我们以outer来带指它,如果我们想显式的指明该按照如下的
语法,OuterClass.this,例如if(Court.this.beep) Toolkit.getDefaultToolkit().beep();。
此外,因为我们定义的内部类通常是private,所以它通常是通过外部类的方法创建,如
本例中的start()方法,这样那个隐式的reference就指向调用了start()方法的对象,就是this。
如果内部类声明为public,那么我们就可以在任何地方实例化一个内部类,如下面代码:
Court court=new Court(10000,true);
Court.TimerPrinter test=court.new TimerPrinter();
上述代码在InnerClassTest的main方法中,这时候那个隐式的reference就直接指向了
court对象。注意语法是:OuterClassName.InnerClassName
许多人认为内部类的语法十分复杂,尤其是匿名内部类,这与Java所一直奉行的“简单”原则相背离的,有人甚至怀疑java中加入这么一个“特征”(feature),是不是已经开始走向“灭亡”?就像许多其它语言一样走向“灭亡”?内部类是否真的有用,有没有存在的必要?我们首先来看看内部的工作原理。
先指明一点,内部类如何工作是由编译器来负责的,与java虚拟机无关,它对这个是一无所知的。仔细留意一下上篇中编译后产生的class文件,你会发现有一个class文件的名字是Court$TimerPrinter,它的基本格式是:外部类名称$内部类名称。当碰到内部类时,编译器会自动根据内部类的代码生成一个class文件并按照上述规则命名,那么编译器到底对它做了什么呢?我们可以使用Java的反射(reflection)机制来“偷窥”它,嘿嘿。具体的代码如下所示:
* Created on 2006-09-24
*
* TODO To change the template for this generated file go to
* Window - Preferences - Java - Code Style - Code Templates
*/
package cn.edu.hust.cm.access;
import java.lang.reflect.*;
import javax.swing.*;
/**
* @author Demon
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class ReflectionTest {
public static void main(String[] args) {
String name="";
if(args.length>0)
name=args[0];
else
name=JOptionPane.showInputDialog("Class name (e.g. java.util.Date): ");
try{
Class c1=Class.forName(name);
Class c2=c1.getSuperclass();
System.out.print("class " + name);
if(c2!=null&&c2!=Object.class)
System.out.print(" extends " + c2.getName());
System.out.print("\n{\n");
printConstructors(c1);
System.out.println();
printMethods(c1);
System.out.println();
printFields(c1);
System.out.println("}");
}
catch(ClassNotFoundException e) { e.printStackTrace(); }
System.exit(0);
}
public static void printConstructors(Class c1){
Constructor[] constructors=c1.getDeclaredConstructors();
for(int i=0;i<constructors.length;i++){
Constructor c=constructors[i];
String name =c.getName();
System.out.print(Modifier.toString(c.getModifiers()));
System.out.print(" " + name + "(");
Class[] paramTypes = c.getParameterTypes();
for(int j=0;j<paramTypes.length;j++){
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
public static void printMethods(Class c1){
Method[] methods=c1.getDeclaredMethods();
for(int i=0;i<methods.length;i++){
Method m=methods[i];
String name=m.getName();
Class type=m.getReturnType();
System.out.print(Modifier.toString(m.getModifiers())+" "+type.getName()+" "+name+"(");
Class[] paramTypes=m.getParameterTypes();
for(int j=0;j<paramTypes.length;j++){
if(j>0) System.out.print(",");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
public static void printFields(Class c1){
Field fields[]=c1.getDeclaredFields();
for(int i=0;i<fields.length;i++){
System.out.print(Modifier.toString(fields[i].getModifiers()));
System.out.print(" ");
Class type=fields[i].getType();
System.out.print(type.getName());
System.out.println(" "+fields[i].getName()+";");
}
}
}
运行该程序,在对话框中输入cn.edu.hust.cm.test.Court$TimerPrinter,将会得到如下输出:
}
cn.edu.hust.cm.test.Court$TimerPrinter(cn.edu.hust.cm.test.Court);
public void actionPerformed(java.awt.event.ActionEvent);
final cn.edu.hust.cm.test.Court this$0;
}
如上所示,编译器自动为我们加上了一个域this$0,它指向一个外部类,另外自动给构造方法增加了一个Court型别参数,用来设置this$0的值,注意this$0是编译器自己合成的,不能直接引用。
既然编译器能够自动进行转化,为什么我们不直接自己进行转换,把TimerPrinter改写成普通的class呢?如下所示:
class Court
{
. . .
public void start()
{
ActionListener listener = new TimePrinter(this);
Timer t = new Timer(interval, listener);
t.start();
}
}
class TimePrinter implements ActionListener
{
public TimePrinter(TalkingClock clock)
{
outer = clock;
}
. . .
private TalkingClock outer;
}
问题来了,我们在实现actionPerformed方法的时候要用到访问outer.beep,但是beep是private类型的,在TimerPrinter中是不能直接访问的。这样内部类的一个优点就显示出来了:内部类能够访问其所属外部类中的私有域而其它普通的类则不行。
那么内部类是通过什么样的机制访问它所属的外部类中的私有数据的呢?联想前面讲私有域的时候,我们都是通过方法来间接访问私有域的,那么这里是不是这样的呢?我们还是对外部类Court进行一下反射,结果如下所示:
class cn.edu.hust.cm.test.Court
{
public cn.edu.hust.cm.test.Court(int, boolean);
static boolean access$0(cn.edu.hust.cm.test.Court);
public void start();
private int interval;
private boolean beep;
}
我们看到新增了一个方法access$0,它的返回值就是传递过来的Court对象的beep域,这样actionPerformed方法中的if(beep)就相当于if(access$0(outer)),内部类就是通过这种机制来访问外部类的私有数据。
这里我们介绍一种特殊的内部类:局部内部类(Local Inner Classes)。
在前面的例子中,我们可以发现TimerPrinter仅在start方法中创建一个新的对象时出现过一次,其它地方都没再用到过,这个情况下我们可以把TimerPrinter就定义在start方法中,如下面代码所示:
public void start(){
class TimerPrinter implements ActionListener{
//private boolean beep;
public void actionPerformed(ActionEvent e){
System.out.println("Bigface,would you be my boy?");
if(beep) Toolkit.getDefaultToolkit().beep();
}
}
TimerPrinter action=new TimerPrinter();
Timer t=new Timer(interval,action);
t.start();
}
注意,局部内部类不需要任何access modifier,否则编译出错,它的作用域一般都被限制在它所在的block中。此时,编译会产生一个名字叫Court$1TimerPrinter的class文件。局部内部类有如下两个优点:
1. 它对外面的所有类来说都是隐藏的,即时是它所属的外部类,仅有它所在的方法知道它;
2. 它不仅可以访问它所属外部类中的数据,还可以访问局部变量,不过局部变量必须生命为final类型,看下面的例子:
package cn.edu.hust.cm.test;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Toolkit;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class InnerClassTest {
public InnerClassTest() {
super();
}
public static void main(String[] args) {
Court court=new Court();
court.start(5000,true);
JOptionPane.showMessageDialog(null,"停止么,CMTobby?");
}
}
class Court{
public void start(int interval,final boolean beep){
class TimerPrinter implements ActionListener{
public void actionPerformed(ActionEvent e){
System.out.println("Cindyelf,are you crazy?");
if(beep) Toolkit.getDefaultToolkit().beep();
}
}
TimerPrinter action=new TimerPrinter();//编译器会自动更改
Timer t=new Timer(interval,action);
t.start();
}
}
注意,局部内部类所要访问的局部变量必须声明为final类型,如上例中红色粗体部分。那么局部内部类是如何访问这个局部变量的呢?我们再对Court$1TimerPrinter反射一下,会得到如下的输出:
class cn.edu.hust.cm.test.Court$1TimerPrinter
{
cn.edu.hust.cm.test.Court$1TimerPrinter(cn.edu.hust.cm.test.Court, boolean);
public void actionPerformed(java.awt.event.ActionEvent);
final cn.edu.hust.cm.test.Court this$0;
private final boolean val$beep;
}
编译器又给我们增加了一个域val$beep,同时构造方法增加了一个boolean类型的参数,因此我们猜想如下的实现过程:
TimerPrinter action=new TimerPrinter(this,beep);//编译器自动增加
然后构造方法中会有:val$beep=beep;这样就成功的把局部变量的值复制过来了。局部变量必须为final就是为了保证成功的复制值,因为final类型的变量一经赋值就不能再发生变化了。
这里再介绍一种特殊的内部类――匿名内部类(Anonymous Inner Class),顾名思义,就是没有名字的内部类,这是Java为了方便我们编写程序而设计的一个机制。因为有时候有的内部类只需要创建一个它的对象就可以了,以后再不会用到这个类,这时候使用匿名内部类就比较合适,而且也免去了给它取名字的烦恼,:)。
匿名类的语法是什么样的呢?如下所示:
new SuperType(){
内部类的方法和域;
}
注意,这里的SuperType指超类,它可以是一个接口(interface)或者是一个类(Class),当它是一个接口的时候就不能有构造参数(Construction parameters)了。匿名类的语法相对于Java的其他部分来说稍微复杂一点,我们可以按如下方式理解:
1.SuperType为接口
例子:Interface1() test=new Interface1(){
要实现的的方法;
Interface1的域;
内部类的域以及方法;
}
我们可以如下理解:
先声明了如下一个类,
class Anonymous1 implements Interface1{
要实现的的方法;
Interface1的域;
内部类的域以及方法;
}
然后,Interface1() test=new Anonymouse1();
这样就比较容易理解了。
2.SuperType为类
例子:Class2 test=new Class2(Construction parameters){
内部类的域以及方法;
}
我们可以如下理解:
先声明了如下一个类,
class Anonymous2 extends Class2{
public Anonymous2(Construction parameters){
super(Construction parameters);
}
内部类的域以及方法;
}
然后,Class2 test=new Anonymouse2(Construction parameters);
本节测试代码如下:
package cn.edu.hust.cm.test;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Toolkit;
import javax.swing.JOptionPane;
import javax.swing.Timer;
public class InnerClassTest {
public InnerClassTest() {
super();
}
public static void main(String[] args) {
Court court=new Court(10000,true);
court.start();
JOptionPane.showMessageDialog(null,"停止么,CMTobby?");
System.exit(0);
}
}
class Court{
public Court(int interval,boolean beep){
this.interval=interval;
this.beep=beep;
}
public void start(){
ActionListener action=new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("Cindyelf,wouly you be my girl?");
}
};
Timer t=new Timer(interval,action);
t.start();
}
private int interval;
private boolean beep;
}
这里介绍最后一种比较特殊的内部类――静态内部类(Static Inner Class),即在内部类的前面增加了static修饰符(modifier)。注意,仅仅只有内部类能够被声明为static类型,通常我们声明一个普通类的时候不能使用static,否则编译出错。
那么为什么我们要使用静态的内部类呢?在什么情况下我们需要使用静态的内部类呢?我们前面讲过,编译器会自动给内部类加上一个reference,指向产生它的那个外部类的对象,如果不想要或者说不需要这个reference,那么我们就可以把这个内部类声明为static,禁止这个reference的产生。除此之外静态类的使用与普通的静态类是一样的。例子如下:
package cn.edu.hust.cm.test;
public class StaticInnerClassTest {
public StaticInnerClassTest() {
super();
// TODO Auto-generated constructor stub
}
public static void main(String[] args) {
// TODO Auto-generated method stub
double d[]=new double[20];
for(int i=0;i<d.length;i++)
d[i]=100*Math.random();
//FindMinMax test=new FindMinMax();
FindMinMax.Pair pair=FindMinMax.getMinMax(d);
System.out.println("最小值是:"+pair.getFirst());
System.out.println("最大值是:"+pair.getSecond());
}
}
class FindMinMax{
static double min=Double.MAX_VALUE;
static double max=Double.MIN_VALUE;
public static Pair getMinMax(double d[]){
for(double value:d){
if(min>value) min=value;
if(max<value) max=value;
}
return new Pair(min,max);
}
public static class Pair{
public Pair(double first,double second){
this.first=first;
this.second=second;
}
public double getFirst(){
return this.first;
}
public double getSecond(){
return this.second;
}
private double first;
private double second;
}
}
在这个例子中之所以要用静态内部类,主要是因为getMinMax这个方法是静态的,由类直接调用。而前面说过创建内部类的时候语法是这样的:
OuterClassObject.new InnerClassName(),如果省略了OuterClassObject则是
this. new InnerClassName(),OuterClassObject或者this指代创建这个内部类对象的一个外部类对象,是一个隐式参数,它将传入内部类的构造方法(见前所述)。但是现在这个内部类对象是由“类”直接创建的,不会产生这样的一个隐式参数传入内部类构造方法,因此内部类也就不需要“编译器自动给内部类加上一个reference,指向产生它的那个外部类的对象”,所以我们把这个内部类声明为static。上面的代码中如果我们去掉static将会报错,除非我们把getMinMax的static去掉,同时通过FindMinMax的一个实例来调用这个方法,如下:
FindMinMax test=new FindMinMax();
test.getMinMax(d);
我们下面来对内部类所产生的class文件反射(reflection)一下,结果如下:
class cn.edu.hust.cm.test.FindMinMax$Pair
{
public cn.edu.hust.cm.test.FindMinMax$Pair(double, double);
public double getSecond();
public double getFirst();
private double first;
private double second;
}
很清楚的看到,编译器没有给我们自动加上一个reference,指向产生它的那个外部类的对象,也没有给构造方法加上一个FindMinMax型别的参数。