目录:
- 1. 内部类
- 1.1 内部类访问外围数据的机制
- 1.2 内部类对象的定义和实例化
- 1.2.1 在其它类或静态方法中
- 1.2.2 在外围类的一般方法中
- 1.3 内部类中的静态域
- 1.4 内部类之 this 关键字
- 2.局部内部类
- 3. 匿名内部类
- 4. 静态内部类
1. 内部类
内部类是指定义在另一个类中的类,它的主要优势有:
- 内部类的方法可以访问该类定义所在作用域中的数据(包括私有的数据)。
- 内部类可以对同一个包中的其它类隐藏起来。
- 使用匿名内部类可以便捷的定义一个回调函数。
public class OuterClass {
//...
private class InnerClass {
// an inner class
}
}
在上述代码中,InnerClass 就是一个内部类,而且它前面的修饰词居然是 private 。在Java中,只有内部类可以是私有类,而常规类只可以具有包可见性(不加修饰词),或公有可见性(public)。
1.1 内部类访问外围数据的机制
为验证内部类的机制,可以尝试在上述例子中在加上几行代码(虽然此处新增代码实际意义并不是很大,但可以帮助我们理解内部类):
public class OuterClass {
private String text;
public OuterClass(String text) {
this.text = text;
}
private class InnerClass {
// 访问该类定义所在作用域中的私有数据
public void showText() {
System.out.println(text);
}
}
public static void main(String[] args) {
OuterClass out = new OuterClass("Hello InnerClass");
OuterClass.InnerClass inner = out.new InnerClass();
inner.showText();
}
}
首先,在内部类的 showText 方法中使用到了变量text,我们可以清楚的看到内部类 InnerClass 中并没有定义变量text,而在内部类的方法中也的确用到了此变量。
事实上,在调用 showText 方法时,它引用了创建 InnerClass 的 OuterClass 对象 out 的域,即此处的 text 其实引用的是 out 对象中的text。
内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。那么,这种机制是如何做到的呢?
内部类的对象总有一个隐式引用,它指向了创建它的外围类对象,这个引用在内部类的定义中是不可见的。也就是说,编译器修改了所有的内部类的构造器,添加了一个外围类引用的参数。
为了更好的说明其机制,给出了下面这段代码(注意,此代码知识为了模仿编辑器修改内部类构造器的操作,实际并不需要我们写出,且Java也没有outer关键字,这里只是用其指代外围类对象):
private OuterClass outer;
public InnerClass(OuterClass out) {
outer = out;
}
public void showText() {
System.out.println(outer.text);
}
内部类是一种编译器现象,与虚拟机无关。编译器将会把内部类翻译成用$ (美元符号)分隔外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。
编译器在编译时会对内部类做类似上述代码的操作,当然,具体实现可能不是这样的,这里也不做深究。在使用过程中,大部分情况下只需要知道内部类可以访问该类定义所在作用域中的数据即可。
1.2 内部类对象的定义和实例化
1.2.1 在其它类或静态方法中
对于内部类变量的定义,如果是在其它类中,就需要通过下列方法进行定义:
外部类.内部类 变量名
而对于内部类对象的实例化,需要这么做:
外部类对象.new 内部类
具体例子在前面的代码中已经给出过,因为前面代码中内部类的定义是在外围类的 main 方法中,而 main 方法是个静态方法,外围类的静态方法中不可以直接实例化非静态内部类,所以需要像在其它类中一样对内部类进行实例化:
OuterClass out = new OuterClass("Hello InnerClass");
// 内部类的定义与实例化
OuterClass.InnerClass inner = out.new InnerClass();
/* 注意:在外部类的静态方法中,定义内部类变量可以直接通过类名定义(只有变量的定义是可以这样的,实例化并不能直接new InnerClass()):
* 如 InnerClass inner = out.new InnerClass();
*/
当然,在其它类中,只有对其可见的内部类才能通过此方法进行定义和实例化,前例中的 InnerClass 因为是私有的,所以其实并不能在其它类中进行定义和实例化。
1.2.2 在外围类的一般方法中
在外围类的一般方法中,可以直接对其进行定义与实例化,如:
public class OuterClass {
private String text;
public OuterClass(String text) {
this.text = text;
}
private class InnerClass {
public void showText() {
System.out.println(text);
}
}
public void showText() {
// 直接定义并实例化内部类
InnerClass inner = new InnerClass();
inner.showText();
}
}
1.3 内部类中的静态域
内部类中声明的所有静态域都必须是 final,例如:
public class OuterClass {
//...
private class InnerClass {
public static int a = 0; // error
public static final int a = 0; // ok
}
}
原因很简单,我们希望一个静态域只有一个实例,不过对于每个外部对象,会分别有一个单独的内部类实例。如果这个域不是 final ,它可能就不是唯一的。
同时,Java的内部类不能有 static 方法,除非它是一个静态内部类。
1.4 内部类之 this 关键字
- 如果在一个内部类中直接使用 this,就表示对此内部类对象的引用;
public class OuterClass {
//...
private class InnerClass {
this // 代表 InnerClass 所对应的对象
}
}
- 如果想要引用外围类对象的话,则可以通过“外围类名.this进行引用”;
public class OuterClass {
//...
private class InnerClass {
OuterClass.this // 代表 OuterClass 所对应的对象
}
}
2.局部内部类
如果在某个方法的内部定义了一个类,那么这个类就是局部内部类。
局部类不能用 public 或 private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。这样设计给局部类带来了一个优势,即对外部世界可以完全地隐藏起来。当一个类只在某个方法中使用时,就可以考虑使用局部内部类。例如:
public void showText() {
// 局部内部类定义
class LocalInnerClass {
public void showText() {
System.out.println(text);
}
}
InnerClass inner = new InnerClass();
inner.showText();
}
与其他内部类相比较,局部类还有一个优点。它们不仅能够访问包含它们的外围类,还可以访问局部变量。不过,那些局部变量必须事实上是final的,即它们一旦赋值就绝不会改变。例如:
public void verification() {
int a = 0;
class LocalInnerClass {
int b = a++; // a++ error
// 错误提示:Local variable a defined in an enclosing scope must be final or effectively final
int b = a; // ok
}
}
3. 匿名内部类
将局部内部类的使用再深人一步,假如只需要创建这个类的一个对象,就不必对其命名了,即使用匿名内部类。
public class OuterClass {
public void method() {
// 匿名内部类的示例
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
};
}
}
以上代码是利用匿名内部类实例化一个实现了 ActionListener 接口的类的例子,其功能与下面这段代码是等价的:
public class OuterClass {
public void method() {
class LocalInnerClass implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
}
ActionListener listener = new LocalInnerClass();
}
}
匿名内部类的语法的确有点难以理解,但它的结构简洁,且更贴近实际:
参考上面的代码,我们的需求是要让一个 ActionListener 接口变量引用一个实现了此接口的对象。
既然如此,我们就“实例化一个接口对象”,并在实例化的时候就具体实现此接口中的方法。
其实,对于这种情况,我们还有一种更简介的实现方式——lambda表达式,不过它只适用于函数式接口(只有一个抽象方法的接口)。
4. 静态内部类
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为 static(即静态内部类), 以便取消产生的引用。也就是说,静态内部类不能引用外围类对象。
当然,只有内部类可以声明为 static,静态内部类除了不能引用外围类对象外,其它内部类的功能它都拥有。除此之外,它还可以拥有静态的域和方法。
因为接口是不能实例化的,内部接口只有当它是静态的才有意义。因此声明在接口内部的接口,默认为 static 和 public。