java学习笔记——内部类
内部类是定义在另一个类中的类。使用内部类的主要原因有以下三点:
- 内部类方法可以访问该类定义所在的作用域的数据,包括私有的数据;
- 内部类可以对同一个包中的其他类隐藏起来;
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
内部类包括:普通内部类、局部内部类、匿名内部类、静态内部类。
这里我们不去讨论内部类的实现原理,就看下不同的内部类和普通的类有哪些差别。
下面是一个计时器的类,实现了ActionListener接口,实现的效果是每过10s输出一个提示。
普通类实现:

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();
}
}
普通内部类实现:

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的话,其他类也可以访问它,访问的代码为:

TalkingClock clock=new TalkingClock(10000, true);
//clock.start();
ActionListener listener=clock.new TimerPrinter();
Timer timer=new Timer(1000, listener);
timer.start();
这样的实现看起来和普通类实现差不多,显然不是我们想要的结果。所以不是特殊要求的话,最好将内部类的访问域定义为private,或者将其设置为下面的静态内部类。
局部内部类实现:

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方法可见。
改进后的局部内部类实现:

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的生命周期吧:
- 调用start方法,beep变量生命周期开始
- 调用内部类TimePrinter的构造器,初始化对象变量listener
- 将listener引用传递给Timer构造器,定时器开始计时,start方法结束。此时,start方法的beep参数变量不再存在
- 然后,这时计时器执行,actionPerformed方法执行if(beep)
2)原因是:编译程序实现上的困难,难在何处:内部类对象的生命周期会超过局部变量的生命期。为什么?表现在:局部变量的生命期:当该方法被调用时,该方法中的局部变量在栈中被创建(诞生),当方法调用结束时(执行完毕),退栈,这些局部变量全部死亡。而:内部类对象生命期,与其它类一样,当创建一个该局部类对象后,只有没有其它人再引用它时,它才能死亡。完全可能:一个方法已调用结束(局部变量已死亡),但该局部类的对象仍然活着。即:局部类的对象生命期会超过局部变量。
3)退一万步:局部类的对象生命期会超过局部变量又怎样?问题的真正核心是:如果:局部内部类的对象访问同一个方法中的局部变量,是天经地义的,那么:只要局部内部类对象还活着,则:栈中的那些它要访问的局部变量就不能“死亡”(否则:它都死了,还访问个什么呢?),这就是说:局部变量的生命期至少等于或大于局部内部类对象的生命期。而:正是这一点是不可能做到的
4)但是从理论上:局部内部类的对象访问同一个方法中的局部变量,是天经地义的。所以:经过努力,达到一个折中结果:即:局部内部类的对象可以访问同一个方法中的局部变量,只要这个变量被定义为final.那么:为什么定义为final变可以呢?定义为final后,编译程序就好实现了:具体实现方法是:将所有的局部内部类对象要访问的final型局部变量,都成为该内部类对象中的一个数据成员。这样,即使栈中局部变量(含final)已死亡,但由于它是final,其值永不变,因而局部内部类对象在变量死亡后,照样可以访问final型局部变量。


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,就可以取消产生的引用。
当然这段代码完全可以用普通类来替代

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类型对象初始化内部类对象。
这些就是我们的内部类了,怎么去使用它要根据具体情况分析。