Java核心技术卷I学习笔记(五) 内部类

一、内部类概述

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

  • 内部类方法可以访问该类定义所在的作用于中的数据,包括私有的数据
  • 内部类可以对同一个包中的其他类隐藏起来
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷
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
        public void actionPerformed(ActionEvent event) {
            System.out.println("At the tone, the time is " + new Date());
            if (beep) Toolkit.getDefaultToopkit().beep(); // beep是外部类的private域,但是可以在内部类中直接访问
        }
    }
}

从以上代码段中可以看出,内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
内部类的对象总有一个隐式引用,它指向了创建它的外部类对象,这个引用在内部类的定义中是不可见的,可以把它称为outer(注意outer不是Java的关键字,只是为了说明内部类的机制而引入这一称呼),则以上代码段等价为:

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
        public void actionPerformed(ActionEvent event) {
            System.out.println("At the tone, the time is " + new Date());
            if (outer.beep) Toolkit.getDefaultToolkit().beep(); // outer是对外围类的引用,实际上Java中并不存在这个关键字
        }
    }
}

实际上,在TalkingClock的某方法中创建TimePrinter类的对象时,编译器会把this引用自动传递给构造器:

ActionListener listener = new TimePrinter(this); // this由编译器自动传入

而由于TimePrinter没有定义构造器,所以编译器为这个类生成了一个默认的构造器:

public TimePrinter(TalkingClock clock) { // 由编译器自动生成
    outer = clock;
}

这就是内部类中获得外部类的隐式引用的机制。
实际上在内部类中要获得外部类的引用,可以使用这样的语法:

OuterClass.this

依然选择之前的例子,可以像这样编写actionPerformed方法:

public void actionPerformed(ActionEvent event) {
    System.out.println("At the tone, the time is " + new Date());
    if (TalkingClock.this.beep) Toolkit.getDefaultToopkit().beep(); // outer是对外围类的引用,实际上Java中并不存在这个关键字
}

此处把TimePrinter设置为public,则所有其他的类和方法都可以看到这个内部类。若要在其他类中构造TimePrinter类的对象,需要使用这样的语法:

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

而如果把TimePrinter设置为private,则只有TalkingClock类的方法可以看到TimePrinter并构造这个类的对象。只有内部类可以设置为private,而常规类只可以具有包可见性(默认情况)或公有可见性(public)。

注意:

  • 内部类中声明的所有static域都必须是final:我们希望一个静态域只有一个实例,不过对于每个外部对象,会分别有一个单独的内部类实例。如果这个域不是final,它就有可能不唯一
  • 内部类不能有static方法

二、局部内部类

如果某个内部类只在外部类的某一个方法中被使用到,则可以把内部类定义在这个方法中,称为局部类:

public void start() {
    class TimePrinter implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            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();
}

局部类不能用public或private进行声明,它的作用域被限定在声明这个局部类的块中。
局部类可以对外部世界完全地隐藏起来,即使TalkingClock类中的其他代码也不能访问它。除start方法之外,没有任何方法知道TimePrinter类的存在。
与其他内部类相比,局部类不仅能够访问包含它们的外部类,还可以访问局部变量。不过那些局部变量必须事实上为final(effectively final):

// 把interval和beep作为参数传入start函数中,此时interval和beep成为局部变量,由于在局部内部类中使用到了beep,因此beep必须事实上为final,无法在方法actionPerformed中修改beep的值
public void start(int interval, boolean beep) { 
    class TimePrinter implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            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();
}

若确实需要在局部类中修改局部变量的值,则可以通过传递一个长度为1的数组来解决。

三、匿名内部类

假如只创建这个类的一个对象,就不必命名这个类了。这种类被称为匿名内部类:

public void start(int interval, final boolean beep) {
    ActionListener listener = new ActionLIstener() { // 匿名内部类实现了ActionListener接口
        public void actionPerformed(ActionEvent event) {
            System.out.println("At the tone, the time is " + new Date());
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    };
    Timer t = new Timer(interval, listener);
    t.start();
}

匿名类不能有构造器,取而代之的是,将构造器参数传递给超类构造器,尤其是在内部类实现接口的时候,不能有任何构造参数。但是括号不可省略。
在匿名内部类中使用方法的参数时,该参数必须被显式声明为final。
使用lambda表达式来写内部类:

public void start(int interval, final 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();
}

四、静态内部类

有时,使用内部类指示为了把一个类隐藏在另一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。注意使用了静态内部类之后,该内部类仍然可以访问到外部类的private static变量和方法。
只有内部类可以声明为static,静态内部类的对象除了没有对生成它的外围类对象的引用特权外,与其他所有内部类完全一样。
如果在静态方法中创建了某个内部类的对象,则这个内部类必须被声明为静态内部类。
与常规内部类不同,静态内部类可以有静态域和方法。
声明在接口中的内部类自动成为static和public类。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354