Java 内部类详解

来源:互联网 发布:创意中国设计大赛 知乎 编辑:程序博客网 时间:2024/06/06 05:42

内部类顾名思义:就是类中还有类,Java内部类的出现解决了类中被private修饰的变量或引用可以被直接访问到。

成员内部类:

先看一下内部类的写法。

public class Outer {private int x=9;class Inner{//内部类void fun(){System.out.println("Inner:"+x);}}}
因为该内部类和成员变量是同级别的,所以叫成员内部类,既然是成员内部类,那成员变量的修饰符完全适用于内部类中。下面看一下内部类实例的创建

Outer.Inner inner = new Outer().new Inner();
这样就得到了内部类的实例。大家是否有这样的疑惑,我的天,这都是什么, 这里new Outer()表示外部类对象,new Inner()表示内部类对象,而前面的定义是为了区分那个类中的内部类,可以想象,两个内部类名相同,外部类名不同,如果直接写成
Inner inner = new Inner();
那么JVM是不确定你找的具体哪个内部类,所以这样写也在情理之中了。那么问题来了,怎么在内部类中访问外部类的成员呢,通过运行上面代码可以看到控制台输出的是:Inner:9,这里其实是省略了Outer.this.x。因为内部类持有外部类持有了一个外部类的引用,才能直接访问外部类的成员。好,把上面的例子做个简单修改:

class Outer {private  int x=9;class Inner{int x=6;void fun(){int x=3;<span style="font-family: Arial, Helvetica, sans-serif;">//①</span>System.out.println("Inner:"+x);}}}
本次打印结果是 Inner:3,不对呀,上面刚说省略的Outer.this.x,结果应该是9呀,这涉及到Java中的就近原则,附近有这个变量就不出去拿了。那再把上面①注释掉,发现这次打印结果是6,依然是就近原则,那如何在不注释掉①的情况下,随意访问这三个x变量呢?访问Outer的x用Outer.this.x,访问Inner的x用this.x,就可以了。上面说过,成员内部类的修饰符完全适用于内部类中,static也如此,那么被static修饰后的内部类有什么特点呢。可以对比静态成员变量,访问局限性,无法访问非静态的,只是访问内部类时不需要外部类的对象了,创建的实例时候也会有一些小变化:

new Outer.Inner();
这里就创建了Inner类的实例(注:是内部类实例,不是外部类的),再看一段代码:

class Outer {private  int x=9;class Inner{static int x=0;//这里报错void fun(){System.out.println(this.toString());}}}
上述写法的错误的,根据JVM加载Class顺序,必然优先加载类才能够加载静态域,在加载非静态成员。静态成员是不依赖对象就能够访问的,可是内部类属于成员变量,而非静态,又需要一个外部类的对象,这样看来内部类的静态变得毫无意义,所以在定义内部类静态变量时,内部类本身必须被static修饰

小结:成员内部类是跟成员变量同级别,只有同级别的才叫成员内部类,才能适用于成员变量的修饰符。这是其他内部类不具有的特性,内部类可以任意访问外部类的成员变量(包括私有的),外部类访问内部类成员变量需要在外部类中建立内部类的对象才能访问。内部类很少有被public修饰,接口除外。


局部内部类:

局部内部类顾名思义就是写在局部中,下面是例子:

class Outer {public int x = 9;public void fun() {class Inner {void fun() {System.out.println(x);}}}}
这里写在方法中就是局部内部类,该类的非静态的,如果不new对象的话,fun()方法里的类是不会加载和执行的。访问权限修饰符适用于成员变量,所以这里Inner局部内部类是无法使用public private static等修饰符的。下面咋看一段代码:

class Outer {public int x = 9;public void fun() {private int x = 6;//报错,提示需要final修饰后才可以 class Inner {void fun() {System.out.println(x);}}}}
上述代码是有问题的,报错注释已写明,原因是局部内部类在方法体中,属于栈内存,局部局部内部类在堆内存中,栈内存随着方法执行完毕立即释放空间,而堆内存则不然,需要等JVM垃圾回收才会消失,那么这里就存在一个问题,方法执行完毕x消失,可是对象存在并且访问x,这必然会报找不到变量x异常,为了解决这个问题,final修饰后变成常量,改变了存储空间,进而解决该问题。继续修改上面代码。

class Outer {public int x = 9;public void outerfun(final int x) {//  x++;class Inner {void fun() {System.out.println(x);}}new Inner().fun();}}
主函数这样访问

outer.outerfun(1);outer.outerfun(2);

这样是否可以呢?这里的疑惑在于x被final修饰了,能被赋两次吗?答案是完全可以,它们两个没有必然联系,当调用fun方法时被压进栈,执行完毕释放空间,再次被压进栈,所以这两个x是两个不同的变量。把上面的x++注释方法才会正常报错。


小结:内部类只能访问被final修饰的变量,不能被成员修饰符修饰。

匿名内部类:

顾名思义,就是没用名字的内部类,它是内部类的简写格式,定义内部类时必须继承一个类或者实现接口,下面是一个非匿名内部类的写法。

abstract class AbsDemo{abstract void show();}class Outer {public void fun(){class Inner extends AbsDemo{@Overridevoid show() {System.out.println("Inner:show");}}}}

接下来就可以用Inner的对象.shou()方法就能够正常调用了,这里只是为了调用show方法而把代码写的这样复杂,接下来就用到匿名内部类,它完全能够简化内部类中的代码。

abstract class AbsDemo{abstract void show();}class Outer {public void fun(){new AbsDemo() {@Overridevoid show() {System.out.println("Inner:show");}}.show();}}
这就是匿名内部类的写法,大家都知道抽象类无法创建实例,这里与之不同的是new AbsDemo()后面还有大括号,这等同于重写了AbsDemo中的抽象方法,而大括号结束后得到的是AbsDemo的子类(能重写父类方法必然是子类),在调用show方法就可以了。其实匿名内部类的实质就是一个匿名子类的对象,写匿名内部类就是为了覆盖方法简化书写,虽然这样简化了书写,但也有弊端,比如父类中的方法有5个,使用匿名内部类覆盖会发现可阅读性特别差,所以一般情况要覆盖父类方法中不超过3个,再看一个例子:

class Outer {public void fun(){new AbsDemo() {@Overridevoid show() {System.out.println("Inner:show");}//子类特有方法void method(){System.out.println("method");}}.show();}}
调用父类方法在后面直接.show()就行了,那子类特有方法怎么调用,不用想了,根本无法调用,因为该方法子类特有,子类无名子,没有办法使用子类的实例来接受这个对象,如果使用父类的接受调用依然出错,因为父类未定义该方法。这也是匿名内部类另外一个弊端。在show()上定义个int x=5;这样也是可以的,AbsDemo(){}这里的是类的大括号,是类就可以定义成员变量和成员方法,该种写法确实不妥,这样为了简化而出现的匿名内部类就失去了它的意思。


小结:匿名内部类为了简化而生,应用于在创建完类之后立即就执行中,Android开发中经常使用匿名内部类。



出处:http://blog.csdn.net/u010829905/article/details/47667085

1 0