内部类

  2018-4-17 


JAVA内部类

内部类对象类型:OuterClassName.InnerClassName

  1. 内部类访问外部类

    内部类拥有外部类一切元素的访问权

    本质:当某个外部类对象创建了一个内部类对象的时候,此内部类对象必定会秘密地捕获一个指向那个外部类对象的引用,然后在你访问此外部类成员的时候,就是用那个引用来选择外部类成员

    对外部类对象的引用可以用OuterClassName.this

  2. (在其它地方)告知某外部类去创建其自己的内部类对象:.new语法(易错)

    这个方法也适合多重嵌套内部类,.new能限定正确的作用域

    //外部类对象outer
    OuterClassName outer = new OuterClassName();
    //【通知外部类对象outer去建立其内部类对象】,注意这里的用法!
    OuterClassName.InnerClassName inner = outer.new InnerClassName()
        
  3. 拥有外部类对象之后,才能创建内部类对象

  4. 内部类的一大用途:与接口配合使用——工厂方法(内部类Ver.最常用)

    阻止任何依赖于类型的代码,并且完全隐藏了实现的细节。我们并不需要知道是什么类实现了接口,我们只要根据接口的描述去使用这个新生成的对象(引用类型为接口类类型)即可,这个对象通过工厂类里的实现了该接口的内部类生成

    举例如下:其中有接口SingDance,于是我建立了一个工厂类Factory,在里面通过内部类SingerDancer实现了SingDance接口(显然这么设计是因为Singer当然会Sing,只要一个对象满足Sing接口的要求,那它就可以是个Sing接口类型的对象,其子类型为Singer)。然后我在工厂类里提供两个供外部使用的方法createSingercreateDancer,其返回SingDance接口类型的对象,在这里在函数中返回的实际上是接口类型的子类对象SingerDancer,于是就经过了自动的向上转型为接口类型。在外部看来,该函数返回的就是符合接口的对象,并不关心也没法知道具体细节,只需要根据接口定义来使用该对象即可。

    package aisaka;
    interface Sing
    {
        boolean sound = true;
        void singing();
    }
    
    interface Dance
    {
        boolean dance = true;
        void dancing();
    }
    
    class Factory
    {
        //私有内部类,禁止外部访问,实现了Sing接口
        private class Singer implements Sing
        {
            String name;
            Singer(String name)
            {
                super();
                this.name = name;
            }
            @Override
            public void singing() {
                System.out.println(name + ":A Singer is singing");
            }
        }
    
        //私有内部类,禁止外部访问,实现了Dance接口
        private class Dancer implements Dance
        {
            String name;
            Dancer(String name)
            {
                super();
                this.name = name;
            }
            @Override
            public void dancing() {
                System.out.println(name + ":A Dancer is dancing");
            }
        }
        //提供外部创建符合Sing接口的对象
        public Sing createSinger(String name)
        {
            return new Singer(name);
        }
        //提供外部创建符合Dance接口的对象
        public Dance createDancer(String name)
        {
            return new Dancer(name);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Factory factory = new Factory();
            Sing singer1 = factory.createSinger("世界第一歌手aisaka");
            Dance dancer1 = factory.createDancer("世界第一舞者aisaka");
            singer1.singing();
            dancer1.dancing();
        }
    }
    /*OUTPUTS
    * 世界第一歌手aisaka:A Singer is singing
    * 世界第一舞者aisaka::A Dancer is dancing
    */

    不过注意工厂类内的东西不能为static的

  5. 更多内部类的使用方法

    作用:你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的

    局部内部类:定义在 private方法/任意作用域内中 的内部类

    注意,公有方法或默认方法中不能定义局部内部类

    局部内部类可以访问当前代码块的常量,和外围类的所有成员

    匿名内部类

    当一个子类继承/实现父类的时候,一种简化不需要给子类命名的一种表达式

    注意新建对象的时候就不是写那个继承父类的那个匿名内部类的引用了(因为匿名了,本来也没有),而是写父类对象引用

    //将上面的工厂方法中,实现接口的类写作工厂类的匿名内部类
    //这里直接在工厂类中的生成对象方法里写内部类,就不需要单独写个内部类实现接口了,而是新建对象的时候直接写在匿名内部类里
        public Dance createDancer(String name)
        {
            //注意这里是new一个Dance接口类型而不是Dancer实现类型,实际上这里的意思是创建一个继承/实现自Dance的匿名类的对象
            return new Dance()
            {  //内部类开始{
                @Override  
                public void dancing() {
                    System.out.println("A Dancer is dancing");
                }
            }; //内部类结束};
        }
    

    匿名内部类不能有构造器;

    对于需要构造器的的匿名内部类,a. 对于传参数到构造器:只需要简单地将参数直接传递给基类(父类)构造器即可 b.对于想要在构造器初始化:用初始化块替代

    new Dance(x){内部类};

    但注意,接口没有构造器,所以匿名内部类只有继承的时候才有构造方法,实现接口不能有构造方法!

    显然匿名内部类只能实现一个接口or继承一个类;如果要在同一个类中继承两个类,那就只能通过匿名内部类的方式继承(最多继承一个类,多个接口)

    匿名内部类想要使用外部定义对象的时候,需要该参数引用是final

    ③静态内部类(嵌套类):

    如果不需要内部类对象与其外围类对象之间有联系,那么就可以将内部类声明为static,称作嵌套类

    要创建静态内部类,不需要先实例化外围类对象!,且静态内部类只能访问外围类对象中的final方法(与静态方法、静态成员一样,都不需要实例化外围类对象)

    嵌套类可以放接口中,接口中的任何内部类都是自动public static

  6. 内部类是面向对象的闭包

    闭包是一个可调用对象,它记录了一些信息,这些信息来源于创建它的作用域。

    如上面的工厂方法,工厂类中的新建对象函数返回了一个引用,这就是回调,这个引用就是“钩子”,这个钩子很安全,只能调用其实现的接口给的方法。

    根据工厂方法的原理,可以看出回调的价值在于可以在运行时动态地决定调用什么方法

  7. 内部类的继承问题

    当继承某个外围类的时候,子类中新建一个与父类内部类重名的内部类,这两个内部类是共存的,各自在各自的命名空间互不影响;如果明确直接继承某个内部类,那调用子类的时候就是调用该子类了

    内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,导出类不再存在可以连接的默认对象,这时候就需要将外围类对象传入这个子类构造函数中并用outerObject.super()调用其构造器

    class WithInner {
      class Inner {}
    }
    
    public class InheritInner extends WithInner.Inner {
      //! InheritInner() {} // 这样写就报错了
      InheritInner(WithInner wi) { //注意这里,在子类的构造函数中传入了继承内部类的外围类对象
        wi.super(); //继承外围类构造器
      }
      public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
      }
    } 
  8. 内部类标识符

    每个类都会产生.class文件,内部类则WithInner$Inner.class


且听风吟