嵌套类是指定义在一个类内部的类,嵌套类存在的目的是为外围类提供服务的。嵌套类分为四种。

  1. 静态成员类
  2. 非静态成员类
  3. 匿名类
  4. 局部类

    1. 静态成员类

    静态成员类也是一个静态成员,符合类的静态访问规则,如果被声明为private只能在外围类中使用,静态成员类可以访问外围类的所有静态成员,包括私有静态成员。
    静态成员类的一个常见用途是作为公共帮助类,仅在与其外部类一起使用时才有用。 例如,考虑一个描述计算器支持的操作的枚举类型。 Operation枚举应该是Calculator类的公共静态成员类。 Calculator客户端可以使用Calculator.Operation.PLUSCalculator.Operation.MINUS等名称来引用操作。

2. 非静态成员类

非静态成员类和静态成员类只差一个static修饰符,但是二者却有着很大差别,非静态内部类必须要依赖于外部的成员变量,也就是说想要生成一个内部成员类的实例,必须要先创建一个外部类的实例。如果嵌套类的实例可以与其宿主类的实例隔离存在,那么嵌套类必须是静态成员类:不可能在没有宿主实例的情况下创建非静态成员类的实例。
非静态成员类实例和其宿主实例之间的关联是在创建成员类实例时建立的,并且之后不能被修改。 通常情况下,通过在宿主类的实例方法中调用非静态成员类构造方法来自动建立关联。 尽管很少有可能使用表达式enclosingInstance.new MemberClass(args)手动建立关联。 正如你所预料的那样,该关联在非静态成员类实例中占用了空间,并为其构建添加了时间开销。

  1. package item24;
  2. /**
  3. * @author: qujundong
  4. * @date: 2020/11/28 下午12:08
  5. * @description:
  6. */
  7. public class InnerClass {
  8. public static class StaticInner{
  9. }
  10. public class NonStaticInner{
  11. }
  12. public static void main(String[] args) {
  13. StaticInner staticInner = new StaticInner();
  14. //必须先实例化外部类,在实例化成员内部类
  15. NonStaticInner nonStaticInner = new InnerClass().new NonStaticInner();
  16. }
  17. }

如果你声明了一个不需要访问**宿主实例的成员类,总是把static修饰符放在它的声明中,使它成为一个静态成员类,而不是非静态的成员类**。 如果你忽略了这个修饰符,每个实例都会有一个隐藏的外部引用给它的宿主实例。 如前所述,存储这个引用需要占用时间和空间。 更严重的是,并且会导致即使宿主类在满足垃圾回收的条件时却仍然驻留在内存中。 由此产生的内存泄漏可能是灾难性的。 由于引用是不可见的,所以通常难以检测到。

3. 匿名内部类

它不是其宿主类的成员。 它不是与其他成员一起声明,而是在使用时同时声明和实例化。 在表达式合法的代码中,匿名类是允许的。 当且仅当它们出现在非静态上下文中时,匿名类才会封装实例。 但是,即使它们出现在静态上下文中,它们也不能有除常量型变量之外的任何外部变量,这些常量型变量包括final的基本类型,或者初始化常量表达式的字符串属性。
如下代码,虽然看上去,匿名内部类可以访问外部的普通变量a,但是加上a++;后会报错。这是因为系统在开始的时候将a优化为final,但是执行a++系统会将其视为普通变量,报错。

  1. public class NoName {
  2. public void test(){
  3. //java8后优化默认为final
  4. int a = 2;
  5. //添加会造成a不是final从而报错。
  6. //a ++;
  7. new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. System.out.println(a);
  11. }
  12. }).start();
  13. }
  14. public static void main(String[] args) {
  15. new NoName().test();
  16. }
  17. }

为什么a要声明为final?
内部类对象的生命周期会超过局部变量的生命周期。局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡。而内部类对象生命周期与其它类一样:自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才会死亡(被JVM垃圾回收)。所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但匿名内部类的对象仍然活着。这不被JAVA允许。

4. 局部类

局部类是在某个函数内存在的类,如下:

  1. package item24;
  2. /**
  3. * @author: qujundong
  4. * @date: 2020/11/28 下午12:25
  5. * @description:
  6. */
  7. public class LocalClass {
  8. private int a;
  9. public void test() {
  10. System.out.println("外部类...");
  11. }
  12. /**
  13. * 局部内部类是嵌套在方法里面的
  14. */
  15. public void testB() {
  16. class ClassB {
  17. private int b;
  18. public void testB() {
  19. System.out.println("局部类...");
  20. }
  21. }
  22. ClassB b = new ClassB(); //局部类创建实例
  23. b.testB(); //实例调用testB()方法
  24. }
  25. public static void main(String[] args) {
  26. LocalClass localClass = new LocalClass();
  27. localClass.test();
  28. localClass.testB();
  29. }
  30. }

局部类是四种嵌套类中使用最少的。 一个局部类可以在任何可以声明局部变量的地方声明,并遵守相同的作用域规则。 局部类与其他类型的嵌套类具有共同的属性。 像成员类一样,他们有名字,可以重复使用。 就像匿名类一样,只有在非静态上下文中定义它们时,它们才会包含实例,并且它们不能包含静态成员。 像匿名类一样,应该保持简短,以免损害可读性。

总结:
这四种不同的嵌套类,每个都有它的用途。 如果一个嵌套的类需要在一个方法之外可见,或者太长而不能很好地适应一个方法,使用一个成员类。 如果一个成员类的每个实例都需要一个对其宿主实例的引用,使其成为非静态的; 否则,使其静态。 假设这个类属于一个方法内部,如果你只需要从一个地方创建实例,并且存在一个预置类型来说明这个类的特征,那么把它作为一个匿名类; 否则,把它变成局部类。
能用静态类就用静态类,否则再用非静态的