【java基础整理】2-内部类

1.内部类的定义和性质

  内部类,顾名思义,就是在一个类里面定义一个类,但是内部类的一个特殊之处在于,它能够获取到其外部类对象的所有成员,包括private字段
  这时因为当外部类对象在创建一个内部类对象的时候,编译器又开始悄悄咪咪的把外部类对象的引用传入给内部类对象,于是内部类就可以通过这个外部类对象的引用对外部类的所有成员进行访问。在内部类中,可以通过外部类类名.this的方式获取到外部类对象的引用,见下面的例子。
  基于这个原因,在创建内部类对象的时候,不能使用外部类类名进行创建,而必须使用外部类的对象来进行创建。下面是栗子:

public class Outer{
    public class Inner{
        public Outer getOuter(){
            //可以在内部类中通过外部类类名.this获取到外部类的引用
            return Outer.this;   
        }
    }

    public static void main(string[] args){
        //编译不通过,不能通过外部类类名创建内部类,因为Inner非静态
        //Inner inner = new  Outer.Inner(); 

        //编译通过,通过外部类对象创建内部类
        Outer outer = new Outer();
        Inner inner = outer.new Inner();     
    }
}

  这个时候你是不是隐约感觉到了什么不对!在java基础整理1当中我们提过,static关键字可以使类的成员不再与单个具体对象相互关联,而是与类本身关联,那么我们如果用static关键字对内部类进行修饰会怎么样呢?答案是在创建一个static的内部类时,你并不需要一个外部类对象,这时我们称这个内部类为嵌套类(或静态内部类),下文中static内部类我将一律以嵌套类(或静态内部类)对其进行称呼,而内部类则指代非static的内部类

2.匿名内部类

  在匿名内部类之前,我们首先了解一下在方法和作用域中的内部类

  在方法中定义的内部类其实很好理解,即在方法体当中,内部类可以访问,如下

//在方法中定义的内部类
public class InnerClassInMethod {
    public interface Destination{
        String readLabel();
    }

    //一个返回Destination的方法
    public Destination methodA(String s){
        //在其中定义一个内部类, 该内部类仅可在methodA(String s)的方法体中访问
        class InnerDestination implements Destination{
            private String label;
            private InnerDestination(String dest){
                label  = dest;
            }
            public String readLabel(){
                return label;
            }
        }
        return new InnerDestination(s);
    }
}

  在作用域中定义的内部类与在方法中定义类似,在定义内部类的整个作用于当中,内部类都是可以访问的

//在作用域中定义的内部类
public class InnerClassInField {
    public interface Destination{
        String readLabel();
    }

    //一个返回Destination的方法
    public void methodB(Boolean b){
        if(b){
            //在其中定义一个内部类, 该内部类仅可在methodA(String s)的方法体中访问
            class InnerDestination implements Destination{
                private String label;
                private InnerDestination(String whereTo){
                    label  = whereTo;
                }
                public String readLabel(){
                    return label;
                }
            }
            //可以在if()的作用域中对内部类进行调用
            InnerDestination innerDestination = new InnerDestination("在作用域中可以调用");
        }
        
        //在这里就不能对内部类进行调用
        //InnerDestination innerDestination = new InnerDestination("在作用域外则不能调用");
        
    }
}

匿名内部类则是以上情况的简化形式,当匿名内部类需要使用一个从外部定义的对象,那么这个对象必须声明为final的,否则会出现编译时错误消息,下面我们将InnerClassInMethod.java改变为匿名内部类的

//InnerClassInMethod.java的匿名内部类形式
public class AnonymousClassInMethod {
    public interface Destination{
        String readLabel();
    }

    //一个返回Destination的方法
    public Destination methodA(final String s){
        //使用匿名内部类返回Destination对象,因为需要使用外部String对象s,因此s被定义为final
        return new Destination() {
            private String label = s;
            @Override
            public String readLabel() {
                return label;
            }
        };
    }
}

3.嵌套类(静态内部类)

  前面我们在第1部分中最后有提到,在创建内部类时,外部类对象会将自己的引用传给内部类持有。但是当我们用static关键字修饰内部类时,内部类对象就不会与外部类的对象相联系,即不再持有外部类对象的引用,这时该内部类我们称之为嵌套类
  嵌套类有几个性质:

  • 创建嵌套类时不需要外部类的对象
  • 嵌套类不能访问外部类的非静态对象
  • 嵌套类可以包含static数据和字段,而普通的内部类不能拥有这些

因为嵌套类不具有对外部类对象的引用,以上性质就比较好理解了

4.为什么要用内部类(重点!!!)

  由于java的继承机制使得子类只能具有一个父类,这个限制会使得一些编程问题变得难以解决
  但是内部类的存在使得这个问题变得容易起来。首先我们思考一下内部类的特点,往往在我们的编程当中,外部类可以继承某个类或某个接口,内部类也可以独立的继承某个类或某个接口,而同时由于内部类能够访问其外部类的对象,因此内部类能够访问自己所继承的以及其外部类对象所继承的所有方法,于是该内部类可以看做继承了两个类。因此实际上,内部类是一种多重继承的实现方式

以下介绍两种内部类的应用场景

a. 回调 + “钩子”
/** 一个带有increment()方法的接口*/
interface Incrementable{
    void increment();
}

/** 第一个回调类,实现Incrementable接口 */
class Callee1 implements Incrementable{
    private int i = 0;
    @Override
    public void increment() {
        i++;
        System.out.println(i);
    }
}

/**一个同样含有increment()但做着不同工作的类*/
class OtherOptionClass{
    public void increment(){
        System.out.println("other operation");
    }
}

/**
 * 第二个回调类
 * 若因为某些原因必须继承OtherOptionClass,但又想实现Incrementable
 * 由于两者都具有increment()方法,则可以通过内部类来实现
 */
class Callee2 extends OtherOptionClass{
    private int i = 0;
    /** 继承并覆盖OtherOptionClass中的increment方法 */
    @Override
    public void increment(){
        super.increment();
        i++;
        System.out.println(i);
    }

    /** 定义内部类,内部类实现Incrementable接口 */
    private class Closure implements Incrementable{
        @Override
        public void increment() {
            /** 调用外部类的方法 */
            Callee2.this.increment();
        }
    }

    /** 定义一个方法以获取到Closure对象 */
    Incrementable getCallbackReference(){
        return new Closure();
    }
}

/** 调用者类,需要通过传入Incrementable对象来构建实例,在适当的时候调用其方法,即回调 */
class Caller{
    Incrementable callBackReference;
    Caller(Incrementable cbr){
        callBackReference = cbr;
    }
    /** 在方法中执行回调 */
    void go(){
        callBackReference.increment();
    }
}

/** 主类 */
public class Callbacks {
    public static void main(String[] args) {
        /** 创建两个被回调对象 */
        Callee1 c1 = new Callee1();
        Callee2 c2 = new Callee2();
        /** 构建两个调用者对象,c1传入自身,c2传入自己的内部类*/
        Caller caller1 = new Caller(c1);
        Caller caller2 = new Caller(c2.getCallbackReference());
        /** 在方法中执行回调 */
        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
    }
}

/** Output:
1                                  ->caller1.go();
2                                  ->caller1.go();
other operation                    ->caller2.go();
1
other operation                    ->caller2.go();
2
*/

如上例,构建Callee对象,将其作为构造参数传入Caller对象中,这样可以让Caller对象在恰当的时候决定操作Callee类,这就是程序设计时常用到的回调
在这个例子当中,定义了两个回调类,第二个回调类需要继承一个类和一个接口,但是它们具有不同作用但具有相同方法标签的increment()方法,这个时候,我们可以考虑使用内部类对接口进行继承,并在接口方法实现时调用外部类Callee2的increment()方法,从而达成了外部类Callee2对类和接口的increment()同时进行了继承
在本例中内部类Closure仅可通过Callee2获取,通过它可以转发调用Callee2对象的increment()方法,这被称为钩子,另外,在该类的外部获取到内部类Closure,也仅能通过它调用Callee2对象的increment(),因此这个是个安全的钩子。

b. 内部类与控制框架

控制框架如控制一个温室的运行:控制灯、水、温度的开关,响铃等操作。

  • 首先可以定义一个模板事件类Event,在这个类中具有一个抽象方法action(); (应用模板模式
  • 然后搭建框架抽象类Controller,该类维护一个Event列表,可以添加删除Event,并通过run()方法逐条执行Event。
  • 实现具体的框架类GreenHouseControls,具有私有变量light、water、temperate等,并定义多个内部类如LightOn、LightOff等继承自Event,实现各自的action()方法 (应用命令模式,不同的Event自行决定如果和实现
  • 实现一个main()方法,创建GreenHouseControls实例,操作之~

这个例子虽然也可以不用内部类实现,例如可以单独定义多个操作类继承自Event,然后在构造方法中传入具有light、water、temperate等的GreenHouseControls对象,通过这个对象引用修改其参数。
但是如果使用内部类,就可以将这些所有的细节全部封装起来,此外内部类能够很容易访问外部类GreenHouseControls的变量

该例子详情请见《java编程思想第四版》p207 - p211

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 一. Java基础部分.................................................
    wy_sure阅读 3,805评论 0 11
  • 说话是一门艺术活,都说“良言一句三冬暖,恶语伤人六月寒”,话说得好,能够大事化小,小事化了,干戈化玉帛;话说得不好...
    好听的暖阳阅读 189评论 5 6
  • 与人,与事,切不可浮躁,莫总想以己之心,度他人之心?万事自有规律,众生难逃芸芸之力。 你改变不了什么的话,要么...
    Mark_Per阅读 201评论 0 0