内部类

内部类

a0d492c62fb589175f32dd41b3ec0c07.png
  • 定义:可以将一个类的定义置于另一个类的定义内部

  • 特点:(这些特点是所有类型的内部类都具有的)

    • [非静态内部类可以直接访问外部类的所有成员变量和方法]{.red}
    • [外部类不能够直接访问内部类成员变量和方法]{.red}
    • [内部类在外部创建对象必须依附外部类创建]{.red}
    • [所有类型的内部类都具有构造器(包括匿名内部类)]{.red}
  • 类型:① 成员内部类静态内部类局部内部类匿名内部类

    注:按照这个顺序分类是有原因的,Lambda 表达式就是这样通过内部类地不断演化得来的

  • 问题:

    • 为什么需要提供内部类机制?

      • [内部类使得多重继承的解决方案变得更加完善]{.red}:接口多重继承解决了部分多重继承的问题,内部类完善了多重继承方案

      • 内部类可以非常方便地隐藏实现细节,方便实现定义回调

        /* 这种类型的代码可以在 HashMap 中看到类似的 */
        class OuterClass{
        /*外部类只需要在内部创建内部类相应的引用就可以使用内部类的方法,从而也就可以使用内部类继承的父类的方法,实现多继承*/
        private class InnerClassA extends HashMap{}
        private class InnerClassB extends LinkedHashMap{}
        private InnerClassA getInnerClassAInstance{return new InnerClassA()};
        private InnerClassB getInnerClassBInstance{return new InnerClassB()};
        }
    • 为什么非静态内部类可以直接访问外部类的所有属性?

      • 编译器编译时 [平等对待]{.red} 外部类和内部类:为内部类和外部类都会生成相应的字节码文件,内部类字节码的文件名称为 OuterClassName$InnerClassName

      • 反编译字节码文件就可以看到内部类究竟是如何被初始化的:局部变量表中存在两个引用,一个是内部类自身的引用,另一个是外部类对象的引用

      • 编译器在编译时会在 [构造器]{.red} 中添加对于外部类对象的引用:[静态内部类访问不了的原因显然是因为本身不是对象了]{.red}

        f7584cb7b6d63810f7a0cc5cb0ed00ad.png
  • 细节:

    • 如果内部类存在和外部类名称相同的成员变量或者方法,[内部类的成员变量会 覆盖 外部类的成员变量]{.red}

      外部类属性被覆盖之后,显然内部类无法直接访问到外部类的属性,有两种方式可以提供访问

      OuterClass.this.number; /* 外部类.this.成员变量 或者 外部类.this.方法:可以访问到外部类对象 */
      private OuterClass getOuterClassInstance(){return new OuterClass()}; /*直接创建外部类对象后去引用成员变量或者方法*/
    • 内部类可以采用任何访问权限进行修饰:(方法内部类和匿名内部类除外)

      • 通常内部类不会是公开的,因为需要隐藏实现细节
      • 无论内部类是何种访问权限、普通方法和构造方法的访问权限如何,外部类都可以在 ++自身内部++ 创建内部类的对象并且对其进行访问

成员内部类

  • 定义:最普通的内部类

  • 特点:[成员内部类不可以拥有静态属性(静态变量、静态方法、静态代码块)]{.red}:可以拥有静态常量和普通常量、不可以变方法

    class OuterClass{
    private int number;
    private String string;
    class InnerClass{
    // 静态常量
    private static final int innerNumber = 10;
    // 不可变变量(常量)
    private final String innerString = "";
    // 成员变量
    public int publicNumber;
    // 不可以存在静态变量
    }
    }
  • 创建方式

    	

Java 核心技术卷一:》:成员内部类中不能够存在静态属性,但是 Java 语言规范没有对该限制做出任何解释

静态内部类

  • 定义:采用 static 关键字修饰的内部类

  • 特点:

    • [静态内部类仅能够访问外部类的所有静态属性]{.red}:静态内部类抹去了对于外部类对象的引用,所以是不可能访问到外部类的成员变量的
    • [静态内部类可以拥有静态属性和普通属性]{.red}
    • [接口中所有内部类都默认是静态内部类]{.red}
  • 创建方式:[内部类不能够是私有的]{.red}

    class OuterClass{
    static class InnerClass{

    }
    public void creatInnerClass(){
    // 外部类中创建内部类对象
    InnerClass innerClass = new InnerClass();
    }
    }

    public static void main(String[] args){
    // 其他类类中创建内部类对象
    OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
    }

局部内部类

  • 定义:位于 [方法作用域]{.red} 中的内部类

  • 特点:

    • [局部内部类本身不可以采用任何访问权限进行修饰]{.red}
    • [局部内部类仅可以在该方法中被访问,其他任何位置均不可以访问局部内部类]{.red}
    • 局部内部类可以访问方法提供的形参和方法内部定义的局部变量:[方法的形式参数和局部变量必须是不可变的]{.red}(Java 8 之后 [默认形参和局部变量]{.red} 就是不可变的)
  • 细节:局部内部类对应的字节码文件名称:OuterClassName$XInnerClassName

  • 创建方式:[仅可以在方法内部创建对象]{.red}

    // 默认形式参数是不可变的
    public void methodInnerClass(final int count){
    // 默认局部变量是不可变的
    int number = 10;
    // 局部内部类不可以采用任何关键字进行修饰
    class MethodInnerClass{
    public void method(){
    // 调用方法中的形式参数
    System.out.println(count);
    }
    }
    // 创建局部内部类对象并且调用方法
    new MethodInnerClass().method();
    }

  • 问题:为什么方法的参数或者局部变量必须是不可变的呢?

    • 普通方法和类的声明周期并不一致:类在加载阶段开始直到虚拟机关闭才销毁,方法在调用完成后就会被销毁

    • 形式参数和局部变量会随着方法被虚拟机销毁而跟着销毁,局部内部类/匿名内部类也就无法再次访问形式参数、局部变量

    • 为了避免生命周期不一致引发的数据访问问题:Java 采用 [复制]{.red} 来避免

      (1) [如果局部内部类访问的是局部变量,那么就将这个局部变量的值复制运行时常量池中,确保局部内部类能够访问]{.red}

      (2) [如果局部内部类访问的是形式参数,那么复制这个形式参数到局部内部类的构造器中进行初始化,确保局部内部类始终能够访问]{.red}

    • 实际上两种方式访问的局部变量、形式参数都不是原本的局部变量和形式参数,仅仅只是复制过来的内容,所以如果不增加 final 关键字加以修饰,就会导致 [数据不一致的问题]{.red}

匿名内部类

  • 定义:[没有名字的局部内部类]{.red}

  • 特点:

    • [匿名内部类需要实现相应的接口或者继承抽象类]{.red}
    • [匿名内部类不需要显式的声明]{.red}:
    • [匿名内部类可以借助代码块“迫使”编译器生成构造器]{.red}
  • 细节:

    • 匿名内部类只可以继承一个类或者实现一个接口
    • 匿名内部类对应的字节码文件名称:OuterClassName$XX 代表数字:通常根据数量来生成)
  • 创建方式:[仅可以在方法内部创建对象]{.red}

    interface AnonymousInnerClass{					// 匿名内部类实现的接口
    void method();
    }

    public void anonymousInnerClass(int count){
    /* 匿名内部类隐式的声明 */
    /*
    class AnonymousClass implements AnonymousInnerClass{
    public void method(){

    }
    }
    */
    // 实现接口创建匿名内部类对象
    new AnonymousInnerClass(){
    @Override
    public void method(){
    // 匿名内部类和局部内部类相同依然可以使用外部类的属性和不可变的形式参数
    System.out.println(count);
    }
    };
    // 继承类创建匿名内部类对象
    new ArrayList(){

    };
    }
成员内部类 静态内部类 局部内部类 匿名内部类
属性 [不可以拥有任何静态属性(静态常量除外)]{.red} 可以拥有任何属性 可以拥有任何属性 可以拥有任何属性
继承&实现 继承一个实现多个 继承一个实现多个 继承一个实现多个 [继承一个或者实现一个]{.red}
外部类 直接访问外部类属性 [仅能访问外部类静态属性]{.red} 直接访问外部类属性 直接访问外部类属性
构造器 [有]{.red}
作用域 外部类内部 任意位置 仅能在方法中被访问 仅能在访问中被访问
访问权限 任意访问权限 任意访问权限 [不可以修饰]{.red} [不可以修饰]{.red}
Author: Fuyusakaiori
Link: http://example.com/2021/09/07/java/interface/内部类/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.