共计 3093 个字符,预计需要花费 8 分钟才能阅读完成。
在 Java 程序中,通常情况下,我们把不同的类组织在不同的包下面,对于一个包下面的类来说,它们是在同一层次,没有父子关系:
java.lang
├── Math
├── Runnable
├── String
└── ...
还有一种类,它被定义在另一个类的内部,所以称为内部类(Nested Class)。Java 的内部类分为好几种,通常情况用得不多,但也需要了解它们是如何使用的。
Inner Class
如果一个类定义在另一个类的内部,这个类就是 Inner Class:
class Outer {class Inner {// 定义了一个 Inner Class
}
}
上述定义的 Outer
是一个普通类,而 Inner
是一个 Inner Class,它与普通类有个最大的不同,就是 Inner Class 的实例不能单独存在,必须依附于一个 Outer Class 的实例。示例代码如下:
// inner class
public class Main {public static void main(String[] args) {Outer outer = new Outer("Nested"); // 实例化一个 Outer
Outer.Inner inner = outer.new Inner(); // 实例化一个 Inner
inner.hello();}
}
class Outer {private String name;
Outer(String name) {this.name = name;
}
class Inner {void hello() {System.out.println("Hello," + Outer.this.name);
}
}
}
观察上述代码,要实例化一个 Inner
,我们必须首先创建一个Outer
的实例,然后,调用 Outer
实例的 new
来创建 Inner
实例:
Outer.Inner inner = outer.new Inner();
这是因为 Inner Class 除了有一个 this
指向它自己,还隐含地持有一个 Outer Class 实例,可以用 Outer.this
访问这个实例。所以,实例化一个 Inner Class 不能脱离 Outer 实例。
Inner Class 和普通 Class 相比,除了能引用 Outer 实例外,还有一个额外的“特权”,就是可以修改 Outer Class 的 private
字段,因为 Inner Class 的作用域在 Outer Class 内部,所以能访问 Outer Class 的 private
字段和方法。
观察 Java 编译器编译后的 .class
文件可以发现,Outer
类被编译为 Outer.class
,而Inner
类被编译为Outer$Inner.class
。
Anonymous Class
还有一种定义 Inner Class 的方法,它不需要在 Outer Class 中明确地定义这个 Class,而是在方法内部,通过匿名类(Anonymous Class)来定义。示例代码如下:
// Anonymous Class
public class Main {public static void main(String[] args) {Outer outer = new Outer("Nested");
outer.asyncHello();}
}
class Outer {private String name;
Outer(String name) {this.name = name;
}
void asyncHello() {Runnable r = new Runnable() {@Override
public void run() {System.out.println("Hello," + Outer.this.name);
}
};
new Thread(r).start();}
}
观察 asyncHello()
方法,我们在方法内部实例化了一个 Runnable
。Runnable
本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了 Runnable
接口的匿名类,并且通过 new
实例化该匿名类,然后转型为Runnable
。在定义匿名类的时候就必须实例化它,定义匿名类的写法如下:
Runnable r = new Runnable() {// 实现必要的抽象方法...
};
匿名类和 Inner Class 一样,可以访问 Outer Class 的 private
字段和方法。之所以我们要定义匿名类,是因为在这里我们通常不关心类名,比直接定义 Inner Class 可以少写很多代码。
观察 Java 编译器编译后的 .class
文件可以发现,Outer
类被编译为Outer.class
,而匿名类被编译为Outer$1.class
。如果有多个匿名类,Java 编译器会将每个匿名类依次命名为Outer$1
、Outer$2
、Outer$3
……
除了接口外,匿名类也完全可以继承自普通类。观察以下代码:
// Anonymous Class
import java.util.HashMap;
public class Main {public static void main(String[] args) {HashMap<String, String> map1 = new HashMap<>();
HashMap<String, String> map2 = new HashMap<>() {}; // 匿名类!
HashMap<String, String> map3 = new HashMap<>() {
{put("A", "1");
put("B", "2");
}
};
System.out.println(map3.get("A"));
}
}
map1
是一个普通的 HashMap
实例,但 map2
是一个匿名类实例,只是该匿名类继承自 HashMap
。map3
也是一个继承自 HashMap
的匿名类实例,并且添加了 static
代码块来初始化数据。观察编译输出可发现 Main$1.class
和Main$2.class
两个匿名类文件。
Static Nested Class
最后一种内部类和 Inner Class 类似,但是使用 static
修饰,称为静态内部类(Static Nested Class):
// Static Nested Class
public class Main {public static void main(String[] args) {Outer.StaticNested sn = new Outer.StaticNested();
sn.hello();}
}
class Outer {private static String NAME = "OUTER";
private String name;
Outer(String name) {this.name = name;
}
static class StaticNested {void hello() {System.out.println("Hello," + Outer.NAME);
}
}
}
用 static
修饰的内部类和 Inner Class 有很大的不同,它不再依附于 Outer
的实例,而是一个完全独立的类,因此无法引用 Outer.this
,但它可以访问Outer
的private
静态字段和静态方法。如果把 StaticNested
移到 Outer
之外,就失去了访问 private
的权限。
小结
Java 的内部类可分为 Inner Class、Anonymous Class 和 Static Nested Class 三种;
Inner Class 和 Anonymous Class 本质上是相同的,都必须依附于 Outer Class 的实例,即隐含地持有 Outer.this
实例,并拥有 Outer Class 的 private
访问权限;
Static Nested Class 是独立类,但拥有 Outer Class 的 private
访问权限。