在学习 Java 内部类时,很多初学者会遇到两个非常相似的概念:
classOuter{classInner{}staticclassStaticInner{}}其中,Inner是非静态内部类,StaticInner是静态内部类。
它们看起来只是相差一个static,但在对象创建方式、成员访问范围、内存关系和使用场景上都有明显区别。
一句话概括:
非静态内部类依赖外部类对象创建,并且可以直接访问外部类的实例成员;静态内部类不依赖外部类对象创建,只能直接访问外部类的静态成员。
一、什么是内部类?
在一个类的内部定义另一个类,这个类就叫内部类。
例如:
classOuter{classInner{}}这里:
Outer是外部类;Inner是内部类。
内部类通常用于表示一个只服务于外部类的辅助结构。把它放在外部类内部,可以让代码关系更加紧密,也可以隐藏不需要暴露给其他类的实现细节。
成员内部类主要可以分为两类:
非静态内部类 静态内部类二、什么是非静态内部类?
没有使用static修饰的成员内部类,叫非静态内部类。
例如:
classOuter{intage=18;classInner{voidshow(){System.out.println(age);}}}这里的Inner就是非静态内部类。
它最大的特点是:必须依赖一个具体的外部类对象才能创建。
创建方式如下:
Outerouter=newOuter();Outer.Innerinner=outer.newInner();inner.show();不能直接写:
Outer.Innerinner=newOuter.Inner();// 编译错误因为非静态内部类属于某一个具体的外部类对象,而不是只属于外部类本身。
可以把外部类理解成一栋房子,把非静态内部类理解成房子里的房间。
必须先有一栋具体的房子,才能创建这栋房子里的房间。
所以需要先执行:
Outerouter=newOuter();再执行:
Outer.Innerinner=outer.newInner();三、非静态内部类为什么能访问外部类实例成员?
看下面的代码:
classOuter{privateintage=18;classInner{voidshow(){System.out.println(age);}}}age是外部类的实例变量,但是内部类可以直接访问它,甚至不需要写:
Outer.this.age原因是,每个非静态内部类对象都会关联一个具体的外部类对象。
例如:
Outerouter=newOuter();Outer.Innerinner=outer.newInner();这里创建出的inner内部保存了对outer的关联,所以当它访问age时,实际访问的是这个outer对象的age。
可以简单理解为:
非静态内部类对象身上带着一个外部类对象。因此,非静态内部类可以直接访问外部类中的:
实例变量 实例方法 静态变量 静态方法 private 成员示例:
classOuter{privateintage=18;privatestaticintcount=100;classInner{voidshow(){System.out.println(age);System.out.println(count);}}}这段代码是可以正常运行的。
四、什么是静态内部类?
使用static修饰的成员内部类,叫静态内部类,也常被称为静态嵌套类。
例如:
classOuter{staticclassStaticInner{voidshow(){System.out.println("静态内部类");}}}静态内部类不依赖外部类对象,可以直接通过外部类名创建:
Outer.StaticInnerinner=newOuter.StaticInner();inner.show();不需要先创建:
Outerouter=newOuter();原因是,静态内部类属于外部类本身,而不是属于某个具体的外部类对象。
可以简单理解为:
非静态内部类属于外部类对象; 静态内部类属于外部类本身。五、静态内部类为什么不能直接访问实例成员?
看下面的代码:
classOuter{intage=18;staticclassStaticInner{voidshow(){System.out.println(age);// 编译错误}}}这里会报错,因为age是外部类的实例变量。
实例变量属于某一个具体对象,而静态内部类在创建时并没有关联任何外部类对象。
假设存在两个外部类对象:
Outerouter1=newOuter();Outerouter2=newOuter();outer1.age=18;outer2.age=20;当静态内部类访问age时,Java 无法判断它要访问的是:
outer1.age还是:
outer2.age所以静态内部类不能直接访问外部类的实例成员。
不过,它可以直接访问外部类的静态成员:
classOuter{staticintcount=100;staticclassStaticInner{voidshow(){System.out.println(count);}}}因为静态变量属于类本身,不属于某一个具体对象。
如果静态内部类确实需要访问实例成员,可以手动创建或接收外部类对象:
classOuter{intage=18;staticclassStaticInner{voidshow(Outerouter){System.out.println(outer.age);}}}使用:
Outerouter=newOuter();Outer.StaticInnerinner=newOuter.StaticInner();inner.show(outer);六、完整代码对比
下面通过一个完整示例对比两者:
classOuter{intinstanceValue=100;staticintstaticValue=200;classNormalInner{voidshow(){System.out.println(instanceValue);System.out.println(staticValue);}}staticclassStaticInner{voidshow(){// 不能直接访问 instanceValueSystem.out.println(staticValue);}}}创建非静态内部类:
Outerouter=newOuter();Outer.NormalInnernormalInner=outer.newNormalInner();normalInner.show();创建静态内部类:
Outer.StaticInnerstaticInner=newOuter.StaticInner();staticInner.show();两者的创建方式是最容易在考试和面试中被问到的区别。
七、两者的核心区别
| 对比项 | 非静态内部类 | 静态内部类 |
|---|---|---|
是否使用static | 否 | 是 |
| 是否依赖外部类对象 | 依赖 | 不依赖 |
| 创建方式 | outer.new Inner() | new Outer.StaticInner() |
| 能否直接访问外部类实例成员 | 可以 | 不可以 |
| 能否直接访问外部类静态成员 | 可以 | 可以 |
| 是否关联外部类对象 | 是 | 否 |
| 更接近普通类还是对象成员 | 对象成员 | 普通类 |
八、为什么静态内部类更节省关联?
非静态内部类对象会隐式关联一个外部类对象。
例如:
Outerouter=newOuter();Outer.Innerinner=outer.newInner();只要inner还被使用,它关联的outer也可能无法被垃圾回收。
如果内部类实际上并不需要访问外部类的实例成员,却仍然被定义成非静态内部类,就可能产生不必要的对象关联。
静态内部类没有这种隐式关联。
因此,如果内部类不需要使用外部类对象的实例成员,一般更推荐定义为静态内部类。
九、常见使用场景
1. 非静态内部类
当内部类必须依赖外部类对象的数据时,可以使用非静态内部类。
例如,一个订单中的订单项:
classOrder{privateStringorderNo;classOrderItem{voidprintOrderNo(){System.out.println(orderNo);}}}OrderItem与某一个具体订单紧密关联,因此可以使用非静态内部类。
2. 静态内部类
当内部类只是为了组织代码,不需要访问外部类实例成员时,更适合使用静态内部类。
常见例子是建造者模式:
classUser{privateStringname;privateintage;staticclassBuilder{privateStringname;privateintage;Buildername(Stringname){this.name=name;returnthis;}Builderage(intage){this.age=age;returnthis;}Userbuild(){Useruser=newUser();user.name=name;user.age=age;returnuser;}}}使用:
Useruser=newUser.Builder().name("Tom").age(18).build();这里的Builder不需要依赖一个已经存在的User对象,所以通常定义为静态内部类。
十、初学者容易混淆的问题
1. 静态内部类能不能创建对象?
可以。
static并不代表这个类不能创建对象,而是表示创建它时不需要外部类对象。
Outer.StaticInnerinner=newOuter.StaticInner();2. 非静态内部类自己能不能有成员变量?
可以。
classOuter{classInner{intvalue=10;}}3. 静态内部类能不能访问外部类的 private 静态成员?
可以。
classOuter{privatestaticintcount=10;staticclassInner{voidshow(){System.out.println(count);}}}内部类和外部类之间可以互相访问对方的私有成员,只要访问方式符合静态和非静态规则。
十一、面试怎么回答?
面试时可以这样回答:
非静态内部类依赖外部类实例创建,内部会关联一个具体的外部类对象,因此可以直接访问外部类的实例成员和静态成员;静态内部类不依赖外部类实例,可以直接通过外部类名创建,只能直接访问外部类的静态成员。如果内部类不需要访问外部类实例成员,通常更推荐使用静态内部类。
也可以用一句话回答:
非静态内部类属于外部类对象,静态内部类属于外部类本身。
十二、总结
非静态内部类和静态内部类最核心的区别,不是有没有static这么简单,而是它们是否依赖外部类对象。
非静态内部类:
必须依赖外部类对象创建; 可以直接访问外部类实例成员; 内部会关联一个具体的外部类对象。静态内部类:
不依赖外部类对象; 可以直接通过外部类名创建; 不能直接访问外部类实例成员; 不会隐式关联外部类对象。最后记住这句口诀:
非静态内部类先有外部对象,再有内部对象; 静态内部类直接通过外部类名创建。