java学习笔记——内部类

内部类是定义在另一个类中的类。使用内部类的主要原因有以下三点:

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

内部类包括:普通内部类、局部内部类、匿名内部类、静态内部类。

这里我们不去讨论内部类的实现原理,就看下不同的内部类和普通的类有哪些差别。

下面是一个计时器的类,实现了ActionListener接口,实现的效果是每过10s输出一个提示。

普通类实现:

View Code
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

public class TimerTest
{
public static void main(String[] args)
{
ActionListener listener=new TimerPrinter();
Timer t=new Timer(10000, listener);
t.start();

JOptionPane.showMessageDialog(null,"Quit program?");
System.exit(0);
}
}

class TimerPrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
Date now=new Date();
System.out.println("At the tone,the time is "+now);
Toolkit.getDefaultToolkit().beep();
}
}

普通内部类实现:

View Code
package com.test.www;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

public class TimerTest
{
public static void main(String[] args)
{
TalkingClock clock=new TalkingClock(10000, true);
clock.start();

JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}

class TalkingClock
{
public TalkingClock(int interval,boolean beep)
{
this.interval=interval;
this.beep=beep;
}

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

private int interval;
private boolean beep;

public class TimerPrinter 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();
}
}
}

可以看出使用内部类,封装性会好一些,调用的时候也更方便了,Timer的配置都放在了TalkingClock类中 。当然完全可以将TimerPrinter类定义成普通类,然后在TalkingClock中构造它,但是这样耦合性增强了,而且感觉上有点绕。这样的使用符合了使用内部类原因的前两条。

内部类定义为public的话,其他类也可以访问它,访问的代码为:

View Code
TalkingClock clock=new TalkingClock(10000, true);
//clock.start();
ActionListener listener=clock.new TimerPrinter();
Timer timer=new Timer(1000, listener);
timer.start();

这样的实现看起来和普通类实现差不多,显然不是我们想要的结果。所以不是特殊要求的话,最好将内部类的访问域定义为private,或者将其设置为下面的静态内部类。

局部内部类实现:

View Code
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

import org.omg.CORBA.PRIVATE_MEMBER;

public class TimerTest
{
public static void main(String[] args)
{
TalkingClock clock=new TalkingClock(10000, true);
clock.start();

JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}

class TalkingClock
{
public TalkingClock(int interval,boolean beep)
{
this.interval=interval;
this.beep=beep;
}

public void start()
{
class TimerPrinter 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 TimerPrinter();
Timer timer=new Timer(interval, listener);
timer.start();
}

private int interval;
private boolean beep;
}

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

局部内部类的优势是它对于外部世界是完全隐藏的,即便是嵌套它的外部类的其他方法也无法访问它,除了包含它的方法

局部内部类明显隐藏性更好了,其连访问域都不能设置了。上面的例子我们也可以看出,和TimerPrinter交互的就只有start方法,将其定义在start方法中好像更符合常理,它只对start方法可见。

改进后的局部内部类实现:

View Code
package com.test.www;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

import org.omg.CORBA.PRIVATE_MEMBER;

public class TimerTest
{
public static void main(String[] args)
{
TalkingClock clock=new TalkingClock();
clock.start(10000,true);

JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}

class TalkingClock
{
public TalkingClock()
{

}

public void start(int interval,final boolean beep)
{
class TimerPrinter 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 TimerPrinter();
Timer timer=new Timer(interval, listener);
timer.start();
}
}

两个代码最大的不同是,TalkingClock类不再存储实例变量beep,而通过start方法传参。这样TalkingClock的工作就更加明确了,本来interval和beep就是被TimerPrinter类所用的,根本就没有必要去保存这两个实例域。这样代码也更简洁了。

看看beep的生命周期吧:

  1. 调用start方法,beep变量生命周期开始
  2. 调用内部类TimePrinter的构造器,初始化对象变量listener
  3. 将listener引用传递给Timer构造器,定时器开始计时,start方法结束。此时,start方法的beep参数变量不再存在
  4. 然后,这时计时器执行,actionPerformed方法执行if(beep)
但是这段代码却能正常执行,唯一的解释就是final的作用,但是查了资料,并没有说final变量的生命周期和其他变量有什么区别的。下面是网上一位高手的解释
1)所谓“局部内部类”就是在对象的方法成员内部定义的类。而方法中的类,访问同一个方法中的局部变量,是天经地义的。那么为什么要加上一个final呢
2)原因是:编译程序实现上的困难,难在何处:内部类对象的生命周期会超过局部变量的生命期为什么?表现在:局部变量的生命期:当该方法被调用时,该方法中的局部变量在栈中被创建(诞生),当法调用结束时(执行完毕),退栈,这些局部变量全部死亡。而:内部类对象生命期,与其它类一样,当创建一个该局部类对象后,只有没有其它人再引用它时,它才能死亡。完全可能:一个方法已调用结束(局部变量已死亡),但该局部类的对象仍然活着。即:局部类的对象生命期会超过局部变量。
3)退一万步:局部类的对象生命期会超过局部变量又怎样?问题的真正核心是:如果局部内部类的对象访问同一个方法中的局部变量,是天经地义的,那么只要局部内部类对象还活着,则:栈中的那些它要访问的局部变量就不能“死亡”(否则:它都死了,还访问个什么呢?),这就是说:局部变量的生命期至少等于或大于局部内部类对象的生命期。而:正是这一点是不可能做到的
4)但是从理论上:局部内部类的对象访问同一个方法中的局部变量,是天经地义的。所以:经过努力,达到一个折中结果即:局部内部类的对象可以访问同一个方法中的局部变量,只要这个变量被定义为final.那么:为什么定义为final变可以呢?定义为final后,编译程序就好实现了:具体实现方法是:将所有的局部内部类对象要访问的final型局部变量,都成为该内部类对象中的一个数据成员。这样,即使栈中局部变量(含final)已死亡,但由于它是final,其值永不变,因而局部内部类对象在变量死亡后,照样可以访问final型局部变量。
总的来说就是java编译器做了一个折中的处理,因为局部类的对象的生命周期比方法的生命周期长是完全可能的,java为了处理这种可能,当一个方法局部变量被定义为final的时候,编译器就会将final变量传递给构造器,并存储在自己的实例域,作为一个副本
匿名内部类实现:
View Code
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

import org.omg.CORBA.PRIVATE_MEMBER;

public class TimerTest
{
public static void main(String[] args)
{
TalkingClock clock=new TalkingClock();
clock.start(10000,true);

JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}

class TalkingClock
{
public TalkingClock()
{

}

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 timer=new Timer(interval, listener);
timer.start();
}

}
这样局部内部类的使用就更深入些了,局部内部类的只用来创建一次对象,完全不需要给其命名,代码再一次精简了。可以这样也产生一个疑问,接口不是不能使用new运算符实例化的吗?
静态内部类实现:
View Code
package com.test.www;

import java.security.PublicKey;

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 pair=ArrayAlg.minmax(d);
System.out.println("min="+pair.getFirst());
System.out.println("max="+pair.getSecond());
}
}

class ArrayAlg
{
public static 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;
}

public static Pair minmax(double[] values)
{
double min=Double.MAX_VALUE;
double max=Double.MIN_VALUE;
System.out.println(min+"----"+max);

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

return new Pair(min,max);
}
}

静态内部类用了另一个实例来体现。这时内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外部类的对象,内部类声明为static,就可以取消产生的引用。

当然这段代码完全可以用普通类来替代

View Code
package com.test.www;

import java.security.PublicKey;

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();
}

Pair pair=ArrayAlg.minmax(d);
System.out.println("min="+pair.getFirst());
System.out.println("max="+pair.getSecond());
}
}

class ArrayAlg
{
public static Pair minmax(double[] values)
{
double min=Double.MAX_VALUE;
double max=Double.MIN_VALUE;
System.out.println(min+"----"+max);

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

return new Pair(min,max);
}
}

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;
}

但是有个问题,Pair是一个大众化的名字,很可能会产生冲突,将其设置为内部类,通过ArrayAlg.Pair来访问很好的解决了这个问题,不然命名可能会让你破费脑筋。

另一个为什么其必须是静态内部类的原因,是因为minmax方法被定义为了static方法,而static方法是不能访问对象状态的,只能访问类的静态域。所以,如果不生, Pair为static的话,编译器就会报错:没有隐式的ArrayAlg类型对象初始化内部类对象。

这些就是我们的内部类了,怎么去使用它要根据具体情况分析。







posted @ 2011-11-02 22:47  okbeng  阅读(743)  评论(0编辑  收藏  举报