问题

静态内部类与非静态内部类有何区别?

答案

一、核心区别

特性 静态内部类(Static Nested Class) 非静态内部类(Inner Class)
定义方式 使用 static 修饰 不使用 static 修饰
依赖关系 不依赖外部类实例 依赖外部类实例
创建方式 Outer.Inner inner = new Outer.Inner() Outer.Inner inner = outer.new Inner()
访问外部类成员 只能访问外部类的 静态成员 可以访问外部类的 所有成员(包括私有)
持有外部类引用 不持有 隐式持有 Outer.this
内存占用 更小(无额外引用) 更大(持有外部类引用)
内存泄漏风险 高(可能导致外部类无法回收)

二、静态内部类(Static Nested Class)

1. 定义与特点

public class Outer {
    private static String staticField = "外部类静态字段";
    private String instanceField = "外部类实例字段";

    // 静态内部类
    public static class StaticInner {
        public void display() {
            // ✅ 可以访问外部类的静态成员
            System.out.println(staticField);

            // ❌ 不能直接访问外部类的实例成员
            // System.out.println(instanceField);  // 编译错误
        }
    }
}

2. 创建方式

// 不需要外部类实例,直接通过类名创建
Outer.StaticInner inner = new Outer.StaticInner();
inner.display();

3. 访问外部类实例成员

public class Outer {
    private String instanceField = "实例字段";

    public static class StaticInner {
        public void display(Outer outer) {
            // 需要显式传入外部类实例
            System.out.println(outer.instanceField);
        }
    }
}

// 使用
Outer outer = new Outer();
Outer.StaticInner inner = new Outer.StaticInner();
inner.display(outer);

三、非静态内部类(Inner Class / Member Inner Class)

1. 定义与特点

public class Outer {
    private static String staticField = "外部类静态字段";
    private String instanceField = "外部类实例字段";

    // 非静态内部类(成员内部类)
    public class Inner {
        public void display() {
            // ✅ 可以访问外部类的静态成员
            System.out.println(staticField);

            // ✅ 可以访问外部类的实例成员
            System.out.println(instanceField);

            // ✅ 显式引用外部类实例
            System.out.println(Outer.this.instanceField);
        }
    }
}

2. 创建方式

// 必须先创建外部类实例
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();

// 或者在外部类方法中创建
public class Outer {
    public Inner createInner() {
        return new Inner();  // 隐式使用 this
    }
}

3. 隐式持有外部类引用

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

    public class Inner {
        public void printOuterName() {
            // 隐式持有 Outer.this 引用
            System.out.println(Outer.this.name);
        }

        public Outer getOuter() {
            return Outer.this;  // 返回外部类实例
        }
    }
}

四、内存模型对比

1. 静态内部类

堆内存:
┌─────────────────┐
│ StaticInner 对象 │  (独立存在,不持有外部类引用)
└─────────────────┘

2. 非静态内部类

堆内存:
┌─────────────┐
│ Outer 对象   │ ←─────┐
└─────────────┘       │
                      │ this$0 (隐式引用)
┌─────────────┐       │
│ Inner 对象   │ ──────┘
└─────────────┘

关键点:非静态内部类对象会持有一个名为 this$0 的隐式引用,指向外部类实例。


五、字节码层面的差异

1. 静态内部类字节码

// 编译后生成:Outer$StaticInner.class
public class Outer$StaticInner {
    public void display() {
        // 直接访问静态字段
        System.out.println(Outer.staticField);
    }
}

2. 非静态内部类字节码

// 编译后生成:Outer$Inner.class
public class Outer$Inner {
    final Outer this$0;  // 隐式持有外部类引用

    public Outer$Inner(Outer outer) {
        this.this$0 = outer;  // 构造方法中传入外部类实例
    }

    public void display() {
        // 通过 this$0 访问外部类成员
        System.out.println(this$0.instanceField);
    }
}

六、内存泄漏风险

1. 非静态内部类导致的内存泄漏

public class Activity {
    private byte[] data = new byte[1024 * 1024];  // 1MB 数据

    // ❌ 错误示例:非静态内部类
    public class AsyncTask {
        public void execute() {
            new Thread(() -> {
                // 长时间运行的任务
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 隐式持有 Activity.this,导致 Activity 无法回收
            }).start();
        }
    }

    public void startTask() {
        new AsyncTask().execute();
    }
}

问题:即使 Activity 不再使用,由于 AsyncTask 持有 Activity 的引用,导致 1MB 数据无法回收。

2. 使用静态内部类避免泄漏

public class Activity {
    private byte[] data = new byte[1024 * 1024];

    // ✅ 正确示例:静态内部类 + 弱引用
    public static class AsyncTask {
        private final WeakReference<Activity> activityRef;

        public AsyncTask(Activity activity) {
            this.activityRef = new WeakReference<>(activity);
        }

        public void execute() {
            new Thread(() -> {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Activity activity = activityRef.get();
                if (activity != null) {
                    // 使用 activity
                }
            }).start();
        }
    }

    public void startTask() {
        new AsyncTask(this).execute();
    }
}

七、使用场景

1. 静态内部类适用场景

(1)工具类或辅助类

public class Calculator {
    // 静态内部类:不依赖外部类实例
    public static class MathUtils {
        public static int add(int a, int b) {
            return a + b;
        }
    }
}

// 使用
int result = Calculator.MathUtils.add(1, 2);

(2)单例模式(静态内部类实现)

public class Singleton {
    private Singleton() {}

    // 静态内部类:延迟加载 + 线程安全
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

(3)Builder 模式

public class User {
    private final String name;
    private final int age;

    private User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    // 静态内部类:构建器
    public static class Builder {
        private String name;
        private int age;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// 使用
User user = new User.Builder()
    .name("Alice")
    .age(25)
    .build();

2. 非静态内部类适用场景

(1)需要访问外部类实例成员

public class LinkedList<E> {
    private Node<E> first;

    // 非静态内部类:需要访问外部类的 first
    private class Node<E> {
        E item;
        Node<E> next;

        Node(E element) {
            this.item = element;
        }
    }

    public void add(E element) {
        Node<E> newNode = new Node<>(element);
        if (first == null) {
            first = newNode;
        }
    }
}

(2)迭代器模式

public class MyList<E> {
    private E[] elements;
    private int size;

    // 非静态内部类:迭代器需要访问外部类的 elements 和 size
    private class Iterator {
        private int cursor = 0;

        public boolean hasNext() {
            return cursor < size;
        }

        public E next() {
            return elements[cursor++];
        }
    }

    public Iterator iterator() {
        return new Iterator();
    }
}

八、其他类型的内部类

1. 局部内部类(Local Inner Class)

public class Outer {
    public void method() {
        final int localVar = 10;

        // 局部内部类:定义在方法内
        class LocalInner {
            public void display() {
                System.out.println(localVar);  // 可以访问 final 或 effectively final 变量
            }
        }

        LocalInner inner = new LocalInner();
        inner.display();
    }
}

2. 匿名内部类(Anonymous Inner Class)

public class Outer {
    public void method() {
        // 匿名内部类:实现接口
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类");
            }
        };

        new Thread(runnable).start();
    }
}

九、性能对比

维度 静态内部类 非静态内部类
对象大小 小(无额外引用) 大(多 4-8 字节存储 this$0
创建速度 稍慢(需要传递外部类引用)
内存泄漏风险
GC 压力

十、面试要点总结

1. 核心区别

  • 静态内部类:不依赖外部类实例,不持有外部类引用,只能访问静态成员
  • 非静态内部类:依赖外部类实例,隐式持有 Outer.this 引用,可访问所有成员

2. 创建方式

// 静态内部类
Outer.StaticInner inner1 = new Outer.StaticInner();

// 非静态内部类
Outer outer = new Outer();
Outer.Inner inner2 = outer.new Inner();

3. 内存泄漏

非静态内部类持有外部类引用,可能导致外部类无法回收。解决方案:

  • 使用静态内部类 + 弱引用(WeakReference
  • 及时解除引用

4. 使用建议

  • 优先使用静态内部类:除非必须访问外部类实例成员
  • 避免在 Activity/Fragment 中使用非静态内部类:防止内存泄漏
  • Builder 模式、单例模式:使用静态内部类

5. 典型面试题

Q:为什么非静态内部类会持有外部类引用? A:编译器自动在非静态内部类中添加 this$0 字段,并在构造方法中传入外部类实例。

Q:如何避免非静态内部类导致的内存泄漏? A:改用静态内部类 + 弱引用,或及时解除引用。

Q:静态内部类能否访问外部类的实例成员? A:不能直接访问,需要显式传入外部类实例。