Java内部类---巩固强化

1.内部类的功能

内部类是定义在另一个类中的类。为什么需要使用内部类呢?主要有两个原因:

  • 内部类可以对同一个包中的其他类隐藏。
  • 内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据。

2.使用内部类访问对象状态

我们将重构TimerTest示例,抽象出一个TalkingClock类。构造一个语音时钟时需要提供两个参数:发出通知的间隔和开关铃声的标志。

public class TalkingClock
{
   private int interval;
   private boolean beep;
   public TalkingClock(int interval,boolean beep){...}
   public void start(){...}
 
   public class TimePrinter implements ActionListener
   //an inner class
   {
     ...
   }
}

需要注意,这里的TimerPrinter类位于TalkingClock类内部。这并不意味着每个TalkingClock都有一个TimePrinter实例字段。如前所示,TimePrinter对象是由TalkingClock类的方法构造的。(事件处理逻辑不熟悉没关系,能立即内部类的功能就行)
下面是TimePrinter类的详细内容。需要注意一点,actionPerformed方法在发出铃声之前会检查beep标志。

public class TimePrinter implements ActionListener
{
   public void actionPerformed(ActionEvent event)
   {
    System.out.println("At the tone,the time is"+Instant.ofEpochMilli(event.getWhen()));
    if(beep)Toolkit.getDefaultToolkit().beep();
   }
}

虽然TimePrinter类没有实例字段或者名为beep的变量,实际上,beep指示TalkingClock对象中创建这个TimePrinter的字段。但是可以看到,一个内部类方法可以访问自身的数据字段,也可以访问创建它的外围类对象的数据字段。为此,内部类的对象总有一个隐式引用,指向创建它的外部类对象,如下图所示。 在这里插入图片描述
这个引用在内部类的定义中是不可见的。不过,为了说明这个概念,我们将外围类对象的引用称为outer。于是actionPerformed方法将等价于以下代码:

public void actionPerformed(ActionEvent event)
{
     System.out.println("At the tone,the time is"+Instant.ofEpochMilli(event.getWhen()));
    if(outer.beep)Toolkit.getDefaultToolkit().beep();
}

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

public TimePrinter(TalkingClock clock)
{
   outer=clock;
}

这里强调一下,注意outer不是Java关键字。我们只是用它说明内部类的有关机制。具体是什么会在后文介绍。在start方法中构造一个TimerPrinter对象后,编译器就会将当前语音时钟的this引用传递给这个构造器:
var listener=new TimePrinter(this);
下面的程序清单给出了测试这个内部类的完整程序。下面我们再来看访问控制。如果TimePrinter类是一个普通的类,它就需要通过TalkingClock类的公共方法访问beep标志,而使用内部类是一个改进,现在不需要提供仅用于另外一个类的访问器。

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Instant;

public class test {
    public static void main(String[] args)
    {
        var clock=new TalkingClock(1000,true);
        clock.start();
        JOptionPane.showMessageDialog(null,"Quit program?");
        System.exit(0);
    }
}
class TalkingClock
{
    private int interval;
    private boolean beep;
    public TalkingClock(int interval,boolean beep)
    {
        this.interval=interval;
        this.beep=beep;
    }
    public void start()
    {
        var listener=new TimePrinter();
        var timer=new Timer(interval,listener);
        timer.start();
    }
    public class TimePrinter implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            System.out.println("At the tone,the time is"+ Instant.ofEpochMilli(event.getWhen()));
            if(beep)Toolkit.getDefaultToolkit().beep();
        }
    }
}

运行结果如下:
在这里插入图片描述

3.内部类的特殊语法规则

在上文中,我们解释了内部类有一个外围类的引用,我们把它叫做outer。事实上,使用外围类引用的正规语法还要更复杂一些。表达式OuterClass.this表示外围类引用。例如,可以像下面这样编写TimePrinter内部类的actionPerformed方法:

public void actionPerformed(ActionEvent event)
{
  ...
  if(TalkingClock.this.beep)Toolkit.getDefaultToolkit().beep();
}

由于TimePrinter是一个公共内部类,对于任意的语音时钟都可以构造一个TimePrinter:

var jabberer=new TalkingClock(1000,true);
TalkingClock.TimePrinter listener=jabberer.new TimePrinter();

注:内部类中声明的所有静态字段都必须是final,并初始化为一个编译时常量。如果这个字段不是一个常量,就可能不唯一。内部类不能有static方法。java语言规范对这个限制没有做任何解释。

4.局部内部类

如果仔细查看TalkingClock示例的代码就会发现,类型TimePrinter的名字只出现了一次:只是在start方法中创建这个类型的对象时使用了一次。
当遇到这类情况时,可以在一个方法中局部地定义这个类。

    public void start()
    {
        class TimePrinter implements ActionListener
        {
            public void actionPerformed(ActionEvent event)
            {
                System.out.println("At the tone,the time is"+ Instant.ofEpochMilli(event.getWhen()));
                if(beep)Toolkit.getDefaultToolkit().beep();
            }
        }
        var listener=new TimePrinter();
        var timer=new Timer(interval,listener);
        timer.start();
    }

声明局部类时不能有访问说明符(即public和private)。局部类的作用域被限定在声明这个局部类的块中。局部类有一个很大的优势,即对外部世界完全隐藏,甚至TalkingClock类中的其他代码也不能访问它。除start方法之外,没有任何方法知道TimePrinter类的存在。

5.匿名内部类

使用局部内部类时,通常还可以再进一步。假如只想创建这个类的一个对象,甚至不需要为类指定名字。这样一个类称为匿名内部类。

    public void start()
    {
        
        var listener=new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                System.out.println("At the tone,the time is"+ Instant.ofEpochMilli(event.getWhen()));
                if(beep)Toolkit.getDefaultToolkit().beep();
            }
        }
        var timer=new Timer(interval,listener);
        timer.start();
    }

它的含义是:创建一个类的新对象,这个类实现了ActionListener接口,需要实现的方法actionPerformed在括号{}内定义。
一般地,语法如下:
new SuperType(construction parameters)
{
inner class methods and data
}
其中,SuperType可以是接口,如ActionListener,如果是这样,内部类就要实现这个接口。SuperType也可以是一个类,如果是这样,内部类就要扩展这个类。
由于构造器的名字必须与类名相同,而匿名内部类没有类名,所以,匿名内部类不能有构造器。实际上,构造参数要传递给超类构造器。具体地,只要内部类实现一个接口,就不能有任何构造参数。不过,仍然要提供一组小括号,如下所示:
new InterfaceType()
{
methods and data
}

6.静态内部类

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类有外围类对象的一个引用。为此,可以将内部类声明为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:(C++用引用就可以了)

class Pair
{
  private double first;
  private double second;
  public Pair(double f,double s)
  {
    first=f;
    second=s;
  }
  public double getFirst(){return first;}
  public double getSecond(){return second;}
}
//minmax方法可以返回一个Pair类型的对象。
class ArrayAlg
{
  public static Pair minmax(double[] values)
  {
     ...
     return new Pair(min,max);
  }
}

这个方法的调用者可以使用getFirst和getSecond方法获得答案:
当然,Pair是一个十分大众化的名字。在大型项目中,其他程序员也很有可能使用这个名字,只不过可能会定义一个Pair类包含一对字符串。这样就会产生名字冲突,解决这个问题的办法是将Pair定义为ArrayAlg的一个公共内部类。此后,就可以通过ArrayAlg.Pair访问它了:
ArrayAlg.Pair p=ArrayAlg.minmax(d);
不过,与前面例子中所使用的内部类不同,在Pair对象中不需要任何其他对象的引用,为此,可以将这个内部类声明为static,从而不生成那个引用:

class ArrayAlg
{
  public static class Pair
  {
    ...
  }
  ...
}

当然,只有内部类可以声明为static。静态内部类就类似于其他内部类,只不过静态内部类没有生成它的外围类对象的引用。在我们的示例中,必须使用静态内部类,这是由于内部类对象时在静态方法中构造的:

猜你喜欢

转载自blog.csdn.net/Achenming1314/article/details/105856634
今日推荐