Java内部类

概述:作用域:成员内部类和局部内部类;其他角度:匿名内部类、静态内部类。

非静态内部类:

1、Java中非静态内部类实例持有外部类实例的引用,所以它可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
2、当内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。

外部类.this.成员变量
外部类.this.成员方法

3、内部类可以无条件地访问外部类的成员,而外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
4、内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建

5、内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和protected包访问两种权限修饰。

静态内部类:

与非静态内部类相比,只不过在类的前面多了一个关键字static,表示静态内部类是不需要依赖于外部类的实例,即不持有外部类实例的引用,不能随意访问外部类的一切。而不表示不依赖于实例,与静态类有别。实例化:

Outter.Inner inner = Outter.new Inner();

成员内部类

最普通的内部类,即该类的作用域是整个外部类。具有非静态内部了的特点。

局部内部类

定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的

匿名内部类

  匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。

new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub

            }
        }

1、使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须先存在
2、匿名内部类也是不能有访问修饰符和static修饰符的。
3、匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

java命令行编译与反编译

javac   xxx.java            //编译java文件
javap -v innerclass //反编译,innerclass 指编译生成的内部类,不用加.class

下面来一段简单的实验代码Test.java

public class Test {
    public static void main(String[] args)  {

    }

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

当我们反编译.class文件,可以看到一些信息,信息指明:
1、非静态内部类拥有一个指向外部类对象的指针final Test this0;
2、虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的final Test this0;指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对final Test this0;引用进行初始化赋值,也就无法创建成员内部类的对象了。

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

上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。我们先考虑这样一个问题:
  当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制 的手段来解决这个问题。将这段代码的字节码反编译可以得到xxx:

我们看到在run方法中有一条指令:

bipush 10

  这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。
  
同时我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参b以参数的形式传进来对匿名内部类中的拷贝(变量b的拷贝)进行赋值初始化。

  也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

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

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

参考链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值