Java 内部类的原理和作用


内部类(inner class)是定义在另一个类中的类,一般有三个特点:

  • 内部类可访问该类定义所在作用域中的数据,包括私有数据
  • 内部类可以对同一个包的其他类隐藏起来
  • 定义一个回调函数且不想编写大量代码时,可通过匿名内部类实现

1 内部类如何访问对象状态

public class TalkingClock
{
	private int interval;
	private boolean beep;
	public TalkingClock(int interval,boolean beep) {...}
	public void start()
	{
		ActionListener listener = new TimePrinter();
		Timer t = new Timer(interval,listener);
		t.start();
	}
	public class TimePrinter implements ActionListener
	{
		@Override
            public void actionPerformed(ActionEvent e) 
            {
                System.out.println("At the tone, the time is " + new Date());
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
     }
}

内部类TimePrinter中访问了外部类TalkingClock对象中的beep变量,这里实际上可以表示为;

TalkingClock.this.beep

在内部类中存在一个对外围类的引用outer(TalkingClock.this),同样,外围类的作用域之外,可以通过OuterClass.InnerClass引用内部类。

2 编译器如何处理内部类

内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成用$符号分割外部类名与内部类名的常规类文件,而虚拟机不知道这些。这一点可以通过反射来测试:4.5 通过反射打印类的信息
在这里插入图片描述
编译器为了让内部类引用外围类,生成了一个附加实例域tihis$0,同时还能看到构造器中的TalkingClock参数。通过this$0可以访问外围类的私有数据。同时编译器根据内部类对外围类变量的引用,在外围类添加静态方法,使用上面的反射程序查看TalkingClock的类信息:
在这里插入图片描述

这里编译器添加了access$000静态方法,返回布尔类型值,即对应了原程序中内部类对外围类beep变量的引用。这样做其实有一定的风险,beep变量在TalkingClock中是私有变量,而内部类TimePrinter类是公有状态,可以在类外访问,这样任何人都可以调用access$000方法轻易地读取到私有域beep。

3 局部内部类

上面示例中,TimePrinter类仅在start方法中创建listener对象时使用了一次,对于这种情况,可以将TimePrinter定义为局部内部类,将其作用域限定在start方法中。

局部类声明在某个块中,不能用public或private访问说明符进行声明,它的作用域声明在这个局部类的块中。局部类的优势在于,可以对外界完全隐藏起来,除了start方法之外,没有任何方法知道TimePrinter类的存在:

public void start(int interval, boolean beep)
    {
        class TimePrinter implements ActionListener
        {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("At the tone, the time is " + new Date());
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
        }
        ActionListener listener = new TimePrinter();
        Timer t = new Timer(interval,listener);
        t.start();
    }

这里将interval和beep变量从构造器参数移到start方法参数中,这样局部内部类访问的就不是内部类的变量,而是start方法参数引用的局部变量,为了能够让actionPerformed方法工作,TimePrinter类在beep域释放之前将beep域用start方法的局部变量进行备份。通过上面的反射程序再次查看TimePrinter类的信息:
在这里插入图片描述
可以看到,编译器在TimePrinter类中生成了一个val$beep变量,并且在构造器参数中有一个布尔类型参数,当创建一个TimePrinter对象时,beep就会被传递给构造器,并存储在val$beep域中。注意,局部变量引用到内部类后被声明为final类型,初始化后便不能修改。

4 匿名内部类

假如只创建内部类的一个对象,就可以不用命名了,这种类被称为匿名内部类(anonymous inner class),即创建一个实现ActionListener接口的新对象,需要实现的方法定义在中括号{}内:

public void start(int interval, boolean beep)
    {
        ActionListener listener = new ActionListener()
        {
            public void actionPerformed(ActionEvent e) {
                System.out.println("At the tone, the time is " + new Date());
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
        };
        Timer t = new Timer(interval,listener);
        t.start();
    }

由于没有类名,所以匿名类不能有构造器,取而代之的是,将构造器参数传递给超类构造器。匿名内部类一般用于实现事件监听器和其他回调,这一点目前主要改为使用lambda表达式实现。

public void start(int interval, boolean beep)
    {
        Timer t = new Timer(interval, event -> 
        {
        	System.out.println("At the tone, the time is " + new Date());
        	if (beep) Toolkit.getDefaultToolkit().beep();
        });
        t.start();
    }

当需要构造一个只使用一次的列表,可以将其作为一个匿名列表,采用双括号初始化(double brace initialization)方法:

ArrayList<String> friends = new ArrayList<>();
friends.add("Harry");
friends.add("Tony");
invite(friends);
//double brace initialization
invite(new ArrayList<String>() {{add("Harry");add("Tony");}});

外层括号建立了ArrayList的一个匿名子类,内层括号则是一个对象构造块。

5 静态内部类

有时,使用内部类只是为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。下面的例子中,minmax函数需要同时返回数组中的最小值和最大值,为此需要定义Pair类描述这种返回类型,为了防止产生名字冲突,将其定义为ArrayAlg的内部公有类,通过ArrayAlg.Pair访问它:

public class StaticInnerClassTest {
    public static void main(String[] args)
    {
        double[] d = new double[20];
        for (int i = 0; i < 20; i++)
        {
            d[i] = 100 * Math.random();
        }
        ArrayAlg.Pair p = ArrayAlg.minmax(d);
        System.out.println("min = " + p.getFirst());
        System.out.println("max = " + p.getSecond());
    }
}

class ArrayAlg
{
    /**
     * A pair of floating-point numbers
     */
    public static class Pair
    {
        private double first;
        private double second;
        /**
         * 构造一对浮点数
         * @param f first number
         * @param s second number
         */
        public Pair(double f,double s)
        {
            first = f;
            second = s;
        }
        /**
         * 返回第一个数字
         * @return first number
         */
        public double getFirst()
        {
            return first;
        }
        /**
         * 返回第二个数组
         * @return second number
         */
        public double getSecond()
        {
            return second;
        }

    }
    /**
     * 同时计算数组的最小值和最大值
     */
    public static Pair minmax(double[] values)
    {
        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;
        for (double v : values)
        {
            min = Math.min(min, v);
            max = Math.max(max, v);
        }
        return new Pair(min,max);
    }
}

只有内部类可以声明为static,与常规内部类不同,只有静态内部类可以用于静态域和静态方法,而声明在接口中的内部类自动成为static和public类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值