Java内部类

1 概述

1.1

Java内部类,是JVM为实现“闭包式对象封装”而设计的完整对象模型。每一个非静态内部类实例,都隐式持有一个外部类实例的引用,这个引用在编译期被注入为合成字段,并在构造时绑定。

1.2

Java内部类共分四类:‌成员内部类、静态内部类、局部内部类、匿名内部类‌。每一种,都对应不同的JVM行为。

非静态内部类不能独立存在‌ —— 它必须依附于外部类实例;

静态内部类是独立的嵌套类‌ —— 它不持有外部引用,行为类似顶级类;

匿名类是局部类的无名版本‌ —— 它的类名由JVM动态生成,但本质仍是内部类;

所有内部类都会生成独立的 .class 文件‌ —— JVM加载时,它们是‌完全独立的类‌,只是命名被编译器特殊处理。

2 局部内部类

内部类必须通过外部类实例创建

外部类不能访问内部类的属性和方法

内部类不能访问外部类的属性和方法

2.1 示例代码

public class InnerDemo1 {
    public static void main(String[] args) {
        //必须通过外部类实例创建
        Car.Engine carEngine= new Car().new Engine();
        carEngine.show2();
    }
}


class Car{
    /*
    final Car this$0;
    Car$Engine(Car car) {
        this.this$0 = car; // 构造时绑定外部实例
        super();
    }
    */

    String carName;
    String carColor;
    String quality;
    private String carDoor;
    public void carRun(){
        //编译错误
        String a=engineType;
    }
    //通过外部类实例创建
    public Engine createInner(){
        return new Engine();
    }
    //成员内部类如同类的成员
    class Engine{
        String engineType;
        String quality;
        public void accessOuter() {
            //显式获取外部类引用
            //Car.this即this$0
            Car thisOuter = Car.this; 
        }
        public void show1(){
            String b=carName+engineType;
            carRun();
        }
        public void show2(){
            String c=Car.this.carName+this.engineType;
            Car.this.carRun();
        }
        public void showQuality(){
            String d1 = quality;//等同于String d1 = this.quality;
            String d3 = Car.this.quality;
        }
        public void showPrivateFieldCarDoor(){
            String e = carDoor;
        }
    }
}

2.2 从JVM层面理解示例代码

编译后生成两个文件

Car.class
Car$Engine.class

Car$Engine.class 的字节码中,编译器自动添加了一个合成字段‌this$0,并且重写构造方法

final Car this$0;// 编译器自动生成的引用字段

Car$Engine(Car car) {
    this.this$0 = car; //构造时绑定外部实例
    super();
} 

堆内存布局:

[Car实例] 
[Engine实例] ────> this$0 ────────┐
                 (引用)           │
                                  ▼
                             [Car实例] ←─ 通过 this$0 可访问所有外部成员

2.3 注意的问题

‌1、不能声明静态成员‌(除 static final 常量)

class Inner {
    static int x = 10; // 编译错误:内部类不能有静态成员
    static final int Y = 20; // 允许:编译时常量
}
  • 因为静态成员属于类级别,而内部类依赖于外部实例,JVM无法为“依赖实例的类”分配类加载时的静态存储区。

2、访问外部类私有成员‌

内部类可以访问外部类的 private 字段和方法。JVM层面,编译器会生成‌桥接方法(synthetic accessor)‌:

        //访问Car的私有成员变量
        public void showPrivateFieldCarDoor(){
            String e = carDoor;
        }
        // 编译器自动生成:
        static String access$000(Car car) {
            return car.carDoor;
        }
  • 调用Engine().showPrivateFieldCarDoor() 实际里面有调用access$000(this$0),绕过访问控制。

2 静态内部类

class Outer {
    private String outerField = "Outer field";
    static String outerStaticWord = "word";
    static String name= "OuterTom";
    // 静态内部类
    static class StaticInner {
        String staticInnerField = "staticInnerField";
        //静态成员
        static int count = 0;
        public void print() {
            // System.out.println(outerField); // ❌ 编译错误:不能访问非静态成员
            String a=outerStaticWord;
            String b= staticInnerField;
        }
        //同名类变量
        static String name= "StaticInnerTom";
        public static void show(){
            //访问内部类的name
            String c = name;
            //访问外部类的name
            String d = Outer.name;
        }
    }

    public static void main(String[] args) {
        Outer.StaticInner inner = new Outer.StaticInner(); // ✅ 直接创建,无需外部实例
        inner.print();
        System.out.println(Outer.StaticInner.count); // ✅ 可直接通过类名访问
    }
}
  • 编译后:Outer.class 和 Outer$StaticInner.class
  • Outer$StaticInner.class ‌没有‌ this$0 字段
  • 构造方法是默认的 Outer$StaticInner()
  • 它‌完全独立于外部类实例‌,就像一个顶级类,只是命名空间被限定在 Outer 下
  • JVM行为 :
    • 加载 Outer$StaticInner.class(类加载器在首次使用时加载)
    • 在堆中创建 Outer$StaticInner 实例
    • 不涉及 Outer 实例的创建或引用‌
    • 静态内部类‌不持有外部类引用‌

3 局部内部类

public class Outer {
    private String outerField = "Outer";

    public void method() {
        final String localVar = "Local"; // 必须是 final

        // 局部内部类:定义在方法内
        class LocalInner {
            void print() {
                System.out.println(outerField); // ✅ 访问外部类成员
                System.out.println(localVar);   // ✅ 访问局部变量(必须是final)
            }
        }

        LocalInner li = new LocalInner();
        li.print();
    }

    public static void main(String[] args) {
        new Outer().method();
    }
}

3.2 JVM与类加载视角

  • 编译后生成:Outer.class 和 Outer$1LocalInner.class

  • LocalInner 的构造方法:

Outer$1LocalInner(Outer outer, String localVar) {
    this.this$0 = outer;
    this.val$localVar = localVar; // 编译器将局部变量复制为合成字段
    super();
}

3.3 对象实例化与“final”要求

  • 局部变量 localVar 被‌复制‌到内部类的合成字段中
  • 局部变量存储在‌栈帧‌中,方法结束后栈帧销毁。但内部类对象可能在方法结束后仍存活(如被返回)。为保证一致性,JVM‌复制值‌而非引用。若允许修改,会导致‌数据不一致‌。
// ❌ 错误示例
public void method() {
    String localVar = "Local";
    class LocalInner {
        void print() { 
            System.out.println(localVar); 
        }
    }
    localVar = "Changed"; // ❌ 编译错误:localVar 不再是 final
    LocalInner li = new LocalInner();
    li.print(); // 如果允许,应该输出 "Local" 还是 "Changed"?不确定!
}

4 匿名内部类

5 JVM类加载与内部类的加载时机

类加载过程(Class Loading)对内部类的影响

类型 加载时机 是否触发外部类加载
静态内部类 首次使用 Outer.StaticInner 时 ✅ 是(因为需要解析 Outer)
成员内部类 创建 outer.new Inner() 时 ✅ 是(必须先有 Outer 实例)
局部内部类 方法中首次 new LocalInner() 时 ✅ 是
匿名内部类 执行 new Runnable(){...} 时 ✅ 是
  • 即使未创建内部类实例,只要代码中引用了内部类的类名(如 Outer.Inner.class),JVM就会加载 Outer$Inner.class,并触发 Outer.class 的加载‌。
Class<?> clazz = Outer.Inner.class; // ✅ 触发 Outer 和 Outer$Inner 加载

参考资料:
http://www.cnblogs.com/dolphin0520/p/3811445.html

posted @ 2026-03-16 09:25  dvdhellohaha  阅读(10)  评论(0)    收藏  举报