天天看点

从字节码角度深入分析内部类(局部内部类,成员内部类,静态内部类,匿名内部类)

1.      局部内部类

源码:

public void subscribeQueue(Jedis jedis,String[] channels) {

           // TODO Auto-generated method stub

           class MyJedisPubSub extendsJedisPubSub{

                    public void onMessage(Stringchannel, String message) { 

                             System.out.println(message);

                             loadLua(jedis,message);

              } 

           }

           MyJedisPubSub myJedisPubSub = newMyJedisPubSub();

           jedis.subscribe(myJedisPubSub ,channels);

}

方法字节码:

  public voidsubscribeQueue(redis.clients.jedis.Jedis, java.lang.String[]);

    descriptor: (Lredis/clients/jedis/Jedis;[Ljava/lang/String;)V

    flags: ACC_PUBLIC

    Code:

      stack=4, locals=4, args_size=3

         0: new           #401                // classRedisDaoImpl$1MyJedisPubS

ub

         3: dup

         4: aload_0

         5: aload_1

         6: invokespecial #403                // MethodRedisDaoImpl$1MyJedisPub

Sub."<init>":(LRedisDaoImpl;Lredis/clients/jedis/Jedis;)V

         9: astore_3

        10: aload_1

        11: aload_3

        12: aload_2

        13: invokevirtual #406                // Methodredis/clients/jedis/Jedi

s.subscribe:(Lredis/clients/jedis/JedisPubSub;[Ljava/lang/String;)V

        16: return

      LineNumberTable:

        line 497: 0

        line 498: 10

        line 499: 16

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0      17    0  this   LRedisDaoImpl;

            0      17    1 jedis  Lredis/clients/jedis/Jedis;

            0      17    2 channels   [Ljava/lang/String;

           10       7    3 myJedisPubSub  LRedisDaoImpl$1MyJedisPubSub;

}

SourceFile: "RedisDaoImpl.java"

InnerClasses:

     #416= #401; //MyJedisPubSub=classRedisDaoImpl$1MyJedisPubSub

     public static #417= #124 of #133;//Entry=class java/util/Map$Entry of clas

s java/util/Map

内部类字节码:

  Last modified 2017-7-21;size 1053 bytes

  MD5 checksum 0f8a8ded714120d03f866c55bf5177d9

  Compiled from"RedisDaoImpl.java"

classRedisDaoImpl$1MyJedisPubSub extends redis.clients.jedis.JedisPubSub

  minor version: 0

  major version: 52

  flags: ACC_SUPER

Constantpool:

   #1 = Class              #2             // RedisDaoImpl$1MyJedisPubSub

   #2 = Utf8               RedisDaoImpl$1MyJedisPubSub

   #3 = Class              #4             // redis/clients/jedis/JedisPubSub

   #4 = Utf8               redis/clients/jedis/JedisPubSub

   #5 = Utf8               this$0

   #6 = Utf8               LRedisDaoImpl;

   #7 = Utf8               val$jedis

   #8 = Utf8               Lredis/clients/jedis/Jedis;

   #9 = Utf8               <init>

  #10 = Utf8               (LRedisDaoImpl;Lredis/clients/jedis/Jedis;)V

  #11 = Utf8               Code

  #12 = Fieldref           #1.#13         // RedisDaoImpl$1MyJedisPubSub.this$0:

LRedisDaoImpl;

  #13 = NameAndType        #5:#6          // this$0:LRedisDaoImpl;

  #14 = Fieldref           #1.#15         // RedisDaoImpl$1MyJedisPubSub.val$jed

is:Lredis/clients/jedis/Jedis;

  #15 = NameAndType        #7:#8          //val$jedis:Lredis/clients/jedis/Jedi

s;

  #16 = Methodref          #3.#17         // redis/clients/jedis/JedisPubSub."<i

nit>":()V

  #17 = NameAndType        #9:#18         // "<init>":()V

  #18 = Utf8               ()V

  #19 = Utf8               LineNumberTable

  #20 = Utf8               LocalVariableTable

  #21 = Utf8               this

  #22 = Utf8               LRedisDaoImpl$1MyJedisPubSub;

  #23 = Utf8               onMessage

  #24 = Utf8              (Ljava/lang/String;Ljava/lang/String;)V

  #25 = Fieldref           #26.#28        // java/lang/System.out:Ljava/io/Print

Stream;

  #26 = Class              #27            // java/lang/System

  #27 = Utf8               java/lang/System

  #28 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;

  #29 = Utf8               out

  #30 = Utf8               Ljava/io/PrintStream;

  #31 = Methodref          #32.#34        // java/io/PrintStream.println:(Ljava/

lang/String;)V

  #32 = Class              #33            // java/io/PrintStream

  #33 = Utf8               java/io/PrintStream

  #34 = NameAndType        #35:#36        // println:(Ljava/lang/String;)V

  #35 = Utf8               println

  #36 = Utf8               (Ljava/lang/String;)V

  #37 = Methodref          #38.#40        // RedisDaoImpl.loadLua:(Lredis/client

s/jedis/Jedis;Ljava/lang/String;)Ljava/lang/String;

  #38 = Class              #39            // RedisDaoImpl

  #39 = Utf8               RedisDaoImpl

  #40 = NameAndType        #41:#42        // loadLua:(Lredis/clients/jedis/Jedis

;Ljava/lang/String;)Ljava/lang/String;

  #41 = Utf8               loadLua

  #42 = Utf8               (Lredis/clients/jedis/Jedis;Ljava/lang/String;)Ljava/

lang/String;

  #43 = Utf8               channel

  #44 = Utf8               Ljava/lang/String;

  #45 = Utf8               message

  #46 = Utf8               SourceFile

  #47 = Utf8               RedisDaoImpl.java

  #48 = Utf8               EnclosingMethod

  #49 = NameAndType        #50:#51        // subscribeQueue:(Lredis/clients/jedi

s/Jedis;[Ljava/lang/String;)V

  #50 = Utf8               subscribeQueue

  #51 = Utf8              (Lredis/clients/jedis/Jedis;[Ljava/lang/String;)V

  #52 = Utf8               InnerClasses

  #53 = Utf8               MyJedisPubSub

{

  final RedisDaoImplthis$0;

    descriptor:LRedisDaoImpl;

    flags: ACC_FINAL,ACC_SYNTHETIC

  private finalredis.clients.jedis.Jedis val$jedis;

    descriptor: Lredis/clients/jedis/Jedis;

    flags: ACC_PRIVATE,ACC_FINAL, ACC_SYNTHETIC

  RedisDaoImpl$1MyJedisPubSub(RedisDaoImpl,redis.clients.jedis.Jedis);

    descriptor:(LRedisDaoImpl;Lredis/clients/jedis/Jedis;)V

    flags:

    Code:

      stack=2,locals=3, args_size=3

         0: aload_0

         1: aload_1

         2:putfield      #12                 // Field this$0:LRedisDaoImpl;

         5: aload_0

         6: aload_2

         7:putfield      #14                 // Fieldval$jedis:Lredis/clients/

jedis/Jedis;

        10:aload_0

        11:invokespecial #16                 //Method redis/clients/jedis/Jedi

sPubSub."<init>":()V

        14: return

      LineNumberTable:

        line 491: 0

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0      15    0  this   LRedisDaoImpl$1MyJedisPubSub;

  public void onMessage(java.lang.String,java.lang.String);

    descriptor:(Ljava/lang/String;Ljava/lang/String;)V

    flags: ACC_PUBLIC

    Code:

      stack=3, locals=3, args_size=3

         0: getstatic     #25                 // Fieldjava/lang/System.out:Ljav

a/io/PrintStream;

         3: aload_2

         4: invokevirtual #31                 // Methodjava/io/PrintStream.prin

tln:(Ljava/lang/String;)V

         7: aload_0

         8: getfield      #12                 // Field this$0:LRedisDaoImpl;

        11: aload_0

        12: getfield      #14                 // Fieldval$jedis:Lredis/clients/

jedis/Jedis;

        15: aload_2

        16: invokevirtual #37                 // Method RedisDaoImpl.loadLua:(Lr

edis/clients/jedis/Jedis;Ljava/lang/String;)Ljava/lang/String;

        19: pop

        20: return

      LineNumberTable:

        line 493: 0

        line 494: 7

        line 495: 20

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0      21    0  this   LRedisDaoImpl$1MyJedisPubSub;

            0      21    1 channel   Ljava/lang/String;

            0      21    2 message   Ljava/lang/String;

}

SourceFile: "RedisDaoImpl.java"

EnclosingMethod:#38.#49                //RedisDaoImpl.subscribeQueue

InnerClasses:

     #53= #1;//MyJedisPubSub=class RedisDaoImpl$1MyJedisPubSub

--可以看到局部内部类的字节码布局和正常内部类大同小异,但是注意红色字体内容,首先局部内部类定义了一个外部类对象的引用final RedisDaoImpl this$0;还有如果内部类使用外围方法的局部变量(包括参数),那么还得在内部类中定义和该局部变量一致的引用比如private final redis.clients.jedis.Jedis val$jedis;这个有什么用呢,再看内部类的构造方法:RedisDaoImpl$1MyJedisPubSub(RedisDaoImpl, redis.clients.jedis.Jedis);可见构造方法中是需要初始化这两个成员变量的,而这两个成员变量其实就是使用了外部类的this引用和外围方法参数Jedis jedis来赋值的:

0:aload_0

         1: aload_1

         2: putfield      #12                 // Field this$0:LRedisDaoImpl;

         5: aload_0

         6: aload_2

         7: putfield      #14                 // Fieldval$jedis:Lredis/clients/

jedis/Jedis;

--指令的意思是转载参数1和参数2 ,并赋值给成员变量this$0和val$jedis。(注意对于外部类指针而言是必须的定义和赋值的,而参数是内部类使用到才需要这样操作),目的就是局部内部类可以正常使用外部类的成员变量和方法(包括私有)还有外围方法的局部变量。编译器通过这种机制保证局部内部类的使用访问效果跟局部语句块一样。

注意:内部类的构造方法跟正常类不一样,它先初始化了编译器添加的成员变量后再调用父类的构造方法。

2.成员内部类

源码:

publicclass ChildTest extends test {

Map<String,String> map = newHashMap<String,String>();

ChildTest() {

           map.put("hello","world");

           // TODOAuto-generated constructor stub

}

void fun(){

}

class interClass{

           void usefun(){

                    map.get("hello");

                    fun();

           }

}

}

内部类的字节码:

Classfile/D:/javadevelop/eclipse/user/workspace/RedisDao/bin/ChildTest$interCla

ss.class

  Last modified 2017-7-21;size 629 bytes

  MD5 checksum 49d8bf61790022b45521275fc68a0abd

  Compiled from"ChildTest.java"

classChildTest$interClass

  minor version: 0

  major version: 52

  flags: ACC_SUPER

Constantpool:

   #1 = Class              #2             // ChildTest$interClass

   #2 = Utf8               ChildTest$interClass

   #3 = Class              #4             // java/lang/Object

   #4 = Utf8               java/lang/Object

   #5 = Utf8               this$0

   #6 = Utf8               LChildTest;

   #7 = Utf8               <init>

   #8 = Utf8               (LChildTest;)V

   #9 = Utf8               Code

  #10 = Fieldref           #1.#11         // ChildTest$interClass.this$0:LChildT

est;

  #11 = NameAndType        #5:#6          // this$0:LChildTest;

  #12 = Methodref          #3.#13         //java/lang/Object."<init>":()V

  #13 = NameAndType        #7:#14         // "<init>":()V

  #14 = Utf8               ()V

  #15 = Utf8               LineNumberTable

  #16 = Utf8               LocalVariableTable

  #17 = Utf8               this

  #18 = Utf8               LChildTest$interClass;

  #19 = Utf8               usefun

  #20 = Fieldref           #21.#23        // ChildTest.map:Ljava/util/Map;

  #21 = Class              #22            // ChildTest

  #22 = Utf8               ChildTest

  #23 = NameAndType        #24:#25        // map:Ljava/util/Map;

  #24 = Utf8               map

  #25 = Utf8              Ljava/util/Map;

  #26 = String             #27            // hello

  #27 = Utf8               hello

  #28 = InterfaceMethodref #29.#31        // java/util/Map.get:(Ljava/lang/Objec

t;)Ljava/lang/Object;

  #29 = Class              #30            // java/util/Map

  #30 = Utf8               java/util/Map

  #31 = NameAndType        #32:#33        // get:(Ljava/lang/Object;)Ljava/lang/

Object;

  #32 = Utf8               get

  #33 = Utf8              (Ljava/lang/Object;)Ljava/lang/Object;

  #34 = Methodref          #21.#35        // ChildTest.fun:()V

  #35 = NameAndType        #36:#14        // fun:()V

  #36 = Utf8               fun

  #37 = Utf8               SourceFile

  #38 = Utf8               ChildTest.java

  #39 = Utf8               InnerClasses

  #40 = Utf8               interClass

{

  final ChildTestthis$0;

    descriptor:LChildTest;

    flags: ACC_FINAL,ACC_SYNTHETIC

  ChildTest$interClass(ChildTest);

    descriptor: (LChildTest;)V

    flags:

    Code:

      stack=2, locals=2, args_size=2

         0: aload_0

         1: aload_1

         2:putfield      #10                 // Field this$0:LChildTest;

         5: aload_0

         6: invokespecial #12                 // Methodjava/lang/Object."<init>

":()V

         9: return

      LineNumberTable:

        line 29: 0

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0      10    0  this   LChildTest$interClass;

  void usefun();

    descriptor: ()V

    flags:

    Code:

      stack=2, locals=1, args_size=1

         0: aload_0

         1: getfield      #10                 // Field this$0:LChildTest;

         4: getfield      #20                 // FieldChildTest.map:Ljava/util/

Map;

         7: ldc           #26                 // String hello

         9: invokeinterface #28,  2          // InterfaceMethod java/util/Map.g

et:(Ljava/lang/Object;)Ljava/lang/Object;

        14: pop

        15: aload_0

        16: getfield      #10                 // Field this$0:LChildTest;

        19: invokevirtual #34                 // Method ChildTest.fun:()V

        22: return

      LineNumberTable:

        line 31: 0

        line 32: 15

        line 33: 22

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0      23    0  this   LChildTest$interClass;

}

SourceFile: "ChildTest.java"

InnerClasses:

     #40= #1 of #21;//interClass=class ChildTest$interClass of class ChildTest

--其实和局部方法类似,还更简单点,编译器自动添加了一个外部类引用,并且把外部类的指针传递进来赋给了该引用,明白这点就能明白为什么局部内部类和成员内部类可以正常访问外部类的所有成员,更明白了为什么创建一个局部内部类或成员内部类对象必须用外部类成员对象.new创建(因为这两类内部类的构造方法中需要传入外部类对象作为参数)。如:

ChildTesttest = new ChildTest();

           ChildTest.interClasstest2 = test.new interClass();

这两行代码字节码如下:

         0: new           #1                  // class ChildTest

         3: dup

         4: invokespecial #36                 // Method"<init>":()V

         7: astore_1

         8: new           #37                 // class ChildTest$interClass

        11: dup

        12: aload_1

        13: dup

        14: invokevirtual #39                 // Methodjava/lang/Object.getClas

s:()Ljava/lang/Class;

        17: pop

        18: invokespecial #45                 // MethodChildTest$interClass."<i

nit>":(LChildTest;)V

        21: astore_2

                   22: return

--翻译过来就是先new出一个ChildTest对象,然后把该对象作为参数创建出一个ChildTest$inter对象。

2.      静态内部类:

源码:

publicclass ChildTest extends test {

private Map<String,String> map = newHashMap<String,String>();

ChildTest() {

           map.put("hello","world");

           // TODO Auto-generated constructorstub

}

void fun(){

}

static class interClass{

           void usefun(){

                    System.out.println("zrf");

           }

}

public static void main(String[] args) {

           // TODO Auto-generated method stub

           ChildTest.interClass test2 = newChildTest.interClass();

}

}

内部类的字节码:

  Last modified 2017-7-22; size 551 bytes

  MD5 checksum 20c26ef1c8018c21b7c63e47edc3938d

  Compiled from "ChildTest.java"

classChildTest$interClass

  minor version: 0

  major version: 52

  flags: ACC_SUPER

Constantpool:

   #1 = Class              #2             // ChildTest$interClass

   #2 = Utf8               ChildTest$interClass

   #3 = Class              #4             // java/lang/Object

   #4 = Utf8               java/lang/Object

   #5 =Utf8               <init>

   #6 = Utf8               ()V

   #7 = Utf8               Code

   #8 = Methodref          #3.#9          //java/lang/Object."<init>":()V

   #9 = NameAndType        #5:#6          // "<init>":()V

  #10 = Utf8               LineNumberTable

  #11 = Utf8               LocalVariableTable

  #12 = Utf8               this

  #13 = Utf8               LChildTest$interClass;

  #14 = Utf8               usefun

  #15 = Fieldref           #16.#18        // java/lang/System.out:Ljava/io/Print

Stream;

  #16 = Class              #17            // java/lang/System

  #17 = Utf8               java/lang/System

  #18 = NameAndType        #19:#20        // out:Ljava/io/PrintStream;

  #19 = Utf8               out

  #20 = Utf8               Ljava/io/PrintStream;

  #21 = String             #22            // zrf

  #22 = Utf8               zrf

  #23 = Methodref          #24.#26        // java/io/PrintStream.println:(Ljava/

lang/String;)V

  #24 = Class              #25            // java/io/PrintStream

  #25 = Utf8               java/io/PrintStream

  #26 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V

  #27 = Utf8               println

  #28 = Utf8               (Ljava/lang/String;)V

  #29 = Utf8              SourceFile

  #30 = Utf8               ChildTest.java

  #31 = Utf8               InnerClasses

  #32 = Class              #33            // ChildTest

  #33 = Utf8               ChildTest

  #34 = Utf8               interClass

{

  ChildTest$interClass();

    descriptor: ()V

    flags:

    Code:

      stack=1,locals=1, args_size=1

         0: aload_0

         1:invokespecial #8                  //Method java/lang/Object."<init>

":()V

         4: return

      LineNumberTable:

        line 29: 0

      LocalVariableTable:

        Start  Length Slot  Name   Signature

            0       5    0  this   LChildTest$interClass;

  void usefun();

    descriptor: ()V

    flags:

    Code:

      stack=2,locals=1, args_size=1

         0:getstatic     #15                 // Fieldjava/lang/System.out:Ljav

a/io/PrintStream;

         3:ldc           #21                 // String zrf

         5:invokevirtual #23                 //Method java/io/PrintStream.prin

tln:(Ljava/lang/String;)V

         8: return

     LineNumberTable:

        line 31: 0

        line 32: 8

     LocalVariableTable:

        Start  Length Slot  Name   Signature

            0       9    0  this   LChildTest$interClass;

}

--首先看看构造方法,红色字段,可见静态成员内部类的构造方法相对简单,就是个无参构造方法,另外编译器也不会帮我们定义什么成员变量。再看内部类的成员方法,没什么好讲,跟正常类的方法一样。另外注意,静态内部类对外部类的访问权限跟静态方法一样,只能调用外部类的静态成员,不能调用外部类的非静态成员。

3.      匿名内部类

源码:

publicclass ChildTest extends test {

private Map<String,String> map = newHashMap<String,String>();

public interface interClass{

           public void usefun(int a);

}

ChildTest() {

           map.put("hello","world");

           // TODO Auto-generated constructorstub

}

public void test(){

}

void fun(int a){

           new interClass(){

                    public void usefun(int b){

                             System.out.println(a);

                    }

           }.usefun(a);

}

}内部类字节码:

  Last modified 2017-7-22; size 701 bytes

  MD5 checksum 391c128ec2eb239d4927333fd42c99f7

  Compiled from "ChildTest.java"

classChildTest$1 implements ChildTest$interClass

  minor version: 0

  major version: 52

  flags: ACC_SUPER

Constantpool:

   #1 = Class              #2             // ChildTest$1

   #2 = Utf8               ChildTest$1

   #3 = Class              #4             // java/lang/Object

   #4 = Utf8               java/lang/Object

   #5 = Class              #6             // ChildTest$interClass

   #6 = Utf8               ChildTest$interClass

   #7 = Utf8               this$0

   #8 = Utf8               LChildTest;

   #9 = Utf8               val$a

  #10 = Utf8               I

  #11 = Utf8               <init>

  #12 = Utf8               (LChildTest;I)V

  #13 = Utf8               Code

  #14 = Fieldref           #1.#15         // ChildTest$1.this$0:LChildTest;

  #15 = NameAndType        #7:#8          // this$0:LChildTest;

  #16 = Fieldref           #1.#17         // ChildTest$1.val$a:I

  #17 = NameAndType        #9:#10         // val$a:I

  #18 = Methodref          #3.#19         //java/lang/Object."<init>":()V

  #19 = NameAndType        #11:#20        // "<init>":()V

  #20 = Utf8               ()V

  #21 = Utf8               LineNumberTable

  #22 = Utf8               LocalVariableTable

  #23 = Utf8               this

  #24 = Utf8               LChildTest$1;

  #25 = Utf8               usefun

  #26 = Utf8               (I)V

  #27 = Fieldref           #28.#30        // java/lang/System.out:Ljava/io/Print

Stream;

  #28 = Class              #29            // java/lang/System

  #29 = Utf8               java/lang/System

  #30 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;

  #31 = Utf8               out

  #32 = Utf8               Ljava/io/PrintStream;

  #33 = Methodref          #34.#36        // java/io/PrintStream.println:(I)V

  #34 = Class              #35            // java/io/PrintStream

  #35 = Utf8               java/io/PrintStream

  #36 = NameAndType        #37:#26        // println:(I)V

  #37 = Utf8               println

  #38 = Utf8               b

  #39 = Utf8               SourceFile

  #40 = Utf8               ChildTest.java

  #41 = Utf8               EnclosingMethod

  #42 = Class              #43            // ChildTest

  #43 = Utf8               ChildTest

  #44 = NameAndType        #45:#26        // fun:(I)V

  #45 = Utf8               fun

  #46 = Utf8               InnerClasses

  #47 = Utf8               interClass

{

  final ChildTest this$0;

    descriptor:LChildTest;

    flags: ACC_FINAL,ACC_SYNTHETIC

  private final intval$a;

    descriptor: I

    flags: ACC_PRIVATE,ACC_FINAL, ACC_SYNTHETIC

 ChildTest$1(ChildTest, int);

    descriptor:(LChildTest;I)V

    flags:

    Code:

      stack=2,locals=3, args_size=3

         0: aload_0

         1: aload_1

         2:putfield      #14                 // Field this$0:LChildTest;

         5: aload_0

         6: iload_2

         7:putfield      #16                 // Field val$a:I

        10: aload_0

        11:invokespecial #18                 //Method java/lang/Object."<init>

":()V

        14: return

      LineNumberTable:

        line 1: 0

        line 33: 10

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0      15    0  this   LChildTest$1;

  public void usefun(int);

    descriptor: (I)V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=2, args_size=2

         0: getstatic     #27                 // Fieldjava/lang/System.out:Ljav

a/io/PrintStream;

         3: aload_0

         4: getfield      #16                 // Field val$a:I

         7: invokevirtual #33                 // Method java/io/PrintStream.prin

tln:(I)V

        10: return

      LineNumberTable:

        line 35: 0

        line 36: 10

      LocalVariableTable:

        Start Length  Slot  Name  Signature

            0      11    0  this   LChildTest$1;

            0      11    1     b   I

}

SourceFile:"ChildTest.java"

EnclosingMethod:#42.#44                // ChildTest.fun

InnerClasses:

     #1; //class ChildTest$1

     public static #47= #5 of #42;//interClass=class ChildTest$interClass of cl

ass ChildTest

--编译器会为匿名内部类命名一个类名,一般是外部类名$1,再看看里面的实现,编译器为我们添加了两个成员变量:final ChildTest this$0和private final int val$a;并且构造方法也是先把该成员变量用外部类对象的this指针初始化后再去构造父类对象。这样匿名内部类就可以调用外部类的成员了。看到这里是不是眼熟,是的,其实匿名内部类的字节码实现机制和局部内部类是几乎一模一样的。唯一不同之处就是对于匿名内部类,编译器给他一个命名。另外需注意一点,局部内部类和匿名内部类都是有生效区域限制的,只能在外围方法内使用。跟局部变量的生命周期一样。