Java 内部类详解

静态内部类

除了静态内部类之外,其它的都默认持有外部类的引用,构造函数中传进去,所以都可以通过Outer.this(默认的不需要显示)访问外部类的属性和方法,包括private的,另一方面这有可能造成内存泄漏,比如Android中handler的定义

非静态内部类(也叫成员内部类)

public class Outter {
    private int age = 10;
    class Inner {
        public int getAge() {
            return age;
            //return Outter.this.age;
        }
        public Outter getOutter() {
            return Outter.this;
        }
    }
}

对Outter$Inner.class反编译一下

class Outter$Inner
{
  Outter$Inner(Outter paramOutter) {} //默认持有外部类引用
  public int getAge()
  {
    return Outter.access$0(this.this$0);
  }
  public Outter getOutter()
  {
    return this.this$0;
  }
}

age等价于Outter.this.age; 默认持有外部类的引用

局部内部类/匿名内部类

局部内部类/匿名内部类 不能有访问符,访问的局部变量必须final修饰 jdk8以前的话,必须强制是final,否则会编译不通过。但是jdk8,新特性只有检查到了基本类型改变了值/对象指向了新引用才会报编译错误,否则如果是只是访问基本类型/String或者修改对象内部是不会报错的

举例 局部变量是基本类型/String s = “binjing”; 此时在常量区,局部内部类默认构造函数会复制一份基本类型

public class Outter {
    private String s = "Outter";
    private void test() {
        String s = "Local";
        String name="binjing";
        class LocalInner {
            String s = "Inner";
            public String getStr() {
                return s; //此时返回的是"Inner",内部类的属性,局部变量“Local"被无视了
            }
            public String getOutterStr() {
                return Outter.this.s; //此时返回的是外部类的属性"Outter"
            }
            public String getName(){
                return name; //返回局部变量name
            }
        }
        // s = "hahja"; jdk8报错,改变了s指向
    }

可以看到局部内部类可以直接访问外部类属性,没有什么限制。同时jdk8中这样是不会报错的,但是jdk8以下版本就会强制要求s修饰为final
对Outter$1LocalInner.class反编译一下

class Outter$1LocalInner
{
  String s = "Inner";
  Outter$1LocalInner(Outter paramOutter, String paramString) {} //持有外部类的引用,同时只有一个String(name)参数,因为局部s被内部类的s覆盖掉了
  public String getStr()
  {
    return this.s; //表明是内部类的属性
  }
  public String getOutterStr()
  {
    return Outter.access$1(this.this$0); //表明是外部类的属性
  }
  public String getName()
  {
    return this.val$name;
  }
}

上述例子说明了内部类怎么去访问外部属性,内部属性,以及局部变量

    private void test() {
        String s = "binjing";
        class LocalInner {
            public String getStr() {
                return s;
            }
        }
        s = "LiaBin";
    }

但是此时就算是在jdk8还是会报编译错误的,因为此时检测到局部变量被修改了,还是提示要final修饰。 jdk8上去掉s = "LiaBin";即可不需要final修饰 Jdk8的特性

另外的例子,局部变量是对象类型,复制一个对象的引用,局部内部类默认构造函数会复制对象的引用,但是还是执行同一个对象的

    private void test(StringBuilder name) {
        new Thread() { //此时匿名内部类会复制name引用,但是指向的却是堆中的同一个对象
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("test: " + name.toString());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();

        name.append(" + haha");
        //name = new StringBuilder("hahaaaa"); 报错,引用指向了别的对象
    }

所以jdk8中这样是不会报错的,除非name = new StringBuilder("hahaaaa");引用指向了别的对象会报错。当然在jdk8以下都报错,提示需要final

局部内部类为什么只能访问final局部变量?

通过Outter$1LocalInner.class反编译的结果我们可以看到,局部变量都会通过局部内部类的构造函数传递进去,为什么要这么处理?
局部变量的生命周期与局部内部类的对象的生命周期的不一致。局部变量当所处的函数执行结束后就已经死亡了,不存在了,但是局部内部类对象还可能一直存在(只要有人还引用该对象),这是就会出现了一个悲剧的结果,局部内部类对象访问一个已不存在的局部变量。Java为了避免上述情况,才发明了上述机制,偷偷地将局部变量的引用放在内部类对象的成员变量中。上述的例子不能说明这个问题

    private void anoTest() {
        String name = "BinJing";
        new Thread() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    Thread.sleep(2000);
                    System.out.println("name: " + name);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }.start();
    }

调用anoTest()方法,一调用那么name引用因为是局部变量在栈中(binjing还是在字符串常量池的),所以该变量立刻无效了,但是匿名对象Thread这个对象的生命周期需要等待2s才结束,休眠了2s,所以生命周期不一致了吧,如果不做局部变量都会通过局部内部类的构造函数传递进去处理,那么肯定有问题的,局部变量的name引用在栈中作用域一过就消失,Thread的name引用是在堆中的,new出来的对象都是在堆中创建,类的属性也在堆中,需要等到GC才能回收该对象
Outter$1.class反编译

class Outter$1
  extends Thread
{
  Outter$1(Outter paramOutter, String paramString) {} //生成的构造函数,默认持有外部类引用,同时会把访问的局部变量参数传递进来,一份拷贝

  public void run()
  {
    try
    {
      Thread.sleep(2000L);
      System.out.println("name: " + this.val$name);
    }
    catch (InterruptedException e)
    {
      e.printStackTrace();
    }
  }
}

但是解决了生命周期的问题之后,又引出了另一个问题,这样的话,如果不用final修饰,那么内部类就可以肆无忌惮的修改局部变量name属性了,所以虚拟机为了约束这两个变量的一致性,所以虚拟机强制要求使用final,否则编译不过.final保证了内部类和局部变量是同一个对象或者基本类型,对于对象,修改对象的内容是没问题的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值