我们在写Java程序的时候我们常常会用到内部类,它是定义在其它类(或者代码块)当中的一种类。下面代码就定义一个内部类InnerClass:
public class OuterClass {
class InnerClass {
// Blablabla
}
// Blablabla
}
一、内部类与外部类的关系:
当我第一次接触内部类的时候,我对它的印象是:只不过是在类里面定义的类,可能是一种辅助类,也有可能只是不想把类暴露出来而已。然而其实内部类和包含它的外部类之间有着密切的联系。
1. 内部类可以访问外部类全部元素。
即使是外部类的私有域(方法),内部类也有访问权限:
public class OuterClass {
class InnerClass {
public int getCount() { // 内部类可以访问外部类的私有域
return count;
}
}
private int count = 0;
public void setCount(int count) {
this.count = count;
}
public static void main (String ...args) {
OuterClass oc = new OuterClass();
InnerClass ic = oc.new InnerClass(); // 接下来就会讲到
oc.setCount(100);
System.out.println(ic.getCount()); // 结果是100
}
}
而且需要注意的是,即使是外部类的外部类(你可以理解为层层包裹,像洋葱一样……)内部类同样有访问所有方法和域的权利。
public class OuterClass {
class InnerClass {
class InnerInnerClass{
public int getCount() {
return count; // 同样可以访问count
}
}
}
private int count = 0;
public void setCount(int count) {
this.count = count;
}
public static void main (String ...args) {
OuterClass oc = new OuterClass();
InnerClass ic = oc.new InnerClass();
InnerClass.InnerInnerClass iic = ic.new InnerInnerClass();
oc.setCount(100);
System.out.println(iic.getCount());
}
}
2. .this 和 .new
内部类可以通过外部类.this来获取外部类实例的引用。通过外部类的实例.new来创建内部类的实例。
public class OuterClass {
class InnerClass {
public void setCount(int count) {
OuterClass.this.count = count; // 内部类为了区分传入的参数count和外部类的count,使用了外部类.this
}
}
private int count = 0;
public int getCount() {
return count;
}
public static void main (String ...args) {
OuterClass oc = new OuterClass();
InnerClass ic = oc.new InnerClass(); // 使用外部类的实例.new来创建内部类
ic.setCount(100);
System.out.println(oc.getCount()); // 结果是100
}
}
二. 接口与实现的分离
使用内部类的一个好处是可以使接口和实现分离。用户代码只需要关心接口即可,这样以后如果更改了实现也不会影响到用户代码:
public class OuterClass {
private class InnerTask implements Runnable{
@Override
public void run() {
System.out.println("Inner Class");
}
}
public InnerTask getTask() {
return new InnerTask();
}
}
public class Tester {
public static void main (String ...args) {
OuterClass oc = new OuterClass();
Runnable task = oc.getTask(); // 并没有意识到InnerTask类的存在
Thread thread = new Thread(task);
thread.start();
}
}
如上,定义了一个私有的内部类来实现Runnable,并且通过getTask()来发布。这样子用户代码只能够通过OuterClass所提供的getTask()方法来获取一个Runnable,他们甚至不需要知道InnerTask的存在。这样就可以很好地实现解耦。
三、内部类可以定义在方法中
内部类也可以定义在方法中,或者更加宽泛地说是代码块中……这样上面的代码可以改成:
public class OuterClass {
public Runnable getTask() {
class InnerTask implements Runnable{
@Override
public void run() {
System.out.println("Inner Class");
}
}
return new InnerTask();
}
public static void main (String ...args) {
OuterClass oc = new OuterClass();
Runnable task = oc.getTask();
Thread thread = new Thread(task);
thread.start();
}
}
需要注意的是,在代码块中时,内部类只能访问当前代码块中的final变量(当然对于外部类的所有域和方法,依旧有访问权限)。
四、反射与内部类
内部类最终编译出来的class文件名是:OuterClass$InnerClass,所以当想用Class.forName来获取内部类的类型的时候就需要写这个全称:
package com.allen.li.inner;
public class OuterClass {
class InnerTask implements Runnable{
@Override
public void run() {
System.out.println("Inner Class");
}
}
public static void main (String ...args) {
try {
Class<?> innerClass = Class.forName("com.allen.li.inner.InnerTask"); // 会抛出ClassNotFoundException
System.out.println(innerClass);
}catch(ClassNotFoundException e) {
e.printStackTrace();
}
try {
Class<?> innerClass = Class.forName("com.allen.li.inner.OuterClass$InnerTask");
System.out.println(innerClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
五、匿名内部类
匿名内部类也是一个很有用的技巧,比如说我们常常定义一个匿名的实现了Runnable或者继承Thread的类:
Thread thread1 = new Thread() {
@Override
public void run() {
// Blablabla
}
};
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// Blablabla
}
});
六、总结
使用内部类给我们提供了很多的便利。在很多设计中都使用到了内部类。举个例子,我们都知道一个Java类可以实现多个接口,却只能扩展一个类,然而接口不提供实现。当你想要继承一些类的实现的时候,内部类就提供了一种很好的方法:
class A {
public void doA() {
System.out.println("Using A's behavior");
}
}
class B {
public void doB() {
System.out.println("Using B's behavior");
}
}
public class OuterClass extends A {
class InnerClass extends B {
}
private InnerClass ic = new InnerClass();
public void doB() {
ic.doB();
}
public void doAnB() {
doA();
ic.doB();
}
public static void main(String ...args) {
OuterClass oc = new OuterClass();
oc.doA();
oc.doB();
System.out.println("\n==========================\n");
oc.doAnB();
}
}
可以看出来,其本质是使用了复合,但是看起来,OuterClass好像是同时继承了A和B