本文的内容参考了:https://blog.csdn.net/Hacker_ZhiDian/article/details/82193100
并在此基础上给出总结。
AOSP的代码中,大量使用了内部类。要准确阅读代码,有必要深刻理解java内部类的使用。
public class InnerClassTest {
public int outField1 = 1;
protected int outField2 = 2;
int outField3 = 3;
private int outField4 = 4;
public InnerClassTest() {
// 在外部类对象内部,直接通过 new InnerClass(); 创建内部类对象
InnerClassA innerObj = new InnerClassA();
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);
System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
}
public class InnerClassA {
public int field1 = 5;
protected int field2 = 6;
int field3 = 7;
private int field4 = 8;
// static int field5 = 5; // 编译错误!普通内部类中不能定义 static 属性
public InnerClassA() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
System.out.println("其外部类的 outField1 字段的值为: " + InnerClassTest.this.outField1);
System.out.println("其外部类的 outField2 字段的值为: " + InnerClassTest.this.outField2);
System.out.println("其外部类的 outField3 字段的值为: " + InnerClassTest.this.outField3);
System.out.println("其外部类的 outField4 字段的值为: " + InnerClassTest.this.outField4);
}
}
public static void main(String[] args) {
InnerClassTest outerObj = new InnerClassTest();
// 不在外部类内部,使用:外部类对象. new 内部类构造器(); 的方式创建内部类对象
InnerClassA innerObj = outerObj.new InnerClassA();
}
}
我们注意到,内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段。
注意:普通内部类中不能定义static变量。
一个类的静态成员独立于这个类的任何一个对象存在,只要在具有访问权限的地方,我们就可以通过 类名.静态成员名
的形式来访问这个静态成员,同样的,静态内部类也是作为一个外部类的静态成员而存在,创建一个类的静态内部类对象不需要依赖其外部类对象。
public class InnerClassTest {
public int field1 = 1;
public InnerClassTest() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
// 创建静态内部类对象
StaticClass innerObj = new StaticClass();
System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);
System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
}
static class StaticClass {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
// 静态内部类中可以定义 static 属性
static int field5 = 5;
public StaticClass() {
System.out.println("创建 " + StaticClass.class.getSimpleName() + " 对象");
System.out.println("内部类的 field1 字段的值为: " + field1); //
}
}
public static void main(String[] args) {
// 无需依赖外部类对象,直接创建内部类对象
InnerClassTest.StaticClass staticClassObj = new InnerClassTest.StaticClass();
InnerClassTest outerObj = new InnerClassTest();
}
}
静态内部类就像外部类的一个静态成员一样,创建其对象无需依赖外部类对象(访问一个类的静态成员也无需依赖这个类的对象,因为它是独立于所有类的对象的)。但是于此同时,静态内部类中也无法访问外部类的非静态成员,因为外部类的非静态成员是属于每一个外部类对象的,而本身静态内部类就是独立外部类对象存在的,所以静态内部类不能访问外部类的非静态成员,而外部类依然可以访问静态内部类对象的所有访问权限的成员,这一点和普通内部类无异。
匿名内部类有多种形式,其中最常见的一种形式莫过于在方法参数中新建一个接口对象 / 类对象,并且实现这个接口声明 / 类中原有的方法了:
public class InnerClassTest {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
public InnerClassTest() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
}
// 自定义接口
interface OnClickListener {
void onClick(Object obj);
}
private void anonymousClassTest() {
// 在这个过程中会新建一个匿名内部类对象,
// 这个匿名内部类实现了 OnClickListener 接口并重写 onClick 方法
OnClickListener clickListener = new OnClickListener() {
// 可以在内部类中定义属性,但是只能在当前内部类中使用,
// 无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名,
// 也就无法创建匿名内部类的对象
int field = 1;
@Override
public void onClick(Object obj) {
System.out.println("对象 " + obj + " 被点击");
System.out.println("内部类的 field1 字段的值为: " + field1);//此处应该是内部类自己的field
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
};
// new Object() 过程会新建一个匿名内部类,继承于 Object 类,
// 并重写了 toString() 方法
clickListener.onClick(new Object() {
@Override
public String toString() {
return "obj1";
}
});
}
public static void main(String[] args) {
InnerClassTest outObj = new InnerClassTest();
outObj.anonymousClassTest();
}
}
上面的代码中展示了常见的两种使用匿名内部类的情况:
1、直接 new 一个接口,并实现这个接口声明的方法,在这个过程其实会创建一个匿名内部类实现这个接口,并重写接口声明的方法,然后再创建一个这个匿名内部类的对象并赋值给前面的 OnClickListener 类型的引用;
2、new 一个已经存在的类 / 抽象类,并且选择性的实现这个类中的一个或者多个非 final 的方法,这个过程会创建一个匿名内部类对象继承对应的类 / 抽象类,并且重写对应的方法。
同样的,在匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,因为是匿名内部类,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。
局部内部类使用的比较少,其声明在一个方法体 / 一段代码块的内部,而且不在定义类的定义域之内便无法使用,其提供的功能使用匿名内部类都可以实现,而本身匿名内部类可以写得比它更简洁,因此局部内部类用的比较少。
public class InnerClassTest {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
public InnerClassTest() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
}
private void localInnerClassTest() {
// 局部内部类 A,只能在当前方法中使用
class A {
// static int field = 1; // 编译错误!局部内部类中不能定义 static 字段
public A() {
System.out.println("创建 " + A.class.getSimpleName() + " 对象");
System.out.println("其外部类的 field1 字段的值为: " + field1);
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
}
A a = new A();
if (true) {
// 局部内部类 B,只能在当前代码块中使用
class B {
public B() {
System.out.println("创建 " + B.class.getSimpleName() + " 对象");
System.out.println("其外部类的 field1 字段的值为: " + field1);
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
}
B b = new B();
}
// B b1 = new B(); // 编译错误!不在类 B 的定义域内,找不到类 B,
}
public static void main(String[] args) {
InnerClassTest outObj = new InnerClassTest();
outObj.localInnerClassTest();
}
}
在局部内部类里面可以访问外部类对象的所有访问权限的字段,而外部类却不能访问局部内部类中定义的字段,因为局部内部类的定义只在其特定的方法体 / 代码块中有效,一旦出了这个定义域,那么其定义就失效了,就像代码注释中描述的那样,即外部类不能获取局部内部类的对象,因而无法访问局部内部类的字段。
new一个内部类实例对象时 是否依赖一个外部类对象 |
是否可以(不通过外部类对象的引用)直接访问外部类对象的 所有访问权限的字段(含private字段) 即:“内”访问“外” |
内部类对象中是否持有 “外部类对象的引用” |
外部类对象是否可以通过“内部类对象的引用” 访问“内部来对象的所有访问权限的字段(含private字段)” 即:“外”访问“内” |
|
---|---|---|---|---|
普通内部类 | 是 | 是 | 是 | 是 |
静态内部类 | 否 | 否(不通过引用的话,只能访问static字段) | 否 | 是 |
匿名内部类 | 是 | 是 | 是 | 否(由于类是匿名的,无法获取类名,也就无法得到类的属性信息) |
局部内部类 | 是 | 是 | 是 | 否(局部内部类只在 “特定的方法体/代码块中” 才有效) |
关于表格中第二纵列数据的解释:
编译器在编译过程中会在外部类中生成 “ private 字段对应的 static 访问接口 ”,内部类对象根据 “ 自己持有的外部类对象的引用 ” 及 “ 新生成的 static 访问接口 ” 实现对 private 字段的访问。
内部类的嵌套,即为内部类中再定义内部类。
外部\内部 | 其中是否可以嵌套“普通内部类” | 其中是否可以嵌套“静态内部类” | 其中是否可以嵌套“匿名内部类” | 其中是否可以嵌套“局部内部类” |
---|---|---|---|---|
普通内部类 | 是 | 否 | 是 | 是 |
静态内部类 | 是 | 是 | 是 | 是 |
匿名内部类(其嵌套的内部类只能在其内部使用) | 是 | 否 | 是 | 是 |
局部内部类(其嵌套的内部类只能在“特定的方法体/代码块中” 使用) | 是 | 否 | 是 | 是 |
以上表格中:“非静态内部类” 中不允许嵌套 “静态内部类”。
普通内部类:在这里我们可以把它看成一个外部类的普通成员方法,在其内部可以定义普通内部类(嵌套的普通内部类),但是无法定义 static 修饰的内部类,就像你无法在成员方法中定义 static 类型的变量一样,当然也可以定义匿名内部类和局部内部类;
静态内部类:因为这个类独立于外部类对象而存在,我们完全可以将其拿出来,去掉修饰它的 static 关键字,他就是一个完整的类,因此在静态内部类内部可以定义普通内部类,也可以定义静态内部类,同时也可以定义 static 成员;
匿名内部类:和普通内部类一样,定义的普通内部类只能在这个匿名内部类中使用,定义的局部内部类只能在对应定义域内使用;
局部内部类:和匿名内部类一样,但是嵌套定义的内部类只能在对应定义域内使用。