java内部类[Nested Classes]

339 阅读4分钟

内部类简介

官方文档docs.oracle.com/javase/tuto…

Java 中,可以将一个类定义在另一个类或者一个方法里面,这样的类称为内部类。内部类与其包裹类的实例相关联,并且可以直接访问包裹类实例的方法和变量。由于内部类与包裹类实例相关联,因此内部类不能定义任何静态成员。 内部类包含四类:成员内部类局部内部类匿名内部类静态内部类

1. 成员内部类

public class OuterClass {
    private int outerValue = 0;
    private void outPrint() {
        System.out.print("the outer print...");
    }
    // 成员内部内可访问包裹类变量(包括私有变量)
    public class InnerClass {
        private void printOuterValue() {
            outPrint(); // the outer print...
            System.out.printf("the out var: %d", outerValue); // the out var:0
        }
    }
}
  • 使用成员内部类

在外部创建内部内时,必须依赖包裹类实例

OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();

在内部创建内部内可直接new类名

public class OuterClass {
   public class InnerClass {}
   void newInnerClass(){
	  InnerClass innerClass = new InnerClass();
   }
}
  • 内部类能访问包裹类变量原因 内部类能访问包裹类成员的真正原因是内部类在创建的时候传入了包裹类的引用,所有对包裹类的访问都通过该引用完成。javac编译该上java文件得出两个class文件: OuterClass.class 、OuterClass$InnerClass.class,使用androidstudio查看class文件(自动查看反编译代码):

OuterClass.class

public class OuterClass {
    private int outerValue = 0;
    public OuterClass() {
    }
    private void outPrint() {
        System.out.print("the outer print...");
    }
}

OuterClass$InnerClass.class

public class OuterClass$InnerClass {
    public OuterClass$InnerClass(OuterClass var1) {
        this.this$0 = var1;
    }
    private void printOuterValue() {
        OuterClass.access$000(this.this$0);
        System.out.printf("the out var: %d", OuterClass.access$100(this.this$0));
    }
}

通过反编译发现InnerClass默认创建了OuterClass参数构造函数,this0指向了OuterClass实例,后续访问OuterClass成员都通过this0指向了OuterClass实例,后续访问OuterClass成员都通过this0实现。

2. 局部内部类

局部内部类和成员内部类类似,编译后同样生成了两个class文件两个class文件: OuterClass.class 、OuterClass$1LocalClass,且局部内部类持有了包裹类实例

public class OuterClass {
    private int outerValue = 0;
    public void doSomething() {
        class LocalClass {
            private void print() {
                System.out.printf("innerclass: print outervalue%d", outerValue);
            }
        }
        new LocalClass().print();
    }
}

3. 匿名内部类

匿名内部类是指直接创建未经实现的接口或抽象类并实现其接口、抽象方法,起到简化代码的作用。

public interface Callback {
    void onCallback();
}

public class ObjectX {
    public void doSomething() {
        Callback callback = new Callback() {
            @Override
            public void onCallback() {
                System.out.print("do someThing");
            }
        };
        callback.onCallback();
    }
}

javac 该上两个java文件生成3个class文件:**Callback.class 、ObjectX.class、ObjectX1.class,编译器帮我们生成了实现Callback接口的文件ObjectX1.class**,编译器帮我们生成了实现Callback接口的文件ObjectX1.class

ObjectX$1.class

class ObjectX$1 implements Callback {
    ObjectX$1(ObjectX var1) {
        this.this$0 = var1;
    }
    public void onCallback() {
        System.out.print("do someThing");
    }
}

ObjectX.class

import com.innerclass.test.ObjectX.1;
public class ObjectX {
    public ObjectX() {}
    public void doSomething() {
        1 var1 = new 1(this);
        var1.onCallback();
    }
}

4. 静态内部类

静态内部类和普通类相似。只有细微差别:

  • 和内部类一样可以被protected、private界限修饰符修饰;(普通类不行)
  • 包裹类能够访问静态内部类所有界限修饰变量及方法,静态内部类不能访问父类成员资源;
  • 外部类访问静态内部类和普通类界限权限一致;
  • 外部类创建静态内部类需要带上父类名
class OuterClass {
    private int outerValue = 0;
    protected static class StaticInnerClass {
        private int innerValue = 0;
        protected void print() {
            System.out.printf("inner value: %d", innerValue);
            // System.out.printf("inner class print outer value: %d", outerValue); 不能访问父类成员资源
        }
    }
    public void print() {
        StaticInnerClass innerClass = new StaticInnerClass();
        System.out.printf("outer value: %d ; inner value:%d", outerValue, innerClass.innerValue);
        innerClass.print();
    }
}
class Test {
    private static void main(String[] args) {
        new OuterClass.StaticInnerClass().print();
    }
}

javac 该上两个java文件生成3个class文件:OuterClass.class 、Test.class、OuterClass$StaticInnerClass.class

OuterClass.class

class OuterClass {
    private int outerValue = 0;
    OuterClass() {
    }
    public void print() {
        OuterClass.StaticInnerClass var1 = new OuterClass.StaticInnerClass();
        System.out.printf("outer value: %d ; inner value:%d", this.outerValue, var1.innerValue);
        var1.print();
    }
    protected static class StaticInnerClass {
        private int innerValue = 0;
        protected StaticInnerClass() {
      	}
        protected void print() {
            System.out.printf("inner value: %d", this.innerValue);
        }
    }
}

OuterClass$StaticInnerClass.class

public class OuterClass$StaticInnerClass {
    private int innerValue = 0;
    protected OuterClass$StaticInnerClass() {
    }
    protected void print() {
        System.out.printf("inner value: %d", this.innerValue);
    }
}

内部类总结

为什么要使用内部类

  • 这是一种逻辑上对只在一个地方使用的类进行分组的方法:如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的。嵌套这样的“助手类”使它们的包更加精简
  • 它增加了封装:考虑两个顶级类A和B,其中B需要访问A的成员,否则这些成员将被声明为私有。通过在类A中隐藏类B,可以将A的成员声明为私有,B可以访问它们。另外,B本身可以对外界隐藏。
  • 它可以产生可读性和可维护性更强的代码:在顶级类中嵌套小型类会使代码更接近使用它的位置。

内部类要点

  • 内部类由于与包裹类相互关联,因此能够相互访问所有界限修饰符资源
  • 内部类由于与包裹类相互关联,因此内部类不能创建静态资源
  • 内部类本质上和普通类一样,那么为什么包裹类和内部类能够相互访问私有成员java编译器帮我们实现了访问私有变量和方法的静态方法(编译器语法糖),使用javap -verbose xxx.claxx查看文件能够看到如下类似代码,我们在使用中无感知的调用了其自动生成的静态方法:
static int access$100(com.innerclass.test.OuterClass$StaticInnerClass);
    descriptor: (Lcom/innerclass/test/OuterClass$StaticInnerClass;)I
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field innerValue:I
         4: ireturn
      LineNumberTable:
        line 7: 0
  static void access$200(com.innerclass.test.OuterClass$StaticInnerClass);
    descriptor: (Lcom/innerclass/test/OuterClass$StaticInnerClass;)V
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method print:()V
         4: return
      LineNumberTable:
        line 7: 0