在学习 Java 内部类时,很多初学者会遇到两个非常相似的概念:

class Outer {
    class Inner {
    }

    static class StaticInner {
    }
}

其中,Inner 是非静态内部类,StaticInner 是静态内部类。

它们看起来只是相差一个 static,但在对象创建方式、成员访问范围、内存关系和使用场景上都有明显区别。

一句话概括:

非静态内部类依赖外部类对象创建,并且可以直接访问外部类的实例成员;静态内部类不依赖外部类对象创建,只能直接访问外部类的静态成员。

一、什么是内部类?

在一个类的内部定义另一个类,这个类就叫内部类。

例如:

class Outer {
    class Inner {
    }
}

这里:

  • Outer 是外部类;
  • Inner 是内部类。

内部类通常用于表示一个只服务于外部类的辅助结构。把它放在外部类内部,可以让代码关系更加紧密,也可以隐藏不需要暴露给其他类的实现细节。

成员内部类主要可以分为两类:

非静态内部类
静态内部类

二、什么是非静态内部类?

没有使用 static 修饰的成员内部类,叫非静态内部类。

例如:

class Outer {
    int age = 18;

    class Inner {
        void show() {
            System.out.println(age);
        }
    }
}

这里的 Inner 就是非静态内部类。

它最大的特点是:必须依赖一个具体的外部类对象才能创建。

创建方式如下:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

inner.show();

不能直接写:

Outer.Inner inner = new Outer.Inner(); // 编译错误

因为非静态内部类属于某一个具体的外部类对象,而不是只属于外部类本身。

可以把外部类理解成一栋房子,把非静态内部类理解成房子里的房间。

必须先有一栋具体的房子,才能创建这栋房子里的房间。

所以需要先执行:

Outer outer = new Outer();

再执行:

Outer.Inner inner = outer.new Inner();

三、非静态内部类为什么能访问外部类实例成员?

看下面的代码:

class Outer {
    private int age = 18;

    class Inner {
        void show() {
            System.out.println(age);
        }
    }
}

age 是外部类的实例变量,但是内部类可以直接访问它,甚至不需要写:

Outer.this.age

原因是,每个非静态内部类对象都会关联一个具体的外部类对象。

例如:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

这里创建出的 inner 内部保存了对 outer 的关联,所以当它访问 age 时,实际访问的是这个 outer 对象的 age

可以简单理解为:

非静态内部类对象身上带着一个外部类对象。

因此,非静态内部类可以直接访问外部类中的:

实例变量
实例方法
静态变量
静态方法
private 成员

示例:

class Outer {
    private int age = 18;
    private static int count = 100;

    class Inner {
        void show() {
            System.out.println(age);
            System.out.println(count);
        }
    }
}

这段代码是可以正常运行的。

四、什么是静态内部类?

使用 static 修饰的成员内部类,叫静态内部类,也常被称为静态嵌套类。

例如:

class Outer {
    static class StaticInner {
        void show() {
            System.out.println("静态内部类");
        }
    }
}

静态内部类不依赖外部类对象,可以直接通过外部类名创建:

Outer.StaticInner inner = new Outer.StaticInner();
inner.show();

不需要先创建:

Outer outer = new Outer();

原因是,静态内部类属于外部类本身,而不是属于某个具体的外部类对象。

可以简单理解为:

非静态内部类属于外部类对象;
静态内部类属于外部类本身。

五、静态内部类为什么不能直接访问实例成员?

看下面的代码:

class Outer {
    int age = 18;

    static class StaticInner {
        void show() {
            System.out.println(age); // 编译错误
        }
    }
}

这里会报错,因为 age 是外部类的实例变量。

实例变量属于某一个具体对象,而静态内部类在创建时并没有关联任何外部类对象。

假设存在两个外部类对象:

Outer outer1 = new Outer();
Outer outer2 = new Outer();

outer1.age = 18;
outer2.age = 20;

当静态内部类访问 age 时,Java 无法判断它要访问的是:

outer1.age

还是:

outer2.age

所以静态内部类不能直接访问外部类的实例成员。

不过,它可以直接访问外部类的静态成员:

class Outer {
    static int count = 100;

    static class StaticInner {
        void show() {
            System.out.println(count);
        }
    }
}

因为静态变量属于类本身,不属于某一个具体对象。

如果静态内部类确实需要访问实例成员,可以手动创建或接收外部类对象:

class Outer {
    int age = 18;

    static class StaticInner {
        void show(Outer outer) {
            System.out.println(outer.age);
        }
    }
}

使用:

Outer outer = new Outer();
Outer.StaticInner inner = new Outer.StaticInner();

inner.show(outer);

六、完整代码对比

下面通过一个完整示例对比两者:

class Outer {
    int instanceValue = 100;
    static int staticValue = 200;

    class NormalInner {
        void show() {
            System.out.println(instanceValue);
            System.out.println(staticValue);
        }
    }

    static class StaticInner {
        void show() {
            // 不能直接访问 instanceValue
            System.out.println(staticValue);
        }
    }
}

创建非静态内部类:

Outer outer = new Outer();

Outer.NormalInner normalInner = outer.new NormalInner();
normalInner.show();

创建静态内部类:

Outer.StaticInner staticInner = new Outer.StaticInner();
staticInner.show();

两者的创建方式是最容易在考试和面试中被问到的区别。

七、两者的核心区别

对比项 非静态内部类 静态内部类
是否使用 static
是否依赖外部类对象 依赖 不依赖
创建方式 outer.new Inner() new Outer.StaticInner()
能否直接访问外部类实例成员 可以 不可以
能否直接访问外部类静态成员 可以 可以
是否关联外部类对象
更接近普通类还是对象成员 对象成员 普通类

八、为什么静态内部类更节省关联?

非静态内部类对象会隐式关联一个外部类对象。

例如:

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

只要 inner 还被使用,它关联的 outer 也可能无法被垃圾回收。

如果内部类实际上并不需要访问外部类的实例成员,却仍然被定义成非静态内部类,就可能产生不必要的对象关联。

静态内部类没有这种隐式关联。

因此,如果内部类不需要使用外部类对象的实例成员,一般更推荐定义为静态内部类。

九、常见使用场景

1. 非静态内部类

当内部类必须依赖外部类对象的数据时,可以使用非静态内部类。

例如,一个订单中的订单项:

class Order {
    private String orderNo;

    class OrderItem {
        void printOrderNo() {
            System.out.println(orderNo);
        }
    }
}

OrderItem 与某一个具体订单紧密关联,因此可以使用非静态内部类。

2. 静态内部类

当内部类只是为了组织代码,不需要访问外部类实例成员时,更适合使用静态内部类。

常见例子是建造者模式:

class User {
    private String name;
    private int age;

    static class Builder {
        private String name;
        private int age;

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

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

        User build() {
            User user = new User();
            user.name = name;
            user.age = age;
            return user;
        }
    }
}

使用:

User user = new User.Builder()
        .name("Tom")
        .age(18)
        .build();

这里的 Builder 不需要依赖一个已经存在的 User 对象,所以通常定义为静态内部类。

十、初学者容易混淆的问题

1. 静态内部类能不能创建对象?

可以。

static 并不代表这个类不能创建对象,而是表示创建它时不需要外部类对象。

Outer.StaticInner inner = new Outer.StaticInner();

2. 非静态内部类自己能不能有成员变量?

可以。

class Outer {
    class Inner {
        int value = 10;
    }
}

3. 静态内部类能不能访问外部类的 private 静态成员?

可以。

class Outer {
    private static int count = 10;

    static class Inner {
        void show() {
            System.out.println(count);
        }
    }
}

内部类和外部类之间可以互相访问对方的私有成员,只要访问方式符合静态和非静态规则。

十一、面试怎么回答?

面试时可以这样回答:

非静态内部类依赖外部类实例创建,内部会关联一个具体的外部类对象,因此可以直接访问外部类的实例成员和静态成员;静态内部类不依赖外部类实例,可以直接通过外部类名创建,只能直接访问外部类的静态成员。如果内部类不需要访问外部类实例成员,通常更推荐使用静态内部类。

也可以用一句话回答:

非静态内部类属于外部类对象,静态内部类属于外部类本身。

十二、总结

非静态内部类和静态内部类最核心的区别,不是有没有 static 这么简单,而是它们是否依赖外部类对象。

非静态内部类:

必须依赖外部类对象创建;
可以直接访问外部类实例成员;
内部会关联一个具体的外部类对象。

静态内部类:

不依赖外部类对象;
可以直接通过外部类名创建;
不能直接访问外部类实例成员;
不会隐式关联外部类对象。

最后记住这句口诀:

非静态内部类先有外部对象,再有内部对象;
静态内部类直接通过外部类名创建。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐