Java梳理之理解内部类

今天整理一下内部类,其中包含了内部类的特殊形式,对比普通类有什么区别和作用,内部类和外围类之间的联系,内部类的扩展和被扩展,内部类编译后文件的标识符。文章中有任何错误或遗漏请告知博主~

前置概念

嵌套类型:将一个类innerClass或者接口innerInterface的定义放在另一个类outterClass或者接口outterInterface定义的内部,那么类innerClass就是内部类、outterClass则是外围类,类似的接口innerInterface为嵌套接口,接口outterInterface为外围接口。

从这里可以看出嵌套类型之间的区别取决于内部类型是一个类还是一个接口,以及它的外围类型是一个类还是一个接口。内部类型可以是静态的,也可以不是:前者允许简单的类型结构,后者定义了它和包围类对象之间的特别关系。
例如:

class OutterClass{
    class InnerClass{
        private String name;
    }
    interface InnerInterface{
        String getName();
    }
    static class InnerClass2{
        String name;
    }
}
interface OutterInterface{
    class InnerClass{
        private String name;
        String getName(){
            return name;
        }
    }
    interface InnerInterface{
        void getName();
    }
}

在这篇文章中,从最常见的类中嵌套类开始理解整个嵌套类型~

内部类

知道了内部类的概念之后,可以根据类的各种形式得到一些特殊的内部类,其中包括,静态内部类,局部内部类,匿名内部类。在对整个内部类应该有个大体的认识后,在这里抛出疑问,为什么需要内部类,仅仅是因为定义在另一个类的内部么?
其实问题在《Thinking in Java》中有早有说明:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类来说都没有影响。这句话说明了它可以提供一个功能,即,使用内部类后,外围类可以得到类似于多继承的能力。但不仅于此,使用内部类还可以获得其他一些特性:

1.内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类的对象信息相互独立。
2.在单个外围类中,可以用多个内部类以不同的形式实现同一个接口或继承同一个类。
3.内部类属于外围类的一个轻量级可选组件。
4.内部类提供了更好的封装效果,只能依靠外围类来访问。
注:《Thinking in Java》中写的几条特性里面只选了前面两条,并去掉其中第三条换了第四条,如果有不同理解可以一起讨论。

大体上,内部类的作用已经被明确了,但是我们并不清楚定义内部类是如何和外围类进行联系的,下面可以看一下内部类和外围类的联系机制。

内部类与外围类的联系

尽管我们知道内部类的作用和带来的特性,但是并不清楚它是如何做到这些的,例如它能提供类似于多继承的机制,但是内部类和外围类又有什么关系?如果无法将内部类和外围类联系在一起,那么内部类仅仅只是写在类定义里面的一个普通类,无法带来它特殊的作用。如下代码:

/**
***内部类与外围类的联系
**/
public class InnerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.new InnerClass();
        System.out.println("--inner.getName = "+inner.getName());
    }

}
class OutterClass{
    private String name = "OutterClassName";
    class InnerClass{
        public String getName(){
            return name;
        }
    }
}   
输出:
--inner.getName = OutterClassName

可以看到,内部类本身并没有定义name字段,它的方法是得到name字段的值,输出的name字段的值是外围类的name字段的值,那么外围类和内部类之间必然是有关联的,这种关联就是内部类特性和作用的关键。在上面的例子中,内部类自然拥有了外围类成员的访问权限,这是如何做到的呢?其实在代码中可以看到,内部类InnerClass实例inner的创建是通过其外部类实例outter.new出来的,那么内部类对象是有机会获取外围类对象的引用的。其实事实上也确实如此,在访问外围类成员的字段时,就是通过捕获的外围类对象引用(非static的内部类)这个引用是限定-this的引用(这个引用强调了一个概念:内部类对象是和包围类对象紧密绑定在一起的,这也是同一个外围类对象实现的一部分),通过这个引用可以访问它的包围类的所有成员,包括私有的。包围类对象也可以访问它内部类对象的私有成员,但只能先声明字段才能访问。

知道了内部类和外部类的联系后,就可以灵活使用内部类语法规则来完成我们的设计,下面来看内部类的使用。

内部类的使用

创建

因为内部类提供了更好的封装性,我们只能通过它的外围类来访问它,那么怎么在外围类外部创建内部类对象呢?在没有使用内部类的时候,我们创建一个类型的实例时,通常选择使用new关键字,但是在内部类这里,会发现new关键字只有在外围类内部才起作用,而在外围类之外是无法new出来的,其实这也是和内部类与外部类的联系有关,内部类既然要自然拥有访问外围类实例的权限,自然要与外围类实例联系在一起,所以需要如上例所示通过外围类实例使用new创建:OutterClass.InnerClass inner = outter.new InnerClass();
当然这种特别的new的方式不是唯一解,我们也可以选择另一种创建方式:

/**
***  .this创建
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.getInnerClass();
        System.out.println("--inner.getName = "+inner.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    class InnerClass{
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            return name;
        }
    }
}
输出:
--inner.getName = OutterClassName

在这个例子中,我们直接通过方法返回一个内部类,在这个内部类中使用.this来绑定它的外围类对象,输出的结果也是正确的。

总结一下,有两种方式在外部创建一个类的内部类,一种是.this,另一种是.new

继承

在之前,我们的内部类一直是一个基类。很多时候,我们使用的内部类,需要继承一个已经存在的类,这个类中存在了一些基本的实现。既然是继承那么内部类的继承也是有选择性的,它可以继承一个普通类,也可以继承一个其他类的内部类。在继承普通类时,就像一般的类,但是如果它要继承另外一个内部类,如下:

/**
***内部类继承内部类
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.getInnerClass();
        System.out.println("--inner.getName = "+inner.getName());
    }
}
class OutterClass extends OutterClass2{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    class InnerClass extends InnerClass2{
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            return name;
        }
    }
}
class OutterClass2{
    private String name = "OutterClassName2";
    public InnerClass2 getInnerClass(){
        return new InnerClass2();
    }
    class InnerClass2{
        public OutterClass2 getOutterClass(){
            return OutterClass2.this;
        }
        public String getName(){
            return name;
        }
    }
}
输出:
--inner.getName = OutterClassName

在这里可以看到InnerClass继承了InnerClass2,程序也是正确的运行的,当我把InnerClass类的外围类OutterClass的继承关系去掉,就会提示错误,这说明,如果一个内部类要继承另外一个内部类,那么需要它的外围类也继承它要继承的内部类的外围类,即InnerClass要继承InnerClass2,则OutterClass要继承OutterClass2

把内部类继承内部类说完之后,再看一下,外部类继承包围类的一个内部类,例如:

/**
***外部类继承内部类
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        Unrelate unrelate = new Unrelate(new OutterClass());
        System.out.println("unrelateName = "+unrelate.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    class InnerClass{
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            return name;
        }
    }
}
class Unrelate extends OutterClass.InnerClass{
    Unrelate(OutterClass outter){
        outter.super();
    }
}
输出:
unrelateName = OutterClassName

在这里,如果直接class Unrelate extends InnerClass会报错,因为它的超类InnerClass是一个内部类,需要关联一个外围类,所以正确的写法是class Unrelate extends OutterClass.InnerClass,尽管Unrelate类不是一个内部类,也不是一个外围类,但是还是需要给它传入一个外围类对象绑定。而Unrelate对象的使用和其他普通类并没有什么不同。

作用字段,继承和隐藏

在这里整体的分析一下内部类的作用情况。
首先,内部类获取了它外围类的引用,所以外围类的所有字段和方法都是可以使用的,术语叫作 在作用字段内。但是,内部类自己也存在的字段和方法,如果内部类的字段和方法和外围类的字段方法名一样,会不会造成冲突?代码如下:

/**
***内部类作用字段
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass outter = new OutterClass();
        OutterClass.InnerClass inner = outter.new InnerClass();
        System.out.println("--getName = "+inner.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public InnerClass getInnerClass(){
        return new InnerClass();
    }
    public String getName(){
        System.out.println("--OutterClass-getName-");
        return name;
    }
    class InnerClass{
        private String name = "InnerClassName";
        public OutterClass getOutterClass(){
            return OutterClass.this;
        }
        public String getName(){
            System.out.println("--InnerClass-getName-");
            return name;
        }
    }
}
输出:
--InnerClass-getName-
--getName = InnerClassName

这里可以看到,输出的结果和之前在InnerClass中没有name字段时不一样,inner使用了它自己的字段name,而属于外围类的字段name和方法getName()被隐藏了,其实隐藏外围类的情况共有两种:
1.内部类有自己的字段和方法。
2.内部类的父类有字段和方法。

在这两种情况下,任意的简单名用法,都会直接引用内部类成员。因为会出现隐藏的问题,所以在内部类内使用外围类的字段和方法的时候,建议使用.this来限定,例如:OutterClass.this.name

把一般的内部类说完之后,我们看一下几种特别的内部类:

静态内部类

定义:把一个静态类定义在另一个类定义中,就是静态内部类(嵌套类),和普通的内部类相比,定义时使用了static关键字。
还记得之前,普通的内部类对象会在内部捕获并保存它外围类对象的引用,但是,静态内部类不是这样的。静态内部类本身就是外围类的一个成员,而不是一个独立的对象存在的。因为没有保存它外围类对象,所以静态内部类的创建不依赖于外围类的引用,也没有自然获取的外围类各种字段方法的权限。 例如:

/**
***静态内部类
**/
public class innerClassDemo {
    public static void main(String[] arg0){
        OutterClass.InnerClass2 inner = new OutterClass.InnerClass2();
        System.out.println("--getName = "+inner.getName());
    }
}
class OutterClass{
    private String name = "OutterClassName";
    public String getName(){
        System.out.println("--OutterClass-getName-");
        return name;
    }

    static class InnerClass2{
        private String name = "InnerClassName2";
        public String getName(){
            System.out.println("--InnerClass2-getName-");
            return name;
        }
    }
}

InnerClass类添加static关键字,使得它变成一个静态内部类,那么使用OutterClass.this.name的时候会提示错误,说明无法访问外围类的非静态字段

局部内部类

定义:定义在外围类的内部代码块中,它不时外围类的一部分,但是能访问当前代码块内的变量和所有的外围类的成员,作用的范围类似于局部变量只在当前代码块内部,因为外部没有任何引用它的路径。
局部内部类可以访问定义的该类作用字段中的所有变量,包括代码块中的局部变量,外围类的所有成员和局部内部类的所有字段,但是代码块中的局部变量或方法参数只能声明成final才能被访问。这是多线程的问题访问保证安全性的措施,而且这样的好处是值是确定的。在局部内部类中,也会存在普通内部类存在的隐藏字段方法等问题,如果隐藏的是代码块的局部变量,那么久没有办法来访问这个被隐藏的变量了。

匿名内部类

定义:扩展了某个类或实现了某个接口的的匿名类。
匿名内部类没有显示声明的构造器,但是可以使用初始代码块来初始化。虽然匿名类使用起来很简单,但是会降低代码的可读性。

接口中的嵌套

虽然可以使用,但是目前来说,接口作为一个行为协议,尽量不要在内部书写除协议本身以外的东西。
接口中嵌套类或者接口,本质上和类中嵌套类一样,只是把一个与接口联系紧密的类型关联到这个接口的内部。例如:

interface OutterInterface{
    class InnerClass{}
    interface InnerInterface{}
    InnerClass getInnerClass();
    InnerInterface getInnerInterface();
}

这样,内部的接口或者内部的类就和外围接口紧密的绑定在一起。注:任何在接口中定义的类都是publicstatic

类中嵌套接口

其实这个是很少很少用,目前来说,我还没有见过,但是说明一下。它们在类中起到的作用仅仅只是组织相关类型的机制。由于接口没有实现,所以它不能是非静态的,默认的static关键字省略。

内部类标识符

一般来说,我们的Demo.java文件编译后文件名为Demo.class文件,但是如果java文件内部包含了内部类,那么文件会将内部类分出一个class文件,内部类的文件名为外围类名$内部类名,例如Outer实体中还有Inner类,那么编译出来后,会存在Outer.class文件和Outer$Inner.class文件。那么如果是个匿名内部类呢,它本身就没有名字,这种情况下,编译器会简单的产生一个数字作为它的标识符。例如Outer$1.class

写到这里已经把内部类给写完了,虽然后面部分只用文字描述了一下,还有一些东西没有写在上面,具体的可以再去看看研究研究,比如匿名内部类和局部内部类的区别等。

本文参考《Thinking in Java》第10章内部类,《Java程序设计语言》第5章 嵌套类和接口

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,581评论 18 399
  • 第一章 对象导论 对象具有状态、行为和标识。这意味着每一个对象都可以拥有内部数据和方法,并且每一个对象都可以唯一地...
    niaoge2016阅读 816评论 0 0
  • 以前的我懵懵懂懂,没有思想;以前的我一分钟刷一次朋友圈,每天在扣扣空间里发一些无聊甚至于抱怨的话;是所有男孩眼...
    雪狐白糖阅读 202评论 0 0
  • 秋天将至,万物呈现出凄凉衰败,北方的候鸟已经开始飞往南方避冬。在这样一群南飞的雁群中,有一对新结发的雁夫妇,公雁我...
    BlueCute阅读 149评论 0 1
  • 今天是你走的第九天,还有104天。我想你,今天心情不好但是和你聊天就好一点了。回来可以看到你是我最开心的事。 昨天...
    rainll阅读 138评论 0 0