在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。内部类总体上可以分为两种:静态内部类和非静态内部类。而非静态内部类又可以分为三种:成员内部类,局部内部类和匿名内部类。
1.成员内部类
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
//内部类
class Draw {
public void drawShape() {
//外部类的private成员
System.out.println(radius);
//外部类的静态成员
System.out.println(count);
}
}
}
注:当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类
.this.
成员变量
外部类
.this.
成员方法
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
//
第一种方式:
Outer outter = new
Outer();
//
必须通过
Outter
对象来创建
Outer.Inner inner = outer.new
Inner();
//
第二种方式:
Outer.Inner inner1 = outer.getInnerInstance();
成员内部类的使用场景:容器类中的迭代器都是通过成员内部类来实现,大家可以思考这样设计的原因,可以加深你对成员内部类使用场景的理解。
2.局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
3.匿名内部类
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。之所以使用匿名类,是因为我们的程序中只需要创建对应实现的一个对象即可,例如工厂模式中的工厂类通过匿名类来实现则是一个不错的选择。
4.静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。 静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
从前面可知,对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象。
创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new外部类类名.内部类类名()
创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx =外部类对象名.new 内部类类名()
与其它内部类相比较,局部内部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,这些局部变量必须被声明为final。对它进行初始化后不能再进行修改。因此,就保证局部变量与在局部类中建立的拷贝保持一致。匿名类内部使用的局部变量也要声明为final常量。
示例代码:
public void start(int interval, final boolean beep) {
<span style="white-space:pre"> </span>// Inner Class
<span style="white-space:pre"> </span>class TimePrinter implements ActionListener {
<span style="white-space:pre"> </span>@Override
<span style="white-space:pre"> </span>public void actionPerformed(ActionEvent event) {
<span style="white-space:pre"> </span>Date now = new Date();
<span style="white-space:pre"> </span>System.out.println(“At the tone, the time is “ + now);
<span style="white-space:pre"> </span>if (beep) {
<span style="white-space:pre"> </span>Toolkit.getDefaultToolkit().beep();
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>ActionListener listener = new TimePrinter();
<span style="white-space:pre"> </span>Timer t = new Timer(interval, listener);
<span style="white-space:pre"> </span>t.start();
}
我们在start函数中定义了一个内部类TimePrinter,其actionPerformed方法中访问了函数的局部变量beep。为什么参数beep要声明为final呢?
为了说明内部类访问局部变量为什么要加final关键字,我们先来看一下java对内部类的实现。
假设上述代码中start函数所在的类的名称为TalkingClock。则编译上述代码的时候,JAVA编译器会把TimePrinter内部类编译为一个独立的class文件。其形式如下(类名为:外部类$内部类):class TalkingClock$1TimePrinter {
<span style="white-space:pre"> </span>// 添加的构造函数,参数为外部类对象的引用和该内部类访问的局部变量的引用(这里为 boolean类型)
<span style="white-space:pre"> </span>TalkingClock$1TimePrinter(TalkingClock, boolean);
<span style="white-space:pre"> </span>// 内部类原有的函数
<span style="white-space:pre"> </span>public void actionPerformed(java.awt.event.ActionEvent);
<span style="white-space:pre"> </span>// 局部变量的引用
<span style="white-space:pre"> </span>final boolean val$beep;
<span style="white-space:pre"> </span>// 外部类对象的引用
<span style="white-space:pre"> </span>final TalkingClock this$0;
}
通过上述类的定义,我们可以看出内部类在构造的时候,会被编译器自动传入外部类对象的一个引用,同时也会传入内部类访问的局部变量的引用,这也就解释了内部类对象为什么可以访问外部类的成员变量和函数还有局部变量了。但是由于这些工作是在编译时进行的,java虚拟机并没有什么所谓的内部类的概念,在java虚拟机看来,该内部类和外部类是两个独立的class文件。我们知道,一个类的私有函数和成员变量是不能被其他类访问的。那么内部类又是如何访问外部类的私有成员变量和函数呢?
我们假设外部类中有一个私有的int型的变量counter。我们想在内部类中访问它。其实在编译的时候,为了内部类可以访问外部类的私有变量,java编译器还偷偷做了一些额外的工作。编译器除了上文提到的会生成一个内部类,同时还会修改我们的外部类代码。其修改如下:
class TalkingClock {
<span style="white-space:pre"> </span>// 编译器自动添加的函数,用来访问私有成员变量counter
<span style="white-space:pre"> </span>static int access$0(TalkingClock);
<span style="white-space:pre"> </span>// 原有的函数
<span style="white-space:pre"> </span>public void start();
<span style="white-space:pre"> </span>// 私有成员变量
<span style="white-space:pre"> </span>private int counter
}
可以看出,为了访问counter私有成员变量,编译器偷偷的为我们添加了一个access$0的静态函数,它接收一个TalkingClock对象的引用,并返回该对象内的coutner的值。并且编译器会把我们在内部类中用到counter的地方都替换为TalkingClock.access$0(this$0)。
好了,现在我们已经了解了内部类的实现机制,那我们最后来看一下访问局部变量为什么要求局部变量添加final关键字。
还是以我们最开始的那个start函数为例。我们来看一下该函数的可能的执行过程:
如果beep变量不被标注为final,那么就意味着可以随时修改beep的值。
假设在创建了TimePrinter对象后修改了beep的值,那么这时的内部类所看到的beep的值还是之前通过构造函数传递进去的旧值,这样就导致内部类和外部函数对beep值“认识”的不一致。
所以final关键字的目的就是为了保证内部类和外部函数对变量“认识”的一致性。