从反编译内部类来认识final

为什么内部类可以无条件访问外部类成员?

public class Outter {
    private Inner inner = null;
    public Outter() {
    }
}
 
public Inner getInnerInstance() {
    if(inner == null)
        inner = new Inner();
    return inner;
}
  
protected class Inner {
    public Inner() {
         
    }
}

我们通过 javap -v Outter$Inner 来反编译代码,最终得到字节码文件:

{
    // 注意这里
    final com.cxh.test2.Outter this$0;
  public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
    Code:
     Stack=2, Locals=2, Args_size=2
     0:   aload_0
     1:   aload_1
     2:   putfield        #10; //Field this$0:Lcom/cxh/test2/Outter;
     5:   aload_0
     6:   invokespecial   #12; //Method java/lang/Object."":()V
     9:   return
    LineNumberTable:
     line 16: 0
     line 18: 9

    LocalVariableTable:
     Start  Length  Slot  Name   Signature
     0      10      0    this       Lcom/cxh/test2/Outter$Inner;

}

第一行就明确了一个外部类的引用,看构造方法就更清晰了:传入了一个外部类的引用。由此可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在内部类随意访问外部类的成员。

为什么局部内部类和匿名内部类访问局部变量要用final修饰?

我们先看这样一段代码

public class Test {

    public static void main(String[] args) {

    }

    public void test(final int a) {
        new Thread() {
            public void run() {
                System.out.println(a);
            }
        }.start();
    }

    public void test1(final int b) {
        final int a = 10;
        new Thread() {
            public void run() {
                System.out.println(a);
                System.out.println(b);
            }
        }.start();
    }

}

我们反编译得到的结果是:

Constant pool:
   #1 = Methodref          #9.#22         // java/lang/Object."":()V
   #2 = Class              #23            // com/roger/algorithm/Test$1
   #3 = Methodref          #2.#24         // com/roger/algorithm/Test$1."":(Lcom/roger/algorithm/Test;I)V
   #4 = Methodref          #2.#25         // com/roger/algorithm/Test$1.start:()V
   #5 = Class              #26            // com/roger/algorithm/Test$2
   #6 = Methodref          #5.#24         // com/roger/algorithm/Test$2."":(Lcom/roger/algorithm/Test;I)V
   #7 = Methodref          #5.#25         // com/roger/algorithm/Test$2.start:()V
   #8 = Class              #27            // com/roger/algorithm/Test
   #9 = Class              #28            // java/lang/Object
  #10 = Utf8               InnerClasses
  
  public void test(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=2, args_size=2
         0: new           #2                  // class com/roger/algorithm/Test$1
         3: dup
         4: aload_0
         5: iload_1
         6: invokespecial #3                  // Method com/roger/algorithm/Test$1."":(Lcom/roger/algorithm/Test;I)V
         9: invokevirtual #4                  // Method com/roger/algorithm/Test$1.start:()V
        12: return
      LineNumberTable:
        line 10: 0
        line 16: 9
        line 17: 12

  public void test1(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=2
         0: new           #5                  // class com/roger/algorithm/Test$2
         3: dup
         4: aload_0
         5: iload_1
         6: invokespecial #6                  // Method com/roger/algorithm/Test$2."":(Lcom/roger/algorithm/Test;I)V
         9: invokevirtual #7                  // Method com/roger/algorithm/Test$2.start:()V
        12: return
      LineNumberTable:
        line 21: 0
        line 26: 9
        line 27: 12

反编译结果依赖 Java1.8.0_201,注意最开始的Constant pool,在内部类的初始化方法声明了外部类引用,构造方法中的引用声明省去了,应该是JVM的一个优化。总的原理与JVM低版本相同:如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。

静态内部类有特殊的地方吗?

从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。

这篇文章很好,给了我很大的帮助,我把其中的内容手动实现了一遍,感谢原作者。

https://blog.csdn.net/davidluo001/java/article/details/50377919

你可能感兴趣的:(从反编译内部类来认识final)