八、内部类

内部类(inner class)定义

内部类是定义在另一个类中的类。
需要内部类的原因:

  • 内部类方法可以访问该类定义所在的域中的数据,包括私有的数据。
  • 内部类可以对同一个包中的其他类隐藏起来。
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。

四种内部类:

  1. 普通内部类
class TalkClock {

    private int interval;
    private boolean beep;

    public TalkClock(int interval, boolean beep) {
        this.interval = interval;
        this.beep = beep;
    }

    public void start() {
        ActionListener listener = new TimePrinter();
        Timer timer = new Timer(interval, listener);
        timer.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的方法体,可以看到内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域(外围类TalkClock的域beep),此处可访问的包括private域和静态域。
因为内部类的对象总有一个隐式引用,指向了创建它的外部类对象。

内部类对象对外围类对象的引用.png


这个引用再内部类的定义中是不可见的,此处为了便于理解,将外围类对象的引用称为, actionPerformed() 等价于:

public class TimePrinter implements ActionListener{
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("At the tone, the time is " + new Date());
        if(outer.beep){
            Toolkit.getDefaultToolkit().beep();
        }
    }
}

外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加了一个外围类引用的参数。此处因为TimePrinter类没有定义构造器,所以编译器为这个类生成了一个默认的构造器:

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

再次强调\color{red}{outer}不是Java的关键字,只是为了便于理解。
\color{red}{内部类可以声明为私有的,只有内部类可以是私有类。常规类只可以具有包可见性或公有可见性。}

内部类的语法规则

上面虚拟了内部类的一个外围类的引用outer,而使用外围类引用的正规语法如下:

OuterClass.this

如上面actionPerformed方法的如下编写:

public class TimePrinter implements ActionListener{
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("At the tone, the time is " + new Date());
        if(TalkClock.this.beep){
            Toolkit.getDefaultToolkit().beep();
        }
    }
}

反过来,可以明确的编写内部对象的构造器:

outerObject.new InnerClass(参数);

\color{red}{注意:}
当普通内部类拥有和外围类同名的域或者方法时,会发生隐藏现象,即默认情况下访问的是内部类的域。如果要访问外围类的同名域,需要以下面的形式进行访问:

外部类.this.域名
外部类.this.方法

内部类可以无条件地访问外围类的域或方法,而外围类中如果要访问内部类的域或方法,必须先创建一个内部类的对象,再通过指向这个对象的引用来进行访问。

class TalkClock {

    private int interval;
    private boolean beep;

    public TalkClock(int interval, boolean beep) {
        this.interval = interval;
        this.beep = beep;
        getTimePrinterInstance().printTest();
    }

    private TimePrinter getTimePrinterInstance() {
        return new TimePrinter();
    }

    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();
            }
        }

        public void printTest() {
            System.out.println(interval);
        }
    }
}

内部类是依附外围类而存在的,也就是说,如果要创建内部类的对象,前提是必须存在一个外围类的对象。创建内部类对象的一般方式如下:

TalkClock clock = new TalkClock(1000, true);

//第一种方式:
TalkClock.TimePrinter inner = clock.new TimePrinter();

//第二种方式:
TalkClock.TimePrinter inner1 = clock.getTimePrinterInstance();
  1. 局部内部类
    如上面普通内部类的示例代码中,TimePrinter类名只在start方法中创建这个类型时使用了一次,所以可以改造成在一个方法中定义局部内部类
class TalkClock {

    private int interval;
    private boolean beep;

    public TalkClock(int interval, boolean beep) {
        this.interval = interval;
        this.beep = beep;
    }

    public void start() {
        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 timer = new Timer(interval, listener);
        timer.start();
    }

}

局部内部类是定义在一个方法或者一个作用域里面的类,局部内部类不能使用private或者public访问说明符进行声明,它和普通内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部类有一个优势,对外部世界可以完全的隐藏起来,即使是TalkClock类中的其他代码也不能访问它,除了start方法以外,没有任何方法知道TimePrinter的存在。

  1. 匿名内部类
    将局部内部类的使用再深入一步,假如只创建这个类的一个对象,就不必命名了,这种类被称为匿名内部类
class TalkClock {

    private int interval;
    private boolean beep;

    public TalkClock(int interval, boolean beep) {
        this.interval = interval;
        this.beep = beep;
    }

    public void start() {
       ActionListener actionListener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("At the tone, the time is " + new Date());
                if (beep) {
                    Toolkit.getDefaultToolkit().beep();
                }
            }
        }

        Timer timer = new Timer(interval, listener);
        timer.start();
    }

}

匿名内部类的语法格式:

new SuperType(参数) {
    inner class method and data;
}

其中SuperType可以是接口,那么内部类就需要实现这个接口;也可以是一个类,那么内部类就要扩展它。
构造一个类的新对象,与构造一个扩展了该类的匿名内部类的区别:

//构造了一个Penson对象
Person queen = new Person("Mary");
//构造了匿名内部类对象
Person count = new Penson("Dracula"){
    方法语句;
    ...
}

再参数的闭小括号后跟一个开大括号,正在定义的就是匿名内部类,匿名内部类不能有修饰符。
\color{red}{注意:}
\color{red}{匿名内部类是唯一一种没有构造器的类。}
\color{red}{正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。}

  1. 静态内部类
    有时候使用内部类只是为了把一个类隐藏再另外一个类的内部,并不需要内部类引用外围类的对象,此时就可以将内部类声明为static,以便取消产生的引用。
  • 在内部类不需要访问外围类对象的时候,应该使用内部类。
  • 与常规内部类不同,静态内部类可以有静态域和方法。
  • 声明在接口中的内部类自动称为static和public类。

对于普通内部类,必须先产生外围类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外围类的实例化对象即可产生内部类的实例化对象。

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

推荐阅读更多精彩内容

  • 转载:https://juejin.im/post/5a903ef96fb9a063435ef0c8 本文将会从以...
    福later阅读 397评论 0 3
  • 搞懂 JAVA 内部类 前些天写了一篇关于 2018 年奋斗计划的文章,其实做 Android 开发也有一段时间了...
    醒着的码者阅读 620评论 0 0
  • 第一次喝,我的小可爱还乱入了。
    陆洋阅读 171评论 0 0
  • 我的闺蜜y,高考之后去上海读了大学,之后就再也没产生过离开上海的想法。大学毕业后,她经历了几次跳槽,终于进了一家不...
    拉草莓的西瓜阅读 257评论 0 2
  • 最近在读佛法相关的书籍,进一步对因果文化在佛法中的因缘与果的照应,感到慰藉。只有对真正真理的追求,才可以让我们志存...
    友才927阅读 508评论 0 0