Java魔法堂:内部类详解

一、前言                              

  对于内部类平时编码时使用的场景不多,比较常用的地方应该就是绑定事件处理程序的时候了(从C#、JS转向Java阵营的孩子总不不习惯用匿名内部类来做事件订阅:()。本文将结合Bytecode对四种内部类作介绍,当作一次梳理以便日后查阅。

  首先要明确的是内部类是编译器提供的特性,编译器会将含内部类的java文件编译成外部类和内部类的N个文件(N>=2) ,然后JVM就按普通类的方式运行。就如下面的源码会被编译为Outer.class和和Outer$Inner.class文件。



class Outer{
  class Inner{}
}

三、成员内部类                        

  定义在一个类的内部。相对外部类仅有默认和public两种访问修饰符而言,成员内部类可有默认、private、proteced和public四种访问修饰符,效果与成员字段和方法的一样。

  示例:

import java.io.*;
// Main.java文件
class Main{
    public static void main(String[] args) throws IOException{
        MemberCls outer = new MemberCls();
        Inner inner1 = outer.new Inner();
        Inner inner2 = outer.getInner();

        System.out.println(inner1.getVal());
        System.out.println(inner2.getVal());
        inner1.setVal(2);
        System.out.println(inner1.getVal());
        System.out.println(inner2.getVal());
        inner2.setVal(3);
        System.out.println(inner1.getVal());
        System.out.println(inner2.getVal());

        System.in.read();
    }
}
// MemberCls.java文件
class MemberCls{
    private int val = 1;

    class Inner{
        void setVal(int val){
            MemberCls.this.val = val;
        }    
        int getVal(){
            return val;
        }
    }

    Inner getInner(){
        return new Inner();
    }
// 运行结果
// 1
// 1
// 2
// 2
// 3
// 3
  并生成MemberCls.class和MemberCls$Inner.class两个类文件。
Classfile /F:/skyDrive/repos/self/jottings/java/sample/01/MemberCls.class
  Last modified 2015-2-3; size 1117 bytes
  MD5 checksum aea71084f78ab319a339717e4d0e1e79
  Compiled from "MemberCls.java"
class MemberCls
  SourceFile: "MemberCls.java"
  InnerClasses:
       #16= #3 of #5; //Inner=class MemberCls$Inner of class MemberCls
  minor version: 0
  major version: 51
  flags: ACC_SUPER

Constant pool:
   #1 = Fieldref           #5.#36         //  MemberCls.val:I
   #2 = Methodref          #15.#37        //  java/lang/Object."<init>":()V
   #3 = Class              #38            //  MemberCls$Inner
   #4 = Methodref          #3.#39         //  MemberCls$Inner."<init>":(LMemberCls;)V
   #5 = Class              #40            //  MemberCls
   #6 = Methodref          #5.#37         //  MemberCls."<init>":()V
   #7 = Methodref          #15.#41        //  java/lang/Object.getClass:()Ljava/lang/Class;
   #8 = Methodref          #5.#42         //  MemberCls.getInner:()LMemberCls$Inner;
   #9 = Fieldref           #43.#44        //  java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #3.#45         //  MemberCls$Inner.getVal:()I
  #11 = Methodref          #46.#47        //  java/io/PrintStream.println:(I)V
  #12 = Methodref          #3.#48         //  MemberCls$Inner.setVal:(I)V
  #13 = Fieldref           #43.#49        //  java/lang/System.in:Ljava/io/InputStream;
  #14 = Methodref          #50.#51        //  java/io/InputStream.read:()I
  #15 = Class              #52            //  java/lang/Object
  #16 = Utf8               Inner
  #17 = Utf8               InnerClasses
  #18 = Utf8               val
  #19 = Utf8               I
  #20 = Utf8               <init>
  #21 = Utf8               ()V
  #22 = Utf8               Code
  #23 = Utf8               LineNumberTable
  #24 = Utf8               getInner
  #25 = Utf8               ()LMemberCls$Inner;
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               Exceptions
  #29 = Class              #53            //  java/io/IOException
  #30 = Utf8               access$002
  #31 = Utf8               (LMemberCls;I)I
  #32 = Utf8               access$000
  #33 = Utf8               (LMemberCls;)I
  #34 = Utf8               SourceFile
  #35 = Utf8               MemberCls.java
  #36 = NameAndType        #18:#19        //  val:I
  #37 = NameAndType        #20:#21        //  "<init>":()V
  #38 = Utf8               MemberCls$Inner
  #39 = NameAndType        #20:#54        //  "<init>":(LMemberCls;)V
  #40 = Utf8               MemberCls
  #41 = NameAndType        #55:#56        //  getClass:()Ljava/lang/Class;
  #42 = NameAndType        #24:#25        //  getInner:()LMemberCls$Inner;
  #43 = Class              #57            //  java/lang/System
  #44 = NameAndType        #58:#59        //  out:Ljava/io/PrintStream;
  #45 = NameAndType        #60:#61        //  getVal:()I
  #46 = Class              #62            //  java/io/PrintStream
  #47 = NameAndType        #63:#64        //  println:(I)V
  #48 = NameAndType        #65:#64        //  setVal:(I)V
  #49 = NameAndType        #66:#67        //  in:Ljava/io/InputStream;
  #50 = Class              #68            //  java/io/InputStream
  #51 = NameAndType        #69:#61        //  read:()I
  #52 = Utf8               java/lang/Object
  #53 = Utf8               java/io/IOException
  #54 = Utf8               (LMemberCls;)V
  #55 = Utf8               getClass
  #56 = Utf8               ()Ljava/lang/Class;
  #57 = Utf8               java/lang/System
  #58 = Utf8               out
  #59 = Utf8               Ljava/io/PrintStream;
  #60 = Utf8               getVal
  #61 = Utf8               ()I
  #62 = Utf8               java/io/PrintStream
  #63 = Utf8               println
  #64 = Utf8               (I)V
  #65 = Utf8               setVal
  #66 = Utf8               in
  #67 = Utf8               Ljava/io/InputStream;
  #68 = Utf8               java/io/InputStream
  #69 = Utf8               read
{
  MemberCls();
    flags: 

    Code:
      stack=2, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #2                  // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: iconst_1      
         6: putfield      #1                  // Field val:I
         9: return        
      LineNumberTable:
        line 2: 0
        line 3: 4
        line 5: 9

  MemberCls$Inner getInner();
    flags: 

    Code:
      stack=3, locals=1, args_size=1
         0: new           #3                  // class MemberCls$Inner
         3: dup           
         4: aload_0       
         5: invokespecial #4                  // Method MemberCls$Inner."<init>":(LMemberCls;)V
         8: areturn       
      LineNumberTable:
        line 15: 0

  public static void main(java.lang.String[]) throws java.io.IOException;
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=4, locals=4, args_size=1
         0: new           #5                  // class MemberCls
         3: dup           
         4: invokespecial #6                  // Method "<init>":()V
         7: astore_1      
         8: new           #3                  // class MemberCls$Inner
        11: dup           
        12: aload_1       
        13: dup           
        14: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
        17: pop           
        18: invokespecial #4                  // Method MemberCls$Inner."<init>":(LMemberCls;)V
        21: astore_2      
        22: aload_1       
        23: invokevirtual #8                  // Method getInner:()LMemberCls$Inner;
        26: astore_3      
        27: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: aload_2       
        31: invokevirtual #10                 // Method MemberCls$Inner.getVal:()I
        34: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        37: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        40: aload_3       
        41: invokevirtual #10                 // Method MemberCls$Inner.getVal:()I
        44: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        47: aload_2       
        48: iconst_2      
        49: invokevirtual #12                 // Method MemberCls$Inner.setVal:(I)V
        52: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        55: aload_2       
        56: invokevirtual #10                 // Method MemberCls$Inner.getVal:()I
        59: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        62: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        65: aload_3       
        66: invokevirtual #10                 // Method MemberCls$Inner.getVal:()I
        69: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        72: aload_3       
        73: iconst_3      
        74: invokevirtual #12                 // Method MemberCls$Inner.setVal:(I)V
        77: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        80: aload_2       
        81: invokevirtual #10                 // Method MemberCls$Inner.getVal:()I
        84: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        87: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        90: aload_3       
        91: invokevirtual #10                 // Method MemberCls$Inner.getVal:()I
        94: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        97: getstatic     #13                 // Field java/lang/System.in:Ljava/io/InputStream;
       100: invokevirtual #14                 // Method java/io/InputStream.read:()I
       103: pop           
       104: return        
      LineNumberTable:
        line 19: 0
        line 20: 8
        line 21: 22
        line 23: 27
        line 24: 37
        line 25: 47
        line 26: 52
        line 27: 62
        line 28: 72
        line 29: 77
        line 30: 87
        line 32: 97
        line 33: 104
    Exceptions:
      throws java.io.IOException

  static int access$002(MemberCls, int);
    flags: ACC_STATIC, ACC_SYNTHETIC

    Code:
      stack=3, locals=2, args_size=2
         0: aload_0       
         1: iload_1       
         2: dup_x1        
         3: putfield      #1                  // Field val:I
         6: ireturn       
      LineNumberTable:
        line 2: 0

  static int access$000(MemberCls);
    flags: ACC_STATIC, ACC_SYNTHETIC

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #1                  // Field val:I
         4: ireturn       
      LineNumberTable:
        line 2: 0
}

MemberCls.class


Classfile /F:/skyDrive/repos/self/jottings/java/sample/01/MemberCls$Inner.class
  Last modified 2015-2-3; size 525 bytes
  MD5 checksum b092ffe3c5b358c786d99d98c104dc40
  Compiled from "MemberCls.java"
class MemberCls$Inner
  SourceFile: "MemberCls.java"
  InnerClasses:
       #25= #5 of #21; //Inner=class MemberCls$Inner of class MemberCls
  minor version: 0
  major version: 51
  flags: ACC_SUPER

Constant pool:
   #1 = Fieldref           #5.#19         //  MemberCls$Inner.this$0:LMemberCls;
   #2 = Methodref          #6.#20         //  java/lang/Object."<init>":()V
   #3 = Methodref          #21.#22        //  MemberCls.access$002:(LMemberCls;I)I
   #4 = Methodref          #21.#23        //  MemberCls.access$000:(LMemberCls;)I
   #5 = Class              #24            //  MemberCls$Inner
   #6 = Class              #27            //  java/lang/Object
   #7 = Utf8               this$0
   #8 = Utf8               LMemberCls;
   #9 = Utf8               <init>
  #10 = Utf8               (LMemberCls;)V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               setVal
  #14 = Utf8               (I)V
  #15 = Utf8               getVal
  #16 = Utf8               ()I
  #17 = Utf8               SourceFile
  #18 = Utf8               MemberCls.java
  #19 = NameAndType        #7:#8          //  this$0:LMemberCls;
  #20 = NameAndType        #9:#28         //  "<init>":()V
  #21 = Class              #29            //  MemberCls
  #22 = NameAndType        #30:#31        //  access$002:(LMemberCls;I)I
  #23 = NameAndType        #32:#33        //  access$000:(LMemberCls;)I
  #24 = Utf8               MemberCls$Inner
  #25 = Utf8               Inner
  #26 = Utf8               InnerClasses
  #27 = Utf8               java/lang/Object
  #28 = Utf8               ()V
  #29 = Utf8               MemberCls
  #30 = Utf8               access$002
  #31 = Utf8               (LMemberCls;I)I
  #32 = Utf8               access$000
  #33 = Utf8               (LMemberCls;)I
{
  final MemberCls this$0;
    flags: ACC_FINAL, ACC_SYNTHETIC



  MemberCls$Inner(MemberCls);
    flags: 

    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: aload_1       
         2: putfield      #1                  // Field this$0:LMemberCls;
         5: aload_0       
         6: invokespecial #2                  // Method java/lang/Object."<init>":()V
         9: return        
      LineNumberTable:
        line 5: 0

  void setVal(int);
    flags: 

    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: getfield      #1                  // Field this$0:LMemberCls;
         4: iload_1       
         5: invokestatic  #3                  // Method MemberCls.access$002:(LMemberCls;I)I
         8: pop           
         9: return        
      LineNumberTable:
        line 7: 0
        line 8: 9

  int getVal();
    flags: 

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #1                  // Field this$0:LMemberCls;
         4: invokestatic  #4                  // Method MemberCls.access$000:(LMemberCls;)I
         7: ireturn       
      LineNumberTable:
        line 10: 0
}

MemberCls$Inner.class

  由于成员内部类依赖于外部类实例,因此创建内部类实例时要先创建外部类实例,然后通过下列两种形式来创建内部类实例:
// 方式一
内部类 内部类实例 = 外部类实例.new 内部类();

// 方式二
外部类{
  内部类{}
  内部类 get内部类(){
    return new 内部类();
  }
}
内部类 内部类实例 = 外部类实例.get内部类();

  注意:
    1. 当成员内部类拥有与外部类同名的成员变量或方法时,默认是使用成员内部类的成员。若要访问外部类的同名成员,则需要进行如下操作:
外部类.this.成员变量;
外部类.this.成员方法;

    2. 对于同一个外部类实例创建的内部类实例,这些内部类实例均操作同一个外部实例。像上述例子那样,均操作同一个val字段。
        看Bytecodes可知,编译器自动为MemberCls创建创建两个静态方法access$002和access$000,而MemberCls$Inner实例则通过这两个静态方法访问私有私有字段val的。
// MemberCls.class文件

/** 等价于
 * static int setVal(MemberCls outer, int val){
 *     outer.val = val;
 *      return val;
 * }
 */
static int access$002(MemberCls, int);
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=3, locals=2, args_size=2
         0: aload_0       
         1: iload_1       
         2: dup_x1        
         3: putfield      #1                  // Field val:I
         6: ireturn       
      LineNumberTable:
        line 2: 0

/** 等价于
 * static int getVal(MemberCls outer){
 *      return outer.val;
 * }
 */
  static int access$000(MemberCls);
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #1                  // Field val:I
         4: ireturn       
      LineNumberTable:
        line 2:


// MemberCls$Inner.class文件

/** 等价于
 * void setVal(int val){
 *     MemberCls实例.setVal(val);
 * }
 */
void setVal(int);
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: getfield      #1                  // Field this$0:LMemberCls;
         4: iload_1       
         5: invokestatic  #3                  // Method MemberCls.access$002:(LMemberCls;I)I
         8: pop           
         9: return        
      LineNumberTable:
        line 7: 0
        line 8: 9

/** 等价于
 * int getVal(int val){
 *     MemberCls实例.getVal();
 * }
 */
  int getVal();
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #1                  // Field this$0:LMemberCls;
         4: invokestatic  #4                  // Method MemberCls.access$000:(LMemberCls;)I
         7: ireturn       
      LineNumberTable:
        line 10: 0

 因此内部类可以访问外部类的所有类型的字段和方法(包括private)。

 

四、局部内部类                           

  局部内部类定义在方法或某个作用域里面,并且仅限于方法和该作用域内访问。

  示例:

import java.io.*;

class Main{
    public static void main(String[] args) throws IOException{
        LocalCls outer = new LocalCls();
        outer.print();

        System.in.read();
    }
}

class LocalCls{
    private int val = 1;

    void print(){
        final String name = "fsjohnhuang";
        class Inner{
            int getVal(){
                return val;
            }    
            void setVal(int val){
                LocalCls.this.val = val;
            }
            String getName(){
                return name;
            }
        }

        Inner inner = new Inner();
        System.out.println(inner.getVal());
        inner.setVal(2);
        System.out.println(inner.getVal());
        System.out.println(inner.getName());
    }
}
// 结果:
// 1
// 2
// fsjohnhuang
生成LocalCls.class和LocalCls$1Inner.class两个类文件。
Classfile /F:/skyDrive/repos/self/jottings/java/sample/02/LocalCls.class
  Last modified 2015-2-3; size 1052 bytes
  MD5 checksum a636f470da37d8c1cb9370dde083d6d8
  Compiled from "LocalCls.java"
class LocalCls
  SourceFile: "LocalCls.java"
  InnerClasses:
       #17= #8; //Inner=class LocalCls$1Inner
  minor version: 0
  major version: 51
  flags: ACC_SUPER

Constant pool:
   #1 = Fieldref           #3.#36         //  LocalCls.val:I
   #2 = Methodref          #16.#37        //  java/lang/Object."<init>":()V
   #3 = Class              #38            //  LocalCls
   #4 = Methodref          #3.#37         //  LocalCls."<init>":()V
   #5 = Methodref          #3.#39         //  LocalCls.print:()V
   #6 = Fieldref           #40.#41        //  java/lang/System.in:Ljava/io/InputStream;
   #7 = Methodref          #42.#43        //  java/io/InputStream.read:()I
   #8 = Class              #44            //  LocalCls$1Inner
   #9 = Methodref          #8.#45         //  LocalCls$1Inner."<init>":(LLocalCls;)V
  #10 = Fieldref           #40.#46        //  java/lang/System.out:Ljava/io/PrintStream;
  #11 = Methodref          #8.#47         //  LocalCls$1Inner.getVal:()I
  #12 = Methodref          #48.#49        //  java/io/PrintStream.println:(I)V
  #13 = Methodref          #8.#50         //  LocalCls$1Inner.setVal:(I)V
  #14 = Methodref          #8.#51         //  LocalCls$1Inner.getName:()Ljava/lang/String;
  #15 = Methodref          #48.#52        //  java/io/PrintStream.println:(Ljava/lang/String;)V
  #16 = Class              #53            //  java/lang/Object
  #17 = Utf8               Inner
  #18 = Utf8               InnerClasses
  #19 = Utf8               val
  #20 = Utf8               I
  #21 = Utf8               <init>
  #22 = Utf8               ()V
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               main
  #26 = Utf8               ([Ljava/lang/String;)V
  #27 = Utf8               Exceptions
  #28 = Class              #54            //  java/io/IOException
  #29 = Utf8               print
  #30 = Utf8               access$000
  #31 = Utf8               (LLocalCls;)I
  #32 = Utf8               access$002
  #33 = Utf8               (LLocalCls;I)I
  #34 = Utf8               SourceFile
  #35 = Utf8               LocalCls.java
  #36 = NameAndType        #19:#20        //  val:I
  #37 = NameAndType        #21:#22        //  "<init>":()V
  #38 = Utf8               LocalCls
  #39 = NameAndType        #29:#22        //  print:()V
  #40 = Class              #55            //  java/lang/System
  #41 = NameAndType        #56:#57        //  in:Ljava/io/InputStream;
  #42 = Class              #58            //  java/io/InputStream
  #43 = NameAndType        #59:#60        //  read:()I
  #44 = Utf8               LocalCls$1Inner
  #45 = NameAndType        #21:#61        //  "<init>":(LLocalCls;)V
  #46 = NameAndType        #62:#63        //  out:Ljava/io/PrintStream;
  #47 = NameAndType        #64:#60        //  getVal:()I
  #48 = Class              #65            //  java/io/PrintStream
  #49 = NameAndType        #66:#67        //  println:(I)V
  #50 = NameAndType        #68:#67        //  setVal:(I)V
  #51 = NameAndType        #69:#70        //  getName:()Ljava/lang/String;
  #52 = NameAndType        #66:#71        //  println:(Ljava/lang/String;)V
  #53 = Utf8               java/lang/Object
  #54 = Utf8               java/io/IOException
  #55 = Utf8               java/lang/System
  #56 = Utf8               in
  #57 = Utf8               Ljava/io/InputStream;
  #58 = Utf8               java/io/InputStream
  #59 = Utf8               read
  #60 = Utf8               ()I
  #61 = Utf8               (LLocalCls;)V
  #62 = Utf8               out
  #63 = Utf8               Ljava/io/PrintStream;
  #64 = Utf8               getVal
  #65 = Utf8               java/io/PrintStream
  #66 = Utf8               println
  #67 = Utf8               (I)V
  #68 = Utf8               setVal
  #69 = Utf8               getName
  #70 = Utf8               ()Ljava/lang/String;
  #71 = Utf8               (Ljava/lang/String;)V
{
  LocalCls();
    flags: 

    Code:
      stack=2, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #2                  // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: iconst_1      
         6: putfield      #1                  // Field val:I
         9: return        
      LineNumberTable:
        line 3: 0
        line 11: 4

  public static void main(java.lang.String[]) throws java.io.IOException;
    flags: ACC_PUBLIC, ACC_STATIC

    Code:
      stack=2, locals=2, args_size=1
         0: new           #3                  // class LocalCls
         3: dup           
         4: invokespecial #4                  // Method "<init>":()V
         7: astore_1      
         8: aload_1       
         9: invokevirtual #5                  // Method print:()V
        12: getstatic     #6                  // Field java/lang/System.in:Ljava/io/InputStream;
        15: invokevirtual #7                  // Method java/io/InputStream.read:()I
        18: pop           
        19: return        
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 8: 12
        line 9: 19
    Exceptions:
      throws java.io.IOException

  void print();
    flags: 

    Code:
      stack=3, locals=3, args_size=1
         0: new           #8                  // class LocalCls$1Inner
         3: dup           
         4: aload_0       
         5: invokespecial #9                  // Method LocalCls$1Inner."<init>":(LLocalCls;)V
         8: astore_2      
         9: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        12: aload_2       
        13: invokevirtual #11                 // Method LocalCls$1Inner.getVal:()I
        16: invokevirtual #12                 // Method java/io/PrintStream.println:(I)V
        19: aload_2       
        20: iconst_2      
        21: invokevirtual #13                 // Method LocalCls$1Inner.setVal:(I)V
        24: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        27: aload_2       
        28: invokevirtual #11                 // Method LocalCls$1Inner.getVal:()I
        31: invokevirtual #12                 // Method java/io/PrintStream.println:(I)V
        34: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        37: aload_2       
        38: invokevirtual #14                 // Method LocalCls$1Inner.getName:()Ljava/lang/String;
        41: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        44: return        
      LineNumberTable:
        line 26: 0
        line 27: 9
        line 28: 19
        line 29: 24
        line 30: 34
        line 31: 44

  static int access$000(LocalCls);
    flags: ACC_STATIC, ACC_SYNTHETIC

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #1                  // Field val:I
         4: ireturn       
      LineNumberTable:
        line 3: 0

  static int access$002(LocalCls, int);
    flags: ACC_STATIC, ACC_SYNTHETIC

    Code:
      stack=3, locals=2, args_size=2
         0: aload_0       
         1: iload_1       
         2: dup_x1        
         3: putfield      #1                  // Field val:I
         6: ireturn       
      LineNumberTable:
        line 3: 0
}

LocalCls.class


Classfile /F:/skyDrive/repos/self/jottings/java/sample/02/LocalCls$1Inner.class
  Last modified 2015-2-3; size 651 bytes
  MD5 checksum a4bf7c12f15f22b2ebb3f79438a555ab
  Compiled from "LocalCls.java"
class LocalCls$1Inner
  SourceFile: "LocalCls.java"
  EnclosingMethod: #23.#24                // LocalCls.print
  InnerClasses:
       #31= #6; //Inner=class LocalCls$1Inner
  minor version: 0
  major version: 51
  flags: ACC_SUPER

Constant pool:
   #1 = Fieldref           #6.#25         //  LocalCls$1Inner.this$0:LLocalCls;
   #2 = Methodref          #7.#26         //  java/lang/Object."<init>":()V
   #3 = Methodref          #23.#27        //  LocalCls.access$000:(LLocalCls;)I
   #4 = Methodref          #23.#28        //  LocalCls.access$002:(LLocalCls;I)I
   #5 = String             #29            //  fsjohnhuang
   #6 = Class              #30            //  LocalCls$1Inner
   #7 = Class              #33            //  java/lang/Object
   #8 = Utf8               this$0
   #9 = Utf8               LLocalCls;
  #10 = Utf8               <init>
  #11 = Utf8               (LLocalCls;)V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               getVal
  #15 = Utf8               ()I
  #16 = Utf8               setVal
  #17 = Utf8               (I)V
  #18 = Utf8               getName
  #19 = Utf8               ()Ljava/lang/String;
  #20 = Utf8               SourceFile
  #21 = Utf8               LocalCls.java
  #22 = Utf8               EnclosingMethod
  #23 = Class              #34            //  LocalCls
  #24 = NameAndType        #35:#36        //  print:()V
  #25 = NameAndType        #8:#9          //  this$0:LLocalCls;
  #26 = NameAndType        #10:#36        //  "<init>":()V
  #27 = NameAndType        #37:#38        //  access$000:(LLocalCls;)I
  #28 = NameAndType        #39:#40        //  access$002:(LLocalCls;I)I
  #29 = Utf8               fsjohnhuang
  #30 = Utf8               LocalCls$1Inner
  #31 = Utf8               Inner
  #32 = Utf8               InnerClasses
  #33 = Utf8               java/lang/Object
  #34 = Utf8               LocalCls
  #35 = Utf8               print
  #36 = Utf8               ()V
  #37 = Utf8               access$000
  #38 = Utf8               (LLocalCls;)I
  #39 = Utf8               access$002
  #40 = Utf8               (LLocalCls;I)I
{
  final LocalCls this$0;
    flags: ACC_FINAL, ACC_SYNTHETIC



  LocalCls$1Inner(LocalCls);
    flags: 

    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: aload_1       
         2: putfield      #1                  // Field this$0:LLocalCls;
         5: aload_0       
         6: invokespecial #2                  // Method java/lang/Object."<init>":()V
         9: return        
      LineNumberTable:
        line 15: 0

  int getVal();
    flags: 

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: getfield      #1                  // Field this$0:LLocalCls;
         4: invokestatic  #3                  // Method LocalCls.access$000:(LLocalCls;)I
         7: ireturn       
      LineNumberTable:
        line 17: 0

  void setVal(int);
    flags: 

    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: getfield      #1                  // Field this$0:LLocalCls;
         4: iload_1       
         5: invokestatic  #4                  // Method LocalCls.access$002:(LLocalCls;I)I
         8: pop           
         9: return        
      LineNumberTable:
        line 20: 0
        line 21: 9

  java.lang.String getName();
    flags: 

    Code:
      stack=1, locals=1, args_size=1
         0: ldc           #5                  // String fsjohnhuang
         2: areturn       
      LineNumberTable:
        line 23: 0
}

LocalCls$1Inner.class

  上述两个类文件与成员内部类的几乎一模一样,那么就是说内部类作用范围的限制其实是编译器的限制,而不是JVM的限制了。

  注意:

    1. 不能有public、protected、private和static作修饰;

    2. 局部内部类中仅能访问方法或作用域内的常量,若访问的是变量则编译时会出错。

       Q:为什么不能访问局部变量呢?

       A:假设可以访问局部变量,那么要考虑的是如何引用到局部变量。

            首先局部变量是存放在JVM栈帧中的局部变量表中,并且当方法执行完栈帧也随之弹出,也就是说局部变量所占的内存空间是短暂的(不稳定)。

            假如局部变量A是基本类型的话,那么数据直接就存放在局部变量表中相应的Slots中,方法执行完就没了。那局部内部类中所访问的局部变量A到底是什么就 无从得知了!             假如局部变量A是String类型或其他类类型,那么局部内部类中访问的局部变量A时就有两种方式了,第一种是访问String常量池中该字符串的地 址,第二种是指向局部变量A的地址,然后通过变量A去访问String常量池中该字符串。

            但上述这些均是在运行时才能决定,而编译时是无法正确地被描述出来。并且由于内部类将被编译成独立的类文件,访问其他类方法的局部变量的操作无法在类文件 中描述。而常量则可以在内部类文件的常量池部分中被正确地描述,而JVM中处理时也十分简单高效。类文件的常量池条目将合并到运行时常量池中,因此外部和 内部量访问的是同一个常量。

      下面的Bytecodes表示内部类中直接将常量池中的常量压栈后作为返回值返回。

java.lang.String getName();
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: ldc           #5                  // String fsjohnhuang
         2: areturn       
      LineNumberTable:
        line 23:

  

五、匿名内部类                          

  匿名内部类其实是局部内部类的特殊形式。一般用来绑定事件监听处理程序上。Android示例:

class Outer{
  public void subs(){ 
      scan_bt.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
            // TODO Auto-generated method stub
             
            }
        });
  }
}

  上述代码生成了一个继承OnClickListener类的匿名内部类,然后实例化匿名类的一个实例,然后以该实例作为参数调用setOnClickListener方法。
  并生成一个Outer.class和Outer$1.class类文件。

  注意事项与局部内部一样。

 

六、静态内部类                          

  静态内部类定义在类下,只不过多了个关键字static。静态内部类只能访问外部类的静态字段和静态方法。
  而实例化静态内部类时只需 new 外部类.静态内部类() 。

上一篇:如何让自己的简历在万人从中一眼就能被HR选中


下一篇:安卓中高级开发面试之数据核心——数组集合高频面试题及解析