对嵌套类的讨论

摘要:与字段和方法类似,Java允许类是其它类的成员。在这里,我们将嵌套类分为4种--嵌套顶级类(nested top-level classes),成员内部类(instance inner classes),本地内部类(local inner classes)和匿名内部类(anonymous inner classes)。
  在教授Java时,我经常发现学生尝试在方法中声明其它的方法。不过,与Pascal语言不同--Pascal允许嵌套声明过程procedures(与方法类似),而Java是不允许这样做的。对于以下的代码,尝试在outerMethod()中声明innerMethod()方法,编译时将会通不过。
  void outerMethod ()
  {
  void innerMethod ()
  {
  }
  }
  不过,由Java语言规范1.1开始,Java就支持类嵌套;Java编译器允许一个类出现在另一个类中。以下的代码段就展示了如何在outerClass类中嵌套innerClass类。
  class outerClass
  {
  class innerClass
  {
  }
  }
  为什么Java支持类嵌套,还有Java支持哪些类嵌套?以下的部分将会回答这些问题,当你读完这篇文章,你就会学习到类嵌套的相关知识,并且运用它来写出强大的Java程序。首先我们先来看一下为什么Java支持类嵌套。
  注意:在发布JDK1.1后,Sun发布了内部类规范的文档。该文档谈到嵌套顶级类和内部类的运用。在读完这篇文章后,我强烈建议你浏览这些文档。 bitsCN.com中国网管联盟
  为什么Java支持类嵌套?
  Java并非一定要支持类嵌套。实际上,如果你看过内部类规范文档,你将会发现类嵌套的应用在哪里。不过,Java支持类嵌套至少有两个好处:
  .令源代码更加清晰
  .减少命名的冲突
  通过类嵌套可以令源代码变得更为清楚,因为你声明了一个包含有一些对象的类,它必须可以管理和允许类中的方法来直接访问对象的字段,以及调用对象的方法--即使是外部类中的私有字段和方法。要理解这个好处,可用以下的一个例子说明,在这个程序中要循环遍历一个Employee对象中的Job对象:
  Listing 1. JobIterator1.java
  // JobIterator1.java
  class Job
  {
  private String jobTitle;
  Job (String jobTitle)
  {
  this.jobTitle = jobTitle;
  }
  public String toString ()
  {
  return jobTitle;
  }
  }
  class Employee
  {
  private String name;
  private Job [] jobs;
  private int jobIndex = 0;
  Employee (String name, Job [] jobs)
  {
  this.name = name;
  this.jobs = jobs;
  }
  String getName () 需要什么来搜一搜吧so.bitsCN.com
  {
  return name;
  }
  boolean hasMoreJobs ()
  {
  return jobIndex < jobs.length;
  }
  Job nextJob ()
  {
  return !hasMoreJobs () ? null : jobs [jobIndex++];
  }
  }
  class JobIterator1
  {
  public static void main (String [] args)
  {
  Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
  Employee e = new Employee ("John Doe", jobs);
  System.out.println (e.getName () + " works the following jobs:/n");
  while (e.hasMoreJobs ())
  System.out.println (e.nextJob ());
  }
  }
  运行后, JobIterator1产生如下的输出:
  John Doe works the following jobs:
  
     Janitor
     Delivery Person
  JobIterator1包含有Job, Employee和JobIterator1类。Job封装了一个job的title,而Employee封装了一个员工的名字以及该员工所做工作的一个数组。JobIterator1包含有一个main()的方法用来创建Job和Employee对象,并且打印出该员工的名字和工作。
  Employee类中包含有方法hasMoreJobs()和nextJob()。这两个方法构成了一个循环。当一个Employee对象初始化时,在私有jobs数组中的一个内部索引被设置为0。如果该索引的值比jobs数组的长度少,hasMoreJobs()方法返回一个布尔值true。nextJob()使用该索引值由数组中返回一个Job对象--并且增加该索引的值,以便下一次调用nextJob()时返回下一个job对象的一个引用。 play.bitsCN.com累了吗玩一下吧
  JobIterator1类在设计上有一些问题。首先,在循环结束后你不能重新启动一个循环。当然,你可以很容易地解决这个问题,只要Employee类加入一个reset()方法就可以了,该方法将jobIndex设置为0。第二个问题更加严重,你不能为一个Employee创建多个循环。这是由于hasMoreJobs()和nextJob()方法已经写死在Employee中了。要解决这两个问题,开发者可以声明一个循环类,它的对象可以循环jobs数组。在循环结束后,程序可以通过创建一个新的循环对象来启动一个新的循环。同样,通过创建多个循环对象,一个程序可以在同一个Employee对象的jobs数组上执行多个循环。列表2的程序为我们展示了一个名字为JobIterator的循环类:
  Listing 2. JobIterator2.java
  // JobIterator2.java
  class Job
  {
  private String jobTitle;
  
  Job (String jobTitle)
  {
  this.jobTitle = jobTitle;
  }
  public String toString ()
  {
  return jobTitle;
  }
  }
  class Employee
  {
  private String name;
  private Job [] jobs;
  Employee (String name, Job [] jobs)
  {
  this.name = name; bitsCN全力打造网管学习平台
  this.jobs = jobs;
  }
  String getName ()
  {
  return name;
  }
  JobIterator getJobIterator ()
  {
  return new JobIterator (jobs);
  }
  }
  class JobIterator
  {
  private Job [] jobs;
  private int jobIndex = 0;
  JobIterator (Job [] jobs)
  {
  this.jobs = jobs;
  }
  boolean hasMoreJobs ()
  {
  return jobIndex < jobs.length;
  }
  Job nextJob ()
  {
  return !hasMoreJobs () ? null : jobs [jobIndex++];
  }
  }
  class JobIterator2
  {
  public static void main (String [] args)
  {
  Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
  Employee e = new Employee ("John Doe", jobs);
  System.out.println (e.getName () + " works the following jobs:/n");
  JobIterator ji = e.getJobIterator ();
  while (ji.hasMoreJobs ())
  System.out.println (ji.nextJob ());
  }
  }
  JobIterator2的输出和JobIterator1一样,但有所不同的是,JobIterator2将循环的代码由Employee转移到JobIterator。还有,Employee声明了一个getJobIterator()的方法,可返回一个新的JobIterator对象引用。要注意到JobIterator和 Employee是紧耦合的类:JobIterator的构造器需要一个Employee私有jobs数组的引用。我们要记下这个耦合关系,因为它为理解类嵌套内部的工作提供了一个线索。
so.bitsCN.com网管资料库任你搜


  虽然JobIterator2很方便地解决了JobIterator1的问题,但这个新的程序又引入了一个新问题:由于新加入了一个和Employee类处在同一级的JobIterator类,这样将来就不能在同一级的源文件中加入一个普遍的JobIterator接口。因为在源文件的同一级中,你不同拥有同样名字的两个类/接口。虽然在我们的例子中这不是一个严重的问题,但是在重要的程序中,有些情况下必须在同一个源文件中使用相同名字的类/接口。要令那些名字共存,你必须认识到一些类完全依赖其它的类。你应该在其依赖的类中声明这些类。列表3展示了如何在一个Employee类中声明一个JobIterator类--JobIterator依赖Employee类。
  Listing 3. JobIterator3.java
  
  // JobIterator3.java
  
  class Job
  {
  private String jobTitle;
  
  Job (String jobTitle)
  {
  this.jobTitle = jobTitle;
  }
  
  public String toString ()
  {
  return jobTitle;
  }
  }
  
  class Employee
  {
  private String name;
  private Job [] jobs;
  
  Employee (String name, Job [] jobs)
  {
  this.name = name; 需要什么来搜一搜吧so.bitsCN.com
  this.jobs = jobs;
  }
  
  String getName ()
  {
  return name;
  }
  
  JobIterator getJobIterator ()
  {
  return new JobIterator ();
  }
  
  class JobIterator
  {
  private int jobIndex = 0;
  
  public boolean hasMoreJobs ()
  {
  return jobIndex < jobs.length;
  }
  
  public Object nextJob ()
  {
  return !hasMoreJobs () ? null : jobs [jobIndex++];
  }
  }
  }
  class JobIterator3
  {
  public static void main (String [] args)
  {
  Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
  Employee e = new Employee ("John Doe", jobs);
  System.out.println (e.getName () + " works the following jobs:/n");
  Employee.JobIterator eji = e.getJobIterator ();
  while (eji.hasMoreJobs ())
  System.out.println (eji.nextJob ());
  }
  }
  JobIterator3的输出和JobIterator1、JobIterator2的一样,它使用了类嵌套:Employee 

Java支持哪些嵌套类?
  Java将嵌套类分成两个主要的分类:嵌套顶级类(nested top-level classes)和内部类(inner classes)。Java还进一步将内部类分为实例内部类(instance inner class) ,本地内部类(local inner class)和匿名内部类(and anonymous inner class)。要掌握嵌套类,你需要懂得每个分类。以下我们就来讨论以下这些分类,首先我们来看一下嵌套顶级类。
  嵌套顶级类
  当你在其它类的外面声明一个类时,Java就认为该类是一个顶级类。如果你在一个顶级类中声明一个类,并且在该嵌套类的声明前加上static的修饰符,你就得到了一个嵌套顶级类。以下的代码段声明了一个顶级类和一个嵌套顶级类:
  class TopLevelClass
  {
  static class NestedTopLevelClass
  {
  }
  }
  就象静态的字段和静态的方法一样(也可称为类字段和类方法),一个嵌套的顶级类也是与对象无关的。考虑以下的代码段:
  class TopLevelClass
  {
  static int staticField;
  int instanceField;
  
  static class NestedTopLevelClass
  {
  static
  {
  System.out.println ("Can access staticField " + staticField); 需要什么来搜一搜吧so.bitsCN.com
  // System.out.println ("Cannot access instanceField " + instanceField);
  }
  
  {
  System.out.println ("Can access staticField " + staticField);
  // System.out.println ("Cannot access instanceField " + instanceField);
  }
  }
  }
  在上面NestedTopLevelClass类的类初始化和对象初始化代码块中,你可以访问TopLevelClass的staticField变量。不过你在这两个代码块中都不能访问instanceField变量。因为NestedTopLevelClass和任何的TopLevelClass对象都是无关的,所以NestedTopLevelClass 不能访问TopLevelClass的instanceField变量。
  警告:一个嵌套顶级类不能访问任何外部类的实例成员(包括字段和方法)。
  虽然NestedTopLevelClass不能访问TopLevelClass的实例字段,但是static的修饰符并不会妨碍NestedTopLevelClass声明自己的实例字段,以及创建NestedTopLevelClass对象。列表4的代码就是一个很好的例子:
  Listing 4. NestedTopLevelClassDemo.java
  // NestedTopLevelClassDemo.java
  class TopLevelClass
  {
  static class NestedTopLevelClass
  {
  int myInstanceField; bitsCN全力打造网管学习平台
  NestedTopLevelClass (int i)
  {
  myInstanceField = i;
  }
  }
  }
  class NestedTopLevelClassDemo
  {
  public static void main (String [] args)
  {
  TopLevelClass.NestedTopLevelClass ntlc;
  ntlc = new TopLevelClass.NestedTopLevelClass (5);
  System.out.println (ntlc.myInstanceField);
  }
  }
  在运行后,NestedTopLevelClassDemo产生以下的输出:
    5
  NestedTopLevelClassDemo的main()方法创建了一个NestedTopLevelClass变量--ntlc。声明该变量的语法和列表3的一样(Employee.JobIterator eji = e.getJobIterator ())。 通常,当你需要一个嵌套类的变量时,要在该嵌套类名的前面加入所有外部类的名字,并且用“.”将它们分开。用new关键字声明该嵌套类的时候也要这样做。
  这时,你可能想知道你是否可以在一个嵌套顶级类中声明一个顶级嵌套类。还有,如果两个不同的外部类声明了同样的字段变量名,但该变量的类型或者初始化值是不同的?要解答这些问题,可看列表5:
  Listing 5. NestingAndShadowingDemo.java
  // NestingAndShadowingDemo.java
  class TopLevelClass

bitsCN全力打造网管学习平台


  {
  private static int a = 1;
  private static int b = 3;
  static class NestedTopLevelClass
  {
  private static int a = 2;
  static class NestedNestedTopLevelClass
  {
  void printFields ()
  {
  System.out.println ("a = " + a);
  System.out.println ("b = " + b);
  }
  }
  }
  }
  class NestingAndShadowingDemo
  {
  public static void main (String [] args)
  {
  TopLevelClass.NestedTopLevelClass.NestedNestedTopLevelClass nntlc;
  nntlc = new TopLevelClass.NestedTopLevelClass.
  NestedNestedTopLevelClass ();
  nntlc.printFields ();
  }
  }
  在运行时,NestingAndShadowingDemo产生以下的输出:
     a = 2
     b = 3
  NestingAndShadowingDemo可以编译运行证明你可以在顶级嵌套类中嵌套顶级嵌套类。结果也显示了NestedTopLevelClass的一个字段屏蔽了TopLevelClass的字段。结果是打印出NestedTopLevelClass的字段内容。
  使用嵌套顶级类,你不可以访问外部类的实例字段或者调用该类的实例方法。要访问实例成员,Java支持内部类。内部类和嵌套顶级类相似,不同的地方是你不会在内部内的声明前加上static关键字。我们以下就讨论一下内部类,先从实例内部类开始。

bitsCN全力打造网管学习平台


  提示:你可以在顶级嵌套类前加上private,protected或者public关键字,以指示该类在外部类以外的访问级别。
  实例内部类(Instance inner classes)
  假设你在声明嵌套类时,没有在前面加上static关键字。这样你得到的不是一个嵌套顶级类,你得到的是一个实例内部类。嵌套顶级类只可以访问外部内的static成员,而实例内部类可以同时访问static和实例成员。列表3的代码中有实例内部内的例子。在Employee类中,你可以看到实例内部类JobIterator。仔细观察这两个类,你可以看到JobIterator可以访问Employee的private jobs实例字段。
  提示:你可以在实例内部类前加上private,protected或者public关键字,以指示该类在外部类以外的访问级别。
  本地内部类(Local inner classes)
  除了以上的类嵌套外,Java还允许你在任意的代码段中放入一个类,该类的代码放在一对大括号中({ })。这意味着类可以出现在一个方法中,甚至是在if语句的一对括号中。这样的类就称为本地内部类。
  相对于实例内部类来说,本地内部类与有一个好处。它除了可以访问外部类的实例和类字段外(称为实例和类方法),还可以访问本地变量和方法的参数。列表6就是一个本地内部类:
  Listing 6. LocalInnerClassDemo.java bitsCN全力打造网管学习平台
  // LocalInnerClassDemo.java
  import java.util.*;
  class ComputerLanguage
  {
  private String name;
  ComputerLanguage (String name)
  {
  this.name = name;
  }
  public String toString ()
  {
  return name;
  }
  }
  class LocalInnerClassDemo
  {
  public static void main (String [] args)
  {
  ComputerLanguage [] cl =
  {
  new ComputerLanguage ("Ada"),
  new ComputerLanguage ("Algol"),
  new ComputerLanguage ("APL"),
  new ComputerLanguage ("Assembly - IBM 360"),
  new ComputerLanguage ("Assembly - Intel"),
  new ComputerLanguage ("Assembly - Mostek"),
  new ComputerLanguage ("Assembly - Motorola"),
  new ComputerLanguage ("Assembly - VAX"),
  new ComputerLanguage ("Assembly - Zilog"),
  new ComputerLanguage ("BASIC"),
  new ComputerLanguage ("C"),
  new ComputerLanguage ("C++"),
  new ComputerLanguage ("Cobol"),
  new ComputerLanguage ("Forth"), bitsCN.com中国网管联盟
  new ComputerLanguage ("Fortran"),
  new ComputerLanguage ("Java"),
  new ComputerLanguage ("LISP"),
  new ComputerLanguage ("Logo"),
  new ComputerLanguage ("Modula 2"),
  new ComputerLanguage ("Pascal"),
  new ComputerLanguage ("Perl"),
  new ComputerLanguage ("Prolog"),
  new ComputerLanguage ("Snobol")
  };
  Enumeration e = enumerator ((Object []) cl);
  while (e.hasMoreElements ())
  System.out.println (e.nextElement ());
  }
  static Enumeration enumerator (final Object [] array)
  {
  class LocalInnerClass implements Enumeration
  {
  private int index = 0;
  public boolean hasMoreElements ()
  {
  return index < array.length;
  }
  public Object nextElement ()
  {
  return array [index++].toString ();
  }
  }
  return new LocalInnerClass ();
  }
  }
  在运行后, LocalInnerClassDemo产生以下的输出:
     Ada
     Algol
     APL

blog.bitsCN.com网管博客等你来搏


     Assembly - IBM 360
     Assembly - Intel
     Assembly - Mostek
     Assembly - Motorola
     Assembly - VAX
     Assembly - Zilog
     BASIC
     C
     C++
     Cobol
     Forth
     Fortran
     Java
     LISP
     Logo
     Modula 2
     Pascal
     Perl
     Prolog
     Snobol
  LocalInnerClassDemo展示了在LocalInnerClassDemo的enumerator()类方法中声

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值