内容


第 18 单元:嵌套类

定义紧密耦合的类

Comments

开始之前

本单元是 “Java 编程入门” 学习路径的一部分。尽管各单元中讨论的概念具有独立性,但实践组件是在您学习各单元的过程中逐步建立起来的,推荐您在继续学习之前复习 前提条件、设置和单元细节

单元目标

  • 了解如何定义嵌套类和何时适合使用它们
  • 了解使用嵌套类的副作用
  • 了解 new 运算符在嵌套类中的特殊用法
  • 了解如何和何时使用静态内部类和匿名内部类

在何处使用嵌套类

顾名思义,嵌套类(或内部类) 是在一个类中定义的另一个类。

public class EnclosingClass {
  . . .
  public class NestedClass {
  . . .

  }
}

像成员变量和方法一样,Java 类也可在任何范围内定义,包括 publicprivateprotected。在想要以面向对象的方式执行类中的内部处理工作时,嵌套类可能很有用,但此功能仅限于需要它的类。

通常,在需要一个与定义它的类紧密耦合的类时,应该使用嵌套类。嵌套类能够访问包含它的类中的私有数据,但此结构会带来负面影响,这些影响在开始使用嵌套(或内部)类时并不明显。

嵌套类中的范围

因为嵌套类具有范围,所以它受范围规则约束。例如,可通过该类的实例(对象)访问成员变量。嵌套类也是如此。

假设一个 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 中的示例为基础构建的,添加了一个处理不属于 StockOptionEligibleEmployee 类型的默认方法。该清单首先提供了 HumanResourcesApplication 中的一个处理库存选项的方法,然后提供了一个驱动该方法的 JUnit 测试:

清单 2. 处理不属于 StockOptionEligibleEmployee 类型
// 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 开发人员,随着您的不断进步,我相信您也会这么做。

测试您的理解情况

  1. 什么是嵌套类?
    1. 一个向应用程序中的其他类提供某种工具的类
    2. 一个在另一个类中定义的类
    3. 将一个或多个接口引用传入其构造方法的类
    4. 上述选项都不是
  2. 何时可以使用嵌套类?
    1. 当需要定义一个与另一个类(在功能上)紧密耦合的类时
    2. 当希望编写一个使用来自 JDK 的大量接口的类时
    3. 当一个类需要访问另一个类的私有数据,而您已超出了应用程序允许的最大类数量时
    4. 对于一个定义了超过 20 个方法且应重构的类
  3. 您能否找到以下代码中的错误?选择最佳答案,解释您的选择,并提供正确的代码。
    package com.makotojava.intro.quiz;
    
    import java.util.logging.Logger;
    
    public class Outer {
       
       private static final Logger log = Logger.getLogger(Outer.class.getName());
       
       public void setInner(Inner inner) {
          this.inner = inner;
       }
    
       private Inner inner;
       
       public Inner getInner() {
    
          return inner;
       }
    
       private class Inner {
       }
       
       public static void main(String[] args) {
          Outer outer = new Outer();
          Inner inner = new Outer.Inner();
          outer.setInner(inner);
          log.info("Outer/Inner: " + outer.hashCode() + "/" + outer.getInner().hashCode());
       }
    
    }
    1. 类名 Outer 不是合法的 Java 类名。
    2. 类名 Inner 难以理解。
    3. Outer 中定义的 main 方法的方法签名是错误的。
    4. main() 中的 log.info() 调用了太多参数。
    5. Inner 的主体是空的。
    6. 上述选项都不是。
  4. 参阅问题 3 中的代码清单。假设您在包含 Outer 的包中想要一个名为 AnotherOuter 且可以实例化 Outer.Inner 的新类。您需要对 Outer.Inner 的声明执行哪些更改?
    1. 没必要执行更改;Inner 可按原样实例化。
    2. 没有内部类可包含它的类外部实例化。
    3. Inner 的可视性更改为 protected 或包私有。
    4. static 修饰符添加到 Inner 类声明中。
    5. 上述选项都不是。
  5. 给定以下框架,充实 main() 方法来执行以下操作:
    1. 实例化 Hello
    2. 调用 Hellotalk() 方法,向它传递一个实现为匿名内部类的 HelloCallback 实例
    3. 修改 sayHello() 的匿名内部类实现,以便使用 Hello 上定义的 Logger 来实现输出:
      • “This implementation says:”
      • whatToSay 字符串
    import java.util.logging.Logger;
    
    public class Hello {
       
       private static final Logger log = Logger.getLogger(Hello.class.getName());
       
       interface HelloCallback {
          void sayHello(String whatToSay);
       }
       
       public void talk(HelloCallback helloCallback) {
          helloCallback.sayHello("Hello, world (how original :/)...");
       }
    
       public static void main(String[] args) {
            // YOUR ANSWER GOES HERE
       }
    
    }
  6. 能否编写一个可由您应用程序中的任何类(无论它位于哪个包中)实例化的内部类,而无需外部类的封闭实例?解释您的答案。
  7. 嵌套类与内部类有何区别?
    1. 内部类是仅使用私有访问级别定义的类,而嵌套类被声明为 static。
    2. 两个术语同义,可以交替使用。
    3. 嵌套类必须位于封闭类中,并在封闭类的任何变量之前声明。
    4. 嵌套类在 main() 中定义,而内部类可在任何地方定义。
    5. 不存在所谓的嵌套类。
    6. 上述选项都不是。
  8. 以下哪句有关内部类的描述是正确的?
    1. 内部类可访问包含它的类的任何私有数据变量,除非将它声明为 static
    2. 内部类必须声明为 public,才能被封闭类以外的其他任何类实例化。
    3. 除非在特殊情况下,否则不允许使用静态内部类。
    4. 内部类对其封闭类完全不可见。
    5. 上述选项都不是。

核对您的答案

进一步探索

Java 教程:嵌套类

Java - 内部类

Java 内部类和静态嵌套类

在 Java 中何时为帮助器类使用内部类

内部类有何用途

上一单元:接口下一单元:正则表达式


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=1039303
ArticleTitle=第 18 单元:嵌套类
publish-date=11012016