什么情况下应该使用 Java 的内部类

内部类实际是嵌套类的一种,指非静态嵌套类,另一种是静态嵌套类。最近想用一用,但是在使用了内部类之后 IDEA 总是提示可以改成静态嵌套类,非常好奇这两种类到底应该分别在什么场景下使用。参考了官方文档Nested Classes in Java 和一些,总结了一套暂时能说服自己判断。

区别

内部类

1
2
3
4
5
6
7
8
9
10
public class OuterClass {
class InnerClass {
// ...
}

public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
}
}

内部类依赖于外层类的实例而存在,可以访问外层类的所有成员,包括 private 声明的成员。因为内部类跟具体实例关联,所以内部类只能声明非静态成员。

静态嵌套类

1
2
3
4
5
6
7
8
9
10
11
12
public class OuterClass {
static class StaticNestedClass {
private void test() {
// ...
}
}

public static void main(String[] args) {
OuterClass.StaticNestedClass nestedClass = new OuterClass.StaticNestedClass();
nestedClass.test();
}
}

静态嵌套类只能访问外层类的 static 成员。自身可以声明静态或非静态成员。

共性

嵌套类可以声明为 private、public、protected 或包内友好,而外层类只能声明为 public 或包内友好。

使用场景

嵌套类

官方给了以下三种使用嵌套类的场景:

  • 内部的类只被外部所使用
  • 外部的类的成员需要保持 private,但又想让内部的类能够访问
  • 使相关的代码集中在一起,更容易阅读和维护

内部类

内层类应该只被外层类所使用,且单独存在没意义,必须有外层类的实例才有意义的情况下,使用非静态嵌套类(内部类)。比如外层是人,内层是三维。不过单独存在是否有意义不好定义,具体看代码里面会不会单独使用。如果会单独使用内层类的实例做一些处理,且不需要外层类的实例存在,就需要用静态嵌套类。

静态嵌套类

内层类被外层类所使用,但可以被单独使用,或者只是为了降低包的深度,更好的组织代码,可以放在外层类中。比如外层是 DateUtil,内层是 TimeZoneUtil,不想把这些类都放在一个包里,因为根据字母顺序排序,这两个相关的类可能离很远,或者包下面有一堆类,想要归类又要多一层包的深度。

其他类型

局部类(Local class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OuterClass {
void foo() {
class LocalClass {
void bar() {
// ...
}
}
LocalClass localClass = new LocalClass();
localClass.bar();
}

public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.foo();
}
}

局部类可以定义在方法或者代码快中。局部类不能有修饰符,可以访问外层静态或非静态成员,但同内部类一样不能定义静态成员,也不能实现接口,因为接口实际是静态的。

上面说如果你的类只被某一个类使用,就应该用内部类,那么,如果类只被某一个方法,或某一段代码块使用,就应该用局部类,粒度比内部类更细一些,就像 Hashtable 与 ConcurrentHashMap 的同步粒度一样。

匿名内部类

AbstractClass.java
1
2
3
4
5
abstract class AbstractClass {
abstract void foo();

abstract void bar();
}
AnonymousClass.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AnonymousClass {
public static void main(String[] args) {
AbstractClass abstractClass = new AbstractClass() {
@Override
void foo() {
// ...
}

@Override
void bar() {
// ...
}
};
}
}

匿名内部类可以在创建实例的时候直接 Override 方法,适合每个实例都要有不同的实现,或者创建实例临时使用的情况。常见的场景有 Thread、Runnable 等,其中 Runnable 是接口类型的。

剩下的问题

还看到了一些说法,暂时还没有弄清楚。

  • 使用内部类是因为 Java 不能多继承,所以可以使用内部类来实现,通过在内部声明多个继承了的内部类,在外层就可以直接创建这些内部类的实例来实现类似于多继承的效果。
  • 如果内部类没有持有外层类的成员信息,就应该使用静态嵌套类,这样可以节约内存。这个可能就是 IDEA 一直建议我使用静态嵌套类的原因?具体答案要去看《Effective Java》。
分享