内部类

内部类的本质

1、内部类只是 Java 编译器的概念,对于 Java 虚拟机而言,它是不知道内部类这回事的,每个内部类最后都会被编译为一个独立的类,生成一个独立的字节码文件

2、也就是说,每个内部类其实都可以被替换为一个独立的类。当然,这是单纯就技术实现而言。内部类可以方便地访问外部类的私有变量,可以声明为 private 从而实现对外完全隐藏,相关代码写在一起,写法也更为简洁,这些都是内部类的好处

 

分类

1、定义在外部类的局部位置

(1)有类名:局部内部类

(2)无类名:匿名内部类

2、定义在外部类的成员位置

(1)无 static:成员内部类

(2)有 static:静态内部类

 

类加载顺序

1、外部类

(1)外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类

(2)实例化外部类、调用外部类的静态方法、静态变量,则外部类必须先进行加载,但只加载一次

(3)直接调用静态内部类时,外部类不会加载

2、内部类

(1)在初次调用的时候加载,且只加载一次

(2)延时加载的,只会在第一次使用时加载,不使用就不加载

 

局部 / 匿名内部类访问局部变量

1、要求:局部变量的生命周期 <= 局部内部类对象的生命期

2、JDK 8 版本之前,只能访问 final 修饰的局部变量

3、JDK 8(包括 JDK 8)版本之后,局部变量不需要用 final 修饰,实际是一个语法糖,底层仍加了 final,无法改变这个局部变量的引用值,如果改变则编译错误

4、原因

(1)Java 为了避免数据不同步,保护数据一致性,对引用变量来说是引用地址的一致性,对基本类型来说是值的一致性,即局部变量的生命周期与内部类的对象的生命周期的不一致性

(2)局部变量的生命周期 < 内部类对象的生命周期

(3)局部变量生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡

(4)内部类对象生命周期:当创建一个局部内部类对象后,只有当它没有被引用时,它才能死亡

(5)局部变量定义为 final 后,将所有的局部内部类对象要访问的 final 局部变量,都拷贝成为该内部类对象中的一个数据成员

(6)避免:外部方法修改引用,而导致内部类得到的引用值不一致;内部类修改引用,而导致外部方法的参数值在修改前和修改后不一致

5、局部 / 匿名内部类访问外部类的变量不需要 final

(1)内部类通常都含有回调,回调是双向调用模式

(2)内部类隐式链接到外部类,拥有外部类的所有成员的访问权

 

内部类中声明的所有静态字段都必须是 final,并初始化为一个编译时常量

1、内部类不能有 static 方法和变量(静态内部类除外)

(1)static 类型的属性和方法,在类加载的时候就会存在于内存中,要想使用某个类的 static 属性和方法,那么这个类必须要加载到虚拟机中

(2)内部类并不随外部类一起加载,只有在实例化外部类之后才会加载

(3)在外部类没有实例化,内部类还没有加载,这时如果调用内部类的静态成员,但内部类还没有加载,却试图在内存中创建该内部类的静态成员,这明显是矛盾的

2、非静态内部类(局部内部类、匿名内部类、成员内部类)可用 final static 修饰变量的原因

(1)final static 修饰变量,访问该变量不会导致类加载,避免所有 static 成员加载

(2)final static 修饰变量后,由于所赋值为字面常量,此时字面常量会在编译阶段确定值,将这样的常量称之为编译期常量,而编译期常量是不需要加载类的字节码文件的,这一步称之为编译期常量折叠

(3)编译期常量折叠:编译器在编译阶段通过语法分析计算出常量表达式的具体值

 

局部内部类

1、可直接访问外部类所有成员,包括 private 成员

2、属于局部变量,不能添加访问修饰符,但可以用 final 修饰

3、作用域:仅在定义它的方法或代码块

4、外部类可以在局部内部类的作用域中,创建、调用局部内部类对象

5、外部其他类不能访问局部内部类

6、外部类、局部内部类的成员重名,遵循就近原则

(1)局部内部类访问外部类成员,外部类名.this 指向一个调用了方法或代码块的外部类对象

外部类名.this.外部类成员名;

(2)局部内部类的方法参数与局部内部类的属性重名,遵循就近原则

(3)局部内部类访问自身属性

this.局部内部类属性名;

7、可以定义属性,属性可以用 final 或 final static 修饰 

8、示例

(1)源代码

public class Outer {
    private int a = 100;
    public void test(final int param){
        final String str = "hello";
        class Inner {
            public void innerMethod(){
                System.out.println("outer a " +a);
                System.out.println("param " +param);
                System.out.println("local var " +str);
            }
        }
        Inner inner = new Inner();
        inner.innerMethod();
    }
}

(2)内部实现

/*
    OuterInner类有一个实例变量outer,指向外部对象,在构造方法中被初始化
    对外部私有实例变量的访问,是通过Outer添加的方法access$O
    方法内部类可以访问方法中的参数和局部变量,这是通过在构造方法中传递参数实现
    OuterInner构造方法中有参数 int param,在新建OuterInner对象时,Outer类将方法中的参数传递给内部类,OuterInner inner = new OuterInner(this, param);
    String str 并没有被作为参数传递,这是因为它被定义为了常量,在生成的代码中,可以直接使用它的值
    这也解释了为什么方法内部类访问外部方法中的参数和局部变量时,这些变量必须被声明为final
    因为实际上,方法内部类操作的并不是外部的变量,而是它自己的实例变量,只是这些变量的值和外部一样,对这些变量赋值,并不会改变外部的值,为避免混淆,所以干脆强制规定必须声明为final
*/

public class Outer {
    private int a = 100;
    public void test(final int param) {
        final String str = "hello";
        OuterInner inner = new OuterInner(this, param);
        inner.innerMethod();
    }
    static int access$0(Outer outer){
        return outer.a;
    }
}

public class OuterInner {
    Outer outer;
    int param;
    OuterInner(Outer outer, int param){
        this.outer = outer;
        this.param = param;
    }
    public void innerMethod() {
        System.out.println("outer a " + Outer.access$0(this.outer));
        System.out.println("param " + param);
        System.out.println("local var " + "hello");
    }
}

 

匿名内部类

1、无类名,本质是类,同时是一个对象

2、定义、调用

new 类名/接口名(参数列表) {
    类体/接口体;
}.方法名();
类名/接口名 对象名 = new 类名/接口名(参数列表) {
    类体/接口体;
};
对象名.方法名;

3、可直接访问外部类所有成员,包括 private 成员

4、属于局部变量,不能添加访问修饰符,但可以用 final 修饰

5、作用域:仅在定义它的方法或代码块

6、外部类可以在匿名内部类的作用域中,创建、调用匿名内部类对象

7、外部其他类不能访问匿名内部类

8、外部类、匿名内部类的成员重名,遵循就近原则

(1)匿名内部类访问外部类成员,外部类.this 指向一个调用了方法或代码块的外部类对象

外部类名.this.外部类成员名;

(2)匿名内部类的方法参数与匿名内部类的属性重名,遵循就近原则

(3)匿名内部类访问自身属性

this.匿名内部类属性名;

9、不能定义任何静态成员,可以定义属性,属性可以用 final 或 final static 修饰

10、方法不能是抽象的,且必须实现接口或抽象父类的所有抽象方法,但可以有额外的方法

11、因为无类名,所以不能定义构造器,但可以使用代码块进行初始化,(参数列表) 是父类构造器的实参,完成对父类的初始化

12、可以定义内部类,可以对其他类进行实例化

13、只能使用一次,但创建的匿名对象可以在作用域中复用

14、应用场景:当作方法的实参传递

15、示例

(1)源代码

public class Outer {
    public void test(final int x, final int y){
        Point p = new Point(2,3){
            @Override
            public double distance() {
                return distance(new Point(x,y));
            }
        };
        System.out.println(p.distance());
    }
}

(2)内部实现

public class Outer {
    public void test(final int x, final int y){
        Point p = new Outer$1(this,2,3,x,y);
        System.out.println(p.distance());
    }
}
public class Outer$1 extends Point {
    int x2;
    int y2;
    Outer outer;
    Outer$1(Outer outer, int x1, int y1, int x2, int y2){
        super(x1,y1);
        this.outer = outer;
        this.x2 = x2;
        this.y2 = y2;
    }
    @Override
    public double distance() {
        return distance(new Point(this.x2,y2));
    }
}

 

(例)匿名内部类

interface A {
    void a();
}

class B {
    String name;

    public B(String name) {
        this.name = name;
    }
}

abstract class C {
    
    public abstract void c();
}

class Outer {
    
    public void example() {
        
        A a = new A() {
            @Override
            public void a() {
                //匿名内部类必须重写实现接口的抽象方法
            }
        };
        
        B b = new B("King") {//实参传递到父类构造器,初始化父类
            //匿名内部类的类体
        };
        
        C c = new C() {
            @Override
            public void c() {
                //匿名内部类必须重写实现父类的抽象方法
            }
        };
    }
}

1、基于接口的匿名内部类

(1)等价于

class Outer$1 implements A {
    @Override
    public void a() {
        //匿名内部类必须重写实现接口的抽象方法
    }
}

A a = new Outer$1();

(2)Outer$1 是底层分配的类名

(3)a:编译类型:A;运行类型:Outer$1

2、基于具体类的匿名内部类

(1)等价于

class Outer$2 extends B {
    //匿名内部类的类体
}

B b = new Outer$2("King");

(2)Outer$2 是底层分配的类名

(3)b:编译类型:B;运行类型:Outer$2

3、基于抽象类的匿名内部类

(1)等价于

class Outer$3 extends C {
    @Override
    public void c() {
        //匿名内部类必须重写实现父类的抽象方法
    }
}

C c = new Outer$3;

(2)Outer$3 是底层分配的类名

(3)c:编译类型:C;运行类型:Outer$3

 

成员内部类

1、可以直接访问外部类所有成员

2、只能定义非静态成员,但属性可以使用 final 或 final static 修饰

3、外部类通过创建对象,访问成员内部类的所有成员

4、外部其他类通过外部类对象,创建成员内部类对象,以访问成员内部类成员

(1)直接创建外部类对象,再创建成员内部类对象

外部类 外部类对象名 = new 外部类();
外部类.成员内部类 成员内部类对象名 = 外部类对象名.new 成员内部类();
外部类.成员内部类 成员内部类对象名 = new 外部类().new 成员内部类();

(2)在外部类创建一个方法,返回一个成员内部类对象

外部类 外部类对象名 = new 外部类();
外部类.成员内部类 成员内部类对象名 = 外部类对象名.方法名();
外部类.成员内部类 成员内部类对象名 = new 外部类().方法名();

5、成员内部类可以添加任意的访问修饰符

6、外部类、成员内部类存在重名成员,遵循就近原则

(1)成员内部类访问外部类成员,外部类名.this 指向一个外部类对象

外部类名.this.外部类成员名;

(2)成员内部类的方法参数与成员内部类的属性重名,遵循就近原则

(3)成员内部类访问自身属性

this.成员内部类属性名;

7、作用域:所在外部类的类体

8、示例

(1)源代码

public class Outer {
    private int a = 100;
    public class Inner {
        public void innerMethod(){
            System.out.println("outer a " +a);
            Outer.this.action();
        }
    }
    private void action(){
        System.out.println("action");
    }
    public void test(){
        Inner inner = new Inner();
        inner.innerMethod();
    }
}

(2)内部实现

/*
    Outers$Inner类有个实例变量outer,指向外部类的对象,它在构造方法中被初始化,Outer在新建Outers$Inner对象时给它传递当前对象
    由于内部类访问了外部类的私有变量和方法,外部类Outcr生成了两个非私有静态方法:access$0用于访问变量a,access$1用于访问方法action
*/

public class Outer {
    private int a = 100;
    private void action() {
        System.out.println("action");
    }
    public void test() {
        Outer$Inner inner = new Outer$Inner(this);
        inner.innerMethod();
    }
    static int access$0(Outer outer) {
        return outer.a;
    }
    static void access$1(Outer outer) {
        outer.action();
    }
}
public class Outer$Inner {
    final Outer outer;
    public Outer$Inner(Outer outer){
        ths.outer = outer;
    }
    public void innerMethod() {
        System.out.println("outer a " + Outer.access$0(outer));
        Outer.access$1(outer);
    }
}

9、应用场景

(1)内部类与外部类关系密切,需要访问外部类的实例变量或方法

(2)外部类的一些方法的返回值可能是某个接口,为了返回这个接口,外部类方法可能使用内部类实现这个接口,这个内部类可以被设为 private,对外完全隐藏

(3)LinkedList 类中,它的两个方法 listIterator 和 descendingIterator 的返回值都是接口 Iterator,调用者可以通过 Iterator 接口对链表遍历,listlterator 和 descendInglterator 内部分别使用了成员内部
类 Listltr 和 Descendinglterator,这两个内部类都实现了接口 Iterator

 

静态内部类

1、可以直接访问外部类所有静态成员,不能直接访问外部类的非静态成员,但可以通过创建一个外部类对象来间接访问  

2、既可以定义静态成员,也可以定义非静态成员

3、外部类创建对象访问静态内部类成员

4、外部其他类通过创建外部类对象,直接调用静态内部类

(1)直接创建外部类对象

外部类.静态内部类 静态内部类对象名 = new 外部类.静态内部类();

(2)普通方法,返回静态内部类对象

外部类 外部类对象名 = new 外部类();
外部类.静态内部类 静态内部类对象名 = 外部类对象名.方法名();
外部类.静态内部类 静态内部类对象名 = new 外部类().方法名();

(3)静态方法,返回内部类对象

外部类.静态内部类 静态内部类对象名 = 外部类.静态方法名();

5、静态内部类可以添加任意访问修饰符

6、外部类、静态内部类存在重名静态成员,遵循就近原则

(1)静态内部类访问外部类静态成员

外部类名.静态成员名;

(2)成员内部类的方法参数与成员内部类的属性重名,遵循就近原则

(3)成员内部类访问自身属性

this.成员内部类属性名;

7、作用域:所在外部类的类体

8、示例

(1)源代码

public class Outer {
    private static int shared = 100;
    public static class StaticInner {
        public void innerMethod(){
            System.out.println("inner " + shared);
        }
    }
    public void test(){
        StaticInner si = new StaticInner();
        si.innerMethod();
    }
}

(2)内部实现

/*
    内部类访问了外部类的一个私有静态变量shared,而私有变量是不能被类外部访问的
    Java的解决方法是:自动为Outer生成一个非私有访问方法access$O,它返回这个私有静态变量shared
*/

public class Outer {
    private static int shared = 100;
    public void test(){
        Outer$StaticInner si = new Outer$StaticInner();
        si.innerMethod();
    }
    
    static int access$0(){
        return shared;
    }
}
public class Outer$StaticInner {
    public void innerMethod() {
        System.out.println("inner " + Outer.access$0());
    }
}

9、应用场景

(1)与外部类关系密切,且不依赖于外部类实例

(2)Integer 类内部有一个私有静态内部类 IntegerCache,用于支持整数的自动装箱

(3)表示链表的 LinkedList 类内部有一个私有静态内部类Node,表示链表中的每个节点

(4)Character 类内部有一个 public 静态内部类 UnicodeBlock,用于表示一个 Unicodeblock

posted @   半条咸鱼  阅读(64)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示