嵌套类
目标
- 了解如何定义嵌套类和何时适合使用它们
- 了解使用嵌套类的副作用
- 了解 new 运算符在嵌套类中的特殊用法
- 了解如何和何时使用静态内部类和匿名内部类
在何处使用嵌套类
顾名思义,嵌套类(或内部类) 是在一个类中定义的另一个类。
public class EnclosingClass {
. . .
public class NestedClass {
. . .
}
}
像成员变量和方法一样,Java 类也可在任何范围内定义,包括 public、private 或 protected。在想要以面向对象的方式执行类中的内部处理工作时,嵌套类可能很有用,但此功能仅限于需要它的类。
通常,在需要一个与定义它的类紧密耦合的类时,应该使用嵌套类。嵌套类能够访问包含它的类中的私有数据,但此结构会带来负面影响,这些影响在开始使用嵌套(或内部)类时并不明显。
嵌套类中的范围
因为嵌套类具有范围,所以它受范围规则约束。例如,可通过该类的实例(对象)访问成员变量。嵌套类也是如此。
假设一个 Manager 和一个名为 DirectReports 的嵌套类之间的关系为:后者是 Manager 下属的 Employee 的集合:
public class Manager extends Employee {
private DirectReports directReports;
public Manager() {
this.directReports = new DirectReports();
}
. . .
private class DirectReports {
. . .
}
}
就像每个 Manager 对象表示唯一的人一样,DirectReports 对象表示一位经理下属的真实的人(员工)的集合。DirectReports 在不同 Manager 中不同。在本例中,应该仅在包含 DirectReports 嵌套类的 Manager 实例中引用它,所以我将它声明为 private。
公共嵌套类
因为它是 private,所以只有 Manager 可以创建 DirectReports 实例。但是,假设您想为一个外部实体提供创建 DirectReports 实例的能力。在这种情况下,似乎可为 DirectReports 类提供 public 范围,然后任何外部代码都可以创建 DirectReports 实例,如清单 1 所示。
清单 1. 创建 DirectReports 实例:第一次尝试
public class Manager extends Employee {
public Manager() {
}
. . .
public class DirectReports {
. . .
}
}
//
public static void main(String[] args) {
Manager.DirectReports dr = new Manager.DirectReports();// This won't work!
}
清单 1 中的代码不起作用,您可能想知道为什么会这样。问题(和它的解决方案)取决于在 Manager 中定义 DirectReports 的方式,以及范围规则。
理解范围规则
如果您拥有 Manager 的一个成员变量,您会认为编译器会要求您拥有 Manager 对象的引用,然后才能引用它,对吧?DirectReports 也是如此,至少在 清单 1 中定义它时是这样。
要创建一个公共嵌套类的实例,需要使用 new 运算符的一个特殊版本。结合对一个外部类的封闭实例的引用,可使用 new 创建嵌套类的实例:
public class Manager extends Employee {
public Manager() {
}
. . .
public class DirectReports {
. . .
}
}
// Meanwhile, in another method somewhere...
public static void main(String[] args) {
Manager manager = new Manager();
Manager.DirectReports dr = manager.new DirectReports();
}
在第 12 行上可以注意到,该语法需要使用对封闭实例的引用,还要使用一个句点和 new 关键字,后跟想要创建的类。
静态内部类
有时,您希望创建一个(在概念上)与另一个类紧密耦合的类,但范围规则比较宽松,不需要使用对封闭实例的引用。这时静态 内部类就可以派上用场。一个常见的示例是实现一个 Comparator,用它来比较同一个类的两个实例,通常用于对类进行排序:
public class Manager extends Employee {
. . .
public static class ManagerComparator implements Comparator<Manager> {
. . .
}
}
// Meanwhile, in another method somewhere...
public static void main(String[] args) {
Manager.ManagerComparator mc = new Manager.ManagerComparator();
. . .
}
在本例中,不需要使用封闭实例。静态内部类的行为与它们的对应常规 Java 类类似,而且仅应在需要将一个类与它的定义紧密耦合时使用它们。显然,对于像 ManagerComparator 这样的实用程序类,没有必要创建外部类,而且它们可能让您的代码库变得杂乱。将这些类定义为静态内部类是一种解决办法。
匿名内部类
使用 Java 语言,可在任何地方实现抽象类和接口,如果必要,甚至可在一个方法内实现,而且无需为类提供名称。此功能实际上是一种编译器窍门,但有时拥有匿名内部类很方便。
清单 2 是以 第 17 单元:接口 中的清单 1 中的示例为基础构建的,添加了一个处理不属于 StockOptionEligible 的 Employee 类型的默认方法。该清单首先提供了 HumanResourcesApplication 中的一个处理库存选项的方法,然后提供了一个驱动该方法的 JUnit 测试:
清单 2. 处理不属于 StockOptionEligible 的 Employee 类型
// From HumanResourcesApplication.java
public void handleStockOptions(final Person person, StockOptionProcessingCallback callback) {
if (person instanceof StockOptionEligible) {
// Eligible Person, invoke the callback straight up
callback.process((StockOptionEligible)person);
} else if (person instanceof Employee) {
// Not eligible, but still an Employee. Let's cobble up a
/// anonymous inner class implementation for this
callback.process(new StockOptionEligible() {
@Override
public void awardStockOptions(int number, BigDecimal price) {
// This employee is not eligible
log.warning("It would be nice to award " + number + " of shares at $" +
price.setScale(2, RoundingMode.HALF_UP).toPlainString() +
", but unfortunately, Employee " + person.getName() +
" is not eligible for Stock Options!");
}
});
} else {
callback.process(new StockOptionEligible() {
@Override
public void awardStockOptions(int number, BigDecimal price) {
log.severe("Cannot consider awarding " + number + " of shares at $" +
price.setScale(2, RoundingMode.HALF_UP).toPlainString() +
", because " + person.getName() +
" does not even work here!");
}
});
}
}
// JUnit test to drive it (in HumanResourcesApplicationTest.java):
@Test
public void testHandleStockOptions() {
List<Person> people = HumanResourcesApplication.createPeople();
StockOptionProcessingCallback callback = new StockOptionProcessingCallback() {
@Override
public void process(StockOptionEligible stockOptionEligible) {
BigDecimal reallyCheapPrice = BigDecimal.valueOf(0.01);
int numberOfOptions = 10000;
stockOptionEligible.awardStockOptions(numberOfOptions, reallyCheapPrice);
}
};
for (Person person : people) {
classUnderTest.handleStockOptions(person, callback);
}
}
在清单 2 的示例中,我提供了两个使用匿名内部类的接口的实现。首先是 StockOptionEligible 的两个独立实现:一个用于 Employee,另一个用于 Person(以符合接口要求)。然后是一个 StockOptionProcessingCallback 实现,用于处理 Manager 实例的库存选项。
可能需要一定的时间才能掌握匿名内部类的概念,但它们非常方便。我一直在 Java 代码中使用它们。而且作为 Java 开发人员,随着您的不断进步,我相信您也会这么做。