内部类
内部类包括如下几种形式:
l作为类成员的内部类;
l成员方法中定义的内部类;
l匿名内部类;
l静态内部类。
内部类和外部类的定义形式相同,但是需要在类中定义:
public classMyOuter {
public class MyInner{
}
}
该文件编译后能够生成两个类:MyOuter.class和MyOuter$MyInner.class。
注意:内部类的访问控制符有:public、protected、private和缺省的,与类的成员变量和成员方法的访问控制符相同,而外部类的访问控制符只有public和缺省的。
内部类可以访问所在的外部类的成员变量,包括私有成员变量,就像类自身的方法能够访问类的所有类型的成员变量一样,例如下面的代码是正确的:
public classMyOuter {
private String name="test";
class MyInner{
publicvoid print(){
System.out.println(name);
}
}
}
内部类中不能定义static类型的方法,下面的代码是错误的:
public class MyOuter{
private String name="test";
class MyInner{
publicstatic void print(){
System.out.println(name);
}
}
}
内部类可以是抽象的,内部类可以继承某个类,也可以实现某个接口,
public classMyOuter {
private String name="test";
abstract class MyInner1{
publicabstract void print();
}
class MyInner2 extends MyInner1implements Serializable{
publicvoid print(){
System.out.println(name);
}
}
}
内部类也可以是final类。
可以把内部类的对象作为外部类的成员变量使用,可以在外部类的方法中使用内部类的对象。
下面的代码演示了如何把内部类的对象作为成员并且展示了两种实例化的代码:
public classMyOuter {
private MyInner in=new MyInner(); //定义的时候实例化
private MyInner in2; //先定义
public MyOuter(){
in2= new MyInner(); //在构造方法中实例化,也可以在其他方法中实例化
}
…
}
下面的代码展示了如何在外部类的方法中使用内部类的对象:
public classMyOuter {
public void makeInner(){
MyInnerin3 = new MyInner(); //方法中定义变量并实例化
in3.print();
}
…
}
上面的实例化代码相当于MyOuter.MyInner in3 = this.new MyInner();,因为在类的内部使用所以在类型前面省略了MyOuter,实例化的时候省略的“this.”。
在外部类的代码之外使用内部类,就像访问类的实例变量一样,需要先创建外部类的对象,然后通过外部类的对象访问内部类的方法,下面的代码展示了用法:
public static void main(String[] args) {
MyOuter.MyInnerin = new MyOuter().new MyInner();
in.print();
MyOuterout = new MyOuter();
MyOuter.MyInnerin2 = out.new MyInner();
in2.print();
}
代码中展示了两种方式,两种方式的作用是相同的,下面的代码是错误的:
MyOuter.MyInnerin3 = MyOuter.new MyInner();
MyOuter.MyInnerin4 = new MyOuter.MyInner();
注意:在外部类的代码之外使用内部类,内部类必须是可见的,下面的代码是错误的:
public classMyOuter {
private class MyInner{
publicvoid print(){
System.out.println(name);
}
}
…
}
public classMain {
public static void main(String[] args) {
MyOuter.MyInnerin = new MyOuter().new MyInner();
}
}
在类中引用当前实例可以使用this,但是内部类中要引用内部类的实例或者内部类所在的外部类的实例如何引用呢?在内部类中引用内部类自身的实例仍然是通过this,在内部类中引用外部类的实例需要使用外部类的名字加上this的方式,下面的代码展示了用法:
public classMyOuter {
int x=20;
public class MyInner2{
intx=10;
publicvoid print(){
int x=5;
System.out.println("局部变量x的值:"+x);
System.out.println("内部类成员变量x的值:"+this.x);
System.out.println("外部类成员变量x的值:"+MyOuter.this.x);
}
}
public static void main(String args[]){
newMyOuter().new MyInner2().print();
}
…
}
程序的运行结果:
局部变量x的值:5
内部类成员变量x的值:10
外部类成员变量x的值:20
成员方法中的内部类的编写与作为成员变量的内部类的编写方法基本相同,下面的代码展示了用法:
public classMyOuter2 {
public void test() {
classMyInner{
public void print(){
System.out.println(x);
}
}
}
}
方法内部的类具有如下特点:
l方法内部定义的类就像方法的局部变量,所以在类外或者类的其他方法中不能访问这个内部类。
l没有访问控制符,因为该类只能在定义该类的方法中并且在类的定义之后使用;
l不能访问方法的局部变量,除非局部变量是final类型的;
l能够访问外部类的各种成员变量,如果内部类所在的方法是静态方法,则这个内部类只能访问外部类的静态成员;
l可以使用final和abstract修饰。
方法中的内部类只能在定义该内部类的方法中访问,并且是方法中内部类的定义之后访问。下面的代码展示了用法:
public void main2(String[] args) {
classMyInner{
public void print(){
System.out.println(x);
}
}
MyInnerin = new MyInner();
in.print();
}
顾名思义,匿名内部类没有类名,而我们创建对象是根据类名创建的,没有类名如何创建对象的?可以使用类的定义直接创建实例,例如下面的代码:
Parent p = newParent() {
publicvoid print() {
System.out.println("匿名内部类中的输出方法");
}
};
new后面的信息表示创建了一个继承了Parent类的子类的实例。
匿名内部类有两种形式:
l通过继承父类而形式的匿名内部类;
l通过实现接口而形式的匿名内部类。
下面分别介绍。
看下面的代码:
class Parent {
public void print() {
System.out.println("父类的输出方法");
}
}
public classMyOuter3 {
Parent p = new Parent() {
publicvoid print() {
System.out.println("匿名内部类中的输出方法");
}
};
}
在MyOut3中声明了类型为Parent的成员变量,通过new Parent来实例化对象,但是这里的实例化代码与以前见到过的实例化代码不同,以前的代码这样写:
Parent p = newParent();
而这里是:
Parent p = newParent(){
这里的大括号意味着新创建了一个类,这个类是Parent的子类,在子类中可以覆盖父类中的方法或者实现父类中定义的抽象方法,上面的代码重写了父类中的print方法。
注意在倒数第二行的“};”,这个分号不能省略。
上面的代码中定义的成员变量p指向了匿名内部类的实例,也可以在成员方法中定义局部变量指向匿名内部类的实例。
对于匿名内部类的实例的方法的调用是通过执行该实例的父类引用,利用了Java语言的多态性。下面的代码展示了如何调用(在MyOuter3中增加的代码):
public static void main(String[] args){
newMyOuter3().p.print();
}
输出结果是:
匿名内部类中的输出方法
注意:因为匿名内部类中的方法都是通过父类引用访问的,所以不能在匿名内部类中定义父类中没有声明的方法,这样的方法不能被访问到。如果是为了匿名内部类中的方法之间共享代码,可以编写供内部方法调用的在父类中没有定义的方法。
package test;
public class niming {
public staticvoid main(String[] args) {
newMyOuter3().p.print();
newMyOuter3().p.say();//这个语句会报错的。
报错信息:The method say() is undefined for the type Parent
也就是说,在编译的时候,是按照父类parent进行编译的,编译器并不认识父类中没有定义过的方法。
}
}
class Parent {
public voidprint() {
System.out.println("父类的输出方法");
}
}
class MyOuter3 {
Parent p = newParent() {
publicvoid print() {
System.out.println("匿名内部类中的输出方法");
}
public voidsay(){
System.out.println("这个是父类中没有的方法");
}
};
}
但是在定义say方法的时候,编译器并不抱此条信息的错误,因为,这些方法都是在运行的时候才起作用的,只要运行的起来那么一定能够工作。这就是java的多态机制。如下证明:
稍稍该代码如下:
public void print() {
System.out.println("匿名内部类中的输出方法");
say();
}
那么运行结果为:
匿名内部类也可以通过实现接口来创建,与继承父类没有本质区别。下面的代码展示了使用接口创建匿名内部类:
interface Fly{
public void takeof(); //起飞
public void landing(); //降落
}
public classMyOuter3 {
Fly bird = new Fly(){
publicvoid takeof(){
System.out.println("小鸟起飞了");
}
publicvoid landing(){
System.out.println("小鸟降落了");
}
};
}
代码中new Fly(){…}意味着创建了一个实现了Fly接口的类的对象,这个类就是匿名内部类。这个匿名内部类必须实现接口中定义的所有方法,这与普通类实现接口的要求是相同的。在这个类中也不能定义其他的业务方法,这些方法是通过接口的引用访问的,因为接口中没有声明该方法,所以该方法将不能被访问。
想要访问还是用上面介绍的 方法,即
package test;
public class niming {
public staticvoid main(String[] args) {
newMyOuter3().bird.landing();
}
}
interface Fly{
public voidtakeof(); //起飞
public voidlanding(); //降落
}
class MyOuter3 {
Fly bird = newFly(){
publicvoid takeof(){
System.out.println("小鸟起飞了");
}
public void landing(){
System.out.println("小鸟降落了");
eat();
}
voideat(){
System.out.println("小鸟吃虫子了");
}
};
}
注意:new Fly(){…}实例化的对象是实现了Fly接口的类的对象,而不是实例化Fly接口,接口是不能被实例化的。
这些匿名内部类被实例化以后通过父类引用或者接口引用访问,通常都会有一个引用指向这个新创建的匿名内部类的实例。有个特殊的应用不需要创建引用指向匿名内部类的实例,而是传递给形参,看下面的代码(在MyOuter3中添加如下代码):
public static void main(String[] args){
//直接把匿名内部类的实例作为实参使用
newMyOuter3().printFly(new Fly(){
public void takeof(){
System.out.println("飞机起飞");
}
public void landing(){
System.out.println("飞机降落");
}
});
}
public void printFly(Fly fly){ //参数需要是实现Fly接口的实现类的对象
fly.takeof();
fly.landing();
}
输出:
注意:我们自己在开发程序的时候尽量不要使用内部类,内部类不利于代码重用,如果非常确定这个类只会使用一次,可以使用匿名内部类。
静态内部类与作为成员的内部类基本相同,只是这个内部类是静态的,使用static,类似于静态成员变量和静态成员方法。下面的代码展示了如何定义静态内部类:
public classMyOuter4 {
static class MyInner{
…
}
}
与静态方法一样,在静态内部类中只能访问外部类的静态方法,看下面的代码:
public classMyOuter4 {
int x = 10;
static int y = 20;
static class MyInner{
publicvoid print(){
System.out.println(x); //错误
System.out.println(y); //正确
}
}
}
System.out.println(x);是错误的,因为它试图访问非静态成员变量x,System.out.println(y)是正确的。
在实例化作为实例成员变量的内部类的时候,要先创建外部类的对象,然后通过对象的new操作符创建内部类的实例。而静态内部类就像静态方法一样,可以通过类名直接访问而不用创建实例。下面的代码展示了在外部类的内容和外部类的外部如何访问静态内部类(第一个方法是在MyOuter4中添加的代码,第二个方法是测试类中的代码):
public void outprint(){
MyInnerinner = new MyInner();
inner.print();
}
public static void main(String args[]){
MyOuter4.MyInnerinner = new MyOuter4.MyInner();
inner.print();
}
(1) 在类中定义一个类(私有内部类,静态内部类)
(2) 在方法中定义一个类(局部内部类,匿名内部类)
1、私有内部类——在方法之间定义的内部类,非静态
我们首先看看类中内部类的两个特点:
(1) 在外部类的作用范围内可以任意创建内部类对象,即使内部类是私有的(私有内部类)。即内部类对包围它的外部类可见。
(2) 在内部类中可以访问其外部类的所有域,即使是私有域。即外部类对内部类可见。
10
问题来了:上面两个特点到底如何办到的呢?内部类的"内部"到底发生了什么?
其实,内部类是Java编译器一手操办的。虚拟机并不知道内部类与常规类有什么不同。编译器是如何瞒住虚拟机的呢?
对内部类进行编译后发现有两个class文件:Outer.class和Outer$Inner.class。这说明内部类Inner仍然被编译成一个独立的类(Outer$Inner.class),而不是Outer类的某一个域。虚拟机运行的时候,也是把Inner作为一种常规类来处理的。
但问题来了,即然是两个常规类,为什么他们之间可以互相访问私有域那(最开始提到的两个内部类特点)?这就要问问编译器到底把这两个类编译成什么东西了。
我们利用reflect反射机制来探查了一下内部类编译后的情况(关于探查类内部机制的代码提供在下面的附件里Reflect.java)。
(1)、编译代码1生成 Outer$Inner.class 文件后使用 ReflectUtil.reflect("Outer$Inner") 对内部类Inner进行反射。运行结果 发现了三个隐含的成分:
好了,现在我们可以解释上面的第一个内部类特点了: 为什么外部类可以创建内部类的对象?并且内部类能够方便的引用到外部类对象?
首先编译器将外、内部类编译后放在同一个包中。在内部类中附加一个包可见构造器。这样,虚拟机运行Outer类中Inner in=new Inner();实际上调用的是包可见构造: newOuter$Inner(this,null)。因此即使是private内部类,也会通过隐含的包可见构造器成功的获得私有内部类的构造权限。
再者,Outer$Inner类中有一个指向外部类Outer的引用this$0,那么通过这个引用就可以方便的得到外部类对象中可见成员。但是Outer类中的private成员是如何访问到的呢?这就要看看下面Outer.class文件中的秘密了。
(2)、编译代码2生成 Outer.class文件,然后使用 ReflectUtil.reflect("Outer") 对外部类Outer进行反射 。 运行结果 发现一个隐含成分如下:
29
现在可以解释第二个特点了:为什么内部类可以引用外部类的私有域?
原因的关键就在编译器在外围类中添加了静态方法access$0。它将返回值作为参数传递给他的对象域data。这样内部类Inner中的打印语句:
System.out.println(data);
实际上运行的时候调用的是:
System.out.println(this$0.access$0(Outer));
总结一下编译器对类中内部类做的手脚吧:
(1) 在内部类中偷偷摸摸的创建了包可见构造器,从而使外部类获得了创建权限。
(2) 在外部类中偷偷摸摸的创建了访问私有变量的静态方法,从而使内部类获得了访问权限。
这样,类中定义的内部类无论私有,公有,静态都可以被包围它的外部类所访问。
2、静态内部类——在方法间定义的内部类,静态
内部类也有静态的区别,这就是静态内部类,我们来看看代码:
34
静态内部类和私有内部类最大的区别在于,静态内部类中无法引用到其外围类的非静态成员。这是为什么?我们还是来看看静态内部类Outer$Inner中发生了什么吧?
与上面私有内部类反编译1比较发现,少了一个指向外围类对象的引用final Outer this$0; 也就是说静态内部类无法得到其外围类对象的引用,那么自然也就无法访问外围类的非静态成员了。因此,静态内部类只能访问其外围类的静态成员,除此之外与非静态内部类没有任何区别。
3、局部内部类——在方法中定义的内部类
方法内部类也有两个特点
(1) 方法中的内部类没有访问修饰符, 即方法内部类对包围它的方法之外的任何东西都不可见。
(2) 方法内部类只能够访问该方法中的局部变量,所以也叫局部内部类。而且这些局部变量一定要是final修饰的常量。
这又是为什么呢?
(1) 我们首先对Outter类进行反射发现,Outter中再也没有返回私有域的隐藏方法了。
(2) 对Inner类的反射发现,Inner类内部多了一个对beep变量的备份隐藏域:final int val$i;
我们可以这样解释Inner类中的这个备份常量域,首先当JVM运行到需要创建Inner对象之后,Outter类已经全部运行完毕,这是垃圾回收机制很有可能释放掉局部变量beep。那么Inner类到哪去找beep变量呢?
编译器又出来帮我们解决了这个问题,他在Inner类中创建了一个beep的备份,也就是说即使Ouuter中的beep被回收了,Inner中还有一个备份存在,自然就不怕找不到了。
但是问题又来了。如果Outter中的beep不停的在变化那。那岂不是也要让备份的beep变量无时无刻的变化。为了保持局部变量与局部内部类中备份域保持一致。编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。
所以为什么局部内部类应用外部方法的域必须是常量域的原因所在了。
内部类的特点总结
(1) 在方法间定义的非静态内部类:
●外围类和内部类可互相访问自己的私有成员。
●内部类中不能定义静态成员变量。
(2) 在方法间定义的静态内部类:
●只能访问外部类的静态成员。
(3) 在方法中定义的局部内部类:
●该内部类没有任何的访问控制权限
●外围类看不见方法中的局部内部类的,但是局部内部类可以访问外围类的任何成员。
●方法体中可以访问局部内部类,但是访问语句必须在定义局部内部类之后。
●局部内部类只能访问方法体中的常量,即用final修饰的成员。
(4) 在方法中定义的匿名内部类:
●没有构造器,取而代之的是将构造器参数传递给超类构造器。