局部内部类+外部类访问final变量+final 回顾

【0】README

0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 局部内部类 的基础知识;
0.2) 其中,还阐述了 由外部类访问final变量(局部内部类只能访问外部类的final实例域)的知识 , 最后对 final 的用法进行了回顾;

【1】局部内部类相关

1.1)仔细查看 TalkingClock的代码就会发现, TimePrinter这个类名字只在 start 方法中创建这个类型的对象时使用了一次;
1.2)当遇到这种情况时, 可以在一个方法中定义局部类:

package com.corejava.chapter6_2;

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

/** * This program demonstrates the use of inner classes. * @version 1.10 2004-02-27 * @author Cay Horstmann */
public class LocalInnerClassTest {
   public static void main(String[] args)
   {
      TalkingClock clock = new TalkingClock(1000, true);
      clock.start();

      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

/** * A clock that prints the time in regular intervals. */
class TalkingClock
{
   private int interval;
   private boolean beep;

   /** * Constructs a talking clock * @param interval the interval between messages (in milliseconds) * @param beep true if the clock should beep */
   public TalkingClock(int interval, boolean beep)
   {
      this.interval = interval;
      this.beep = beep;
   }

   /** * Starts the clock. */
   public void start()
   {
       class TimePrinter 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 TimePrinter();
      Timer t = new Timer(interval, listener);
      t.start();
   }
}

局部内部类+外部类访问final变量+final 回顾_第1张图片
Attention)

  • A1)局部内部类不能使用 public 或 private 访问说明符进行声明。它的作用域被限定在声明这个局部类的块中;
  • A2)局部类有一个优势: 即对外部世界可以完全隐藏起来, 即使 TalkingClock类中的其他代码也不能访问它。除开 start 方法外, 没有 任何方法知道TimePrinter 类存在;

【2】 由外部方法访问 final变量(局部内部类只能访问外部类的final实例域)

2.1)局部类还有一个优点:它们不仅能够访问包含它们的外部类,还可以访问局部变量, 不过那些局部变量必须被声明为 final;
2.2)看个荔枝, 这里,将 TalkingClock 构造器的参数interval 和 beep 移至 start 方法中:

局部内部类+外部类访问final变量+final 回顾_第2张图片 局部内部类+外部类访问final变量+final 回顾_第3张图片

package com.corejava.chapter6_3;

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

/** * This program demonstrates the use of inner classes. * @version 1.10 2004-02-27 * @author Cay Horstmann */
public class LocalInnerClassTest {
   public static void main(String[] args)
   {
      //TalkingClock clock = new TalkingClock(1000, true);
       TalkingClock clock = new TalkingClock();
      clock.start(1000, true);

      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

/** * A clock that prints the time in regular intervals. */
class TalkingClock
{
   /** * Constructs a talking clock */
   //public TalkingClock(int interval, boolean beep)
    public TalkingClock()  {  }

   /** * Starts the clock. */
   public void start(int interval, final boolean beep)
   {
       class TimePrinter 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 TimePrinter();
      Timer t = new Timer(interval, listener);
      t.start();
   }
}

Attention)

  • A1) TalkingClock 类不再需要存储实例变量beep了,它只是引用start方法中的beep 参数变量;
  • A2)毕竟在 start 方法内部, 为什么不能访问beep 变量的值呢?

2.3)为了能够清楚地看到内部问题, 让我们仔细地考察一下控制流程:

  • 2.3.1)调用start 方法;
  • 2.3.2)调用内部类 TimePrinter 的构造器, 以便初始化对象变量 listener;
  • 2.3.3)将 listener 引用传递给 Timer构造器, 定时器开始 计时, start方法结束。此时,start 方法的 beep参数变量不再存在;
  • 2.3.4)然后 , actionPerformed 方法执行 if(beep);

2.4)为了能够让actionPerformed 方法工作, TimePrinter 类在 beep域释放前将 beep域用 start方法的 局部变量进行备份。

  • 2.4.1)实际上,也是这么做的。编译器为局部内部类构造了名字 TalkingClock$TimePrinter, 如果再次运行ReflecttionTest 程序,查看TalkingClock$Time-Printer类, 就会看到下列结果:
class TalkingClock$1TimePrinter
{
    TalkingClock$1TimePrinter(TalkingClock,  boolean);

    public void actionPerfomed(java.awt.event.ActionEvent);

    final boolean val$beep;
    final TalkingClock this$0;
}

对上述代码的分析(Analysis):

  • A1)请注意构造器的boolean 参数和 val$beep 实例变量 。 当创建一个对象的时候, beep 就会被传递给构造器, 并存储在val$beep 域中;
  • A2)编译器必须检测对局部变量的访问, 为每一个变量建立相应的 数据域, 并将局部变量copy 到构造器中, 以便将这些数据域初始化为局部变量的副本;
  • A3)从coder的view 看, 局部变量的访问非常容易, 它减少了需要显式编写的实例域, 从而使得内部类更加simple;
  • A4)局部类的方法只可以引用定义为final 的局部变量, 鉴于此情况, 在列举的示例中, 将 beep 参数声明为final, 对它进行初始化后不能够再进行修改了。 因此,就使得局部变量与在局部类建立的拷贝保持一致;

【3】review of final specification

Annotation)

  • A1)前面将final变量作为常量使用, 如, public static final double SPEED_LIMIT = 55;
  • A2) final 关键字可以应用于局部变量, 示例变量和静态变量。在所有这些情况下, 它们的含义都是: 在创建这个变量后, 只能够为之赋值一次。此后,再也不能修改它的值了,这就是final;
  • A3)但是,在定义final变量的时候, 不必进行初始化。如, 在上例中, 当调用start方法时,final参数变量beep 只能够在创建之后被初始化一次。
  • A4)空final变量: 定义时没有初始化的final变量通常被称为 空final(blank final)变量;

3.1)final限制有时候显得不太方便, 假设想要更新在一个封闭作用域内的计数器:
如, 我们想要统计一下在排序过程中调用 compareTo方法的次数;

3.2)问题与解决方法:

  • 3.2.1)遇到的问题:由于清楚的知道counter 需要update, 所以不能将其声明为 final, 由于 Integer 对象是不可变的, 所以也不能用 Integer 替代;
  • 3.2.2)解决方法:使用一个长度为1的数组;


  • 局部内部类+外部类访问final变量+final 回顾_第4张图片
package com.corejava.chapter6_3;

import java.util.Arrays;
import java.util.Date;

public class FinalCaseCopy {
    public static void main(String[] args)
    {
        final int[] counter;
        Date[] dates;

        counter = new int[1];
        dates = new Date[100];
        for (int i = 0; i < dates.length; i++)
        {
            dates[i] = new Date()
            {
                public int compareTo(Date other)
                {
                    counter[0]++;
                    return super.compareTo(other);
                }
            };
        }
        Arrays.sort(dates);
        System.out.println("counter = " + counter[0]);
    }
}

你可能感兴趣的:(java,final,局部内部类)