嵌套类

嵌套类

目标

  • 了解如何定义嵌套类和何时适合使用它们
  • 了解使用嵌套类的副作用
  • 了解 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 开发人员,随着您的不断进步,我相信您也会这么做。

上一篇 下一篇