楼主: lastwinner

[转载] JAVA应用程序设计开发

[复制链接]
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
11#
 楼主| 发表于 2006-6-27 23:51 | 只看该作者
第四章 Java的类(class)、包(package)和接口(interface)
  在这一章中,我们将阐述Java有关面向对象的基本机制:类、包和接口,并通过实例来介绍面向对象的程序设计方法。
  在Java中,类(class)是用来代表对象的基本单元。对象(object)可以是现实世界中的任何一个实体,它具有若干区别于其它对象的属性和操作。而类则通过为对象定义属性和操作来概括一类实体。它封装了一组变量和方法,是生成实例对象时的模板。如一辆汽车可视为一个对象,它既具有型号、颜色、载重等特点,又有完成启动、行驶、刹车等功能。定义汽车类时需要将这些属性都包括进去,通常用数据变量代表型号、颜色、载重等属性特点,用成员方法来实现启动、行驶、刹车等操作功能。可以说类是对象的抽象,对象是类的实例化。
  接口(interface)可看成一个空的抽象的类,只声明了一组类的若干同名变量和方法,而不考虑方法的具体实现。Java的包(package)中包含一系列相关的类,同一个包中的类可直接互相使用,对包外的类则有一定的使用限制。Java的包近似于其它语言的函数库,可提供重用的方便。
  在下面各部分的详细介绍中,我们将先给出基本概念,然后结合具体实例阐明Java的类、接口、包以及封装、继承、重载等有关内容。

4.1 Java的类
  4.1.1 类的声明
  Java是一种很典型的面向对象的程序设计语言。在面向对象的语言中,世界被看成独立的对象集合,相互间通过消息来通信。因而这种语言以数据对象为中心,而不以处理对象的代码为中心。Java中的类将数据和有关的操作封装在一起,描述一组具有相同类型的对象,作为构筑程序的基本单位。
  类声明定义的格式为:
  [类修饰符] class类名 [extends 父类名][implements 接口名{,接口名}] {类体}
其中类修饰符用于指明类的性质,可缺省。接下来的关键字class指示定义的类的名称,类名最好是唯一的。“extends 父类名”通过指出所定义的类的父类名称来表明类间的继承关系,当缺省时意味着所定义类为Object类的子类。“implements 接口名”用来指出定义的类实现的接口名称。一个类可以同时实现多个接口。类体则包括一系列数据变量和成员方法的定义声明。下面是一些略去类体的类定义例子:
  public class WelcomeApp {//类体}
  public class Welcome extends java.applet.Applet{//类体}
  public Car extends Automobile implements Runable {//类体}
其中前两个类是我们在上一章的示例中定义的。第三个类是小汽车类Car,它的父类是交通工具类Automobile,它还实现了接口Runnable。
  类修饰符是用以指明类的性质的关键字。基本的类修饰符有三个:
  public,abstract和final
  ■public
  如果一个类被声明为public,那么与它不在同一个包中的类也可以通过引用它所在的包来使用这个类;否则这个类就只能被同一个包中的类使用。
  ■abstract
  如果一个类被声明为abstract,那么它是一个抽象的类,不能被实例化生成自己的对象,通常只是定义了它的子类共有的一些变量和方法供继承使用。被声明为abstract的抽象类往往包含有被声明为abstract的抽象方法,这些方法由它的非抽象子类完成实现细节。
  ■final
  如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为abstract的,又被声明为final的。
  继承是面向对象程序设计中一个强有力的工具,它允许在已存在的类的基础上创建新的类。新创建的类称为其基础类的子类,基础类称为其子类的父类。子类的对象除了具有新定义的属性和方法外,还自动具有其父类定义的部分或全部属性方法。这样程序员可以在子类中重用父类中已定义好的变量和方法,只需对子类中不同于父类或新添加的部分重新定义,这样就节省了大量的时间、空间和精力。Java在类声明中使用
  extends 父类名
的方式定义继承关系。如果不明显地写出继承的父类名,则缺省地认为所声明的类是Java的Object类的一个子类。Object类是Java中所有的类的祖先类。我们可以把这种类继承关系想象为一棵倒置的类家族树,Object类就是这棵树的根。

  4.1.2 类的组成
  我们已经知道类是代表对象的,而每一个对象总有特定的状态和行为,在类中分别用变量数据和在数据上可进行的操作表示这些状态和行为。因此类的组成成分是变量和方法。变量和方法的声明格式如下:
  [变量修饰符] 数据类型 变量名[=初值] {,变量名[=初值]};
  [方法修饰符] 返回值类型 方法名(参数表){方法体}
其中修饰符用来指明变量和方法的特性。变量可一次定义一个或多个,定义时可以给出初值。例如:
  public int a,b=12;
  protected String s="Hot Java";
定义方法时一定要给出返回值类型和参数表。当没有返回值时,返回值类型记为void。参数表的形式为:
  参数类型 参数值{,参数类型 参数值}
各参数间以逗号分隔。下面是一些简单的例子:
  public static void main(String args[]){...}
  public void paint(Graphics g){...}
  public int area(int length,int width){return length * width;}
其中前两个是我们在第三章已经见过的方法声明,这里略去了具体语句组成的方法体。第三个则是一个计算长方形面积的简单方法,接受整数类型的长度和宽度参数并返回它们的乘积作为结果。
  变量和方法修饰符是用来指明特性的关键字,主要有以下几种:
  ■public
  一个类中被声明为public的变量和方法是“公开”的,意味着只要能使用这个类,就可以直接存取这个变量的数据,或直接使用这个方法。
  ■protected
  一个类中被声明为protected的变量和方法是“受限”的,意味着它们仅能被与该类处于同一个包的类及该类的子类所直接存取和使用。
  ■private
  被声明为private的变量和方法是“私有”的,除了声明它们的类外,不能被任何其它的类直接存取和使用。
  当变量或方法前不加以上三种修饰符时,被认为取friendly状态,即它们只能被同一个包中的类直接存取和使用。但不存在friendly关键字。
  ■static
  被声明为static的变量和方法是属于类而不是属于对象的。不管这个类产生了多少个对象,它们都共享这个类变量或类方法。我们可以在不创建类实例对象时直接使用类变量和类方法。一般来说,在Java中,引用一个特定的变量或方法的形式是:
  对象名.变量名
  对象名.方法名
例如:
  int a=rectangle.length;
  g.drawString("Welcome to Java World!";
即变量和方法是受限于对象的,但声明为static的变量或方法受限于类,使用形式是
  类名.变量名
  类名.方法名
例如:
  System.out.println("Welcome to Java World!";
  String s=String.valueOf(123);
这里我们并没有创建System类或String类的对象,而直接调用System类的类变量out和String类的类方法valueOf。其中valueOf方法将整形参数转换为String类对象。被声明为static的类方法在使用时有两点要特别注意:
  (1)类方法的方法体中只能使用类中其它同样是static的变量或方法;
  (2)类方法不能被子类修改或重新定义。
  ■final
  将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。
  ■abstract
  这个修饰符仅适用于方法。被声明为abstract的方法不需要实际的方法体,只要提供方法原型接口,即给出方法的名称、返回值类型和参数表,格式如下:
  abstract 返回值类型 方法名(参数表);
定义了abstract抽象方法的类必须被声明为abstract的抽象类。

  4.1.3 构造方法和finalizer
  Java中有两个特殊的方法:用于创建对象的构造方法(constructor)和用于撤销对象的方法finalizer,相当于C++中的构造函数和析构函数。构造方法是生成对象时编译器自动调用的方法,用以给出对象中变量的初值。构造方法必须与类同名,而且绝对不允许有返回值,甚至不允许以void来标记无返回值。一个类的构造方法可以有多个,以不同的参数表区分不同的情形,这是Java多态性的一个体现。下面是一个简单的例子。
  例4.1 Rectangle类的构造方法。
  class Rectangle{
   protected int width;/*类Rectangle的两个整型变量*/
   protected int height;/*分代表长方形的长和宽*/
   /*下面是类Rectangle的三个构造方法*/
   /*第一个构造方法,无参数,缺省地给出长和宽*/
    Rectangle(){width=20;height=30);}
   /*第二个构造方法,给出长、宽参数*/
    Rectangle(int w,int h){width=w;height=h;}
   /*第三个构造方法,给出另一个Rectangle作参数*/
    Rectangle(Rectangle r)
    {width=r.width();
     height=r.height();
    }
   /*下面是类Rectangle的另外两个方法,分别为取长和宽的值*/
    public int width()
     {return width;}
    public int height()
     {return height;}
  }
  class Test{
   Rectangle r1=new Rectangle();/*调用第一个构造方法*/
   Rectangle r2=new Rectangle(12,20);/*调用第二个构造方法*/
   Rectangle r3=new Rectangle(r1);/*调用第三个构造方法*/
  }
  在这个例子中Rectangle有三个构造方法,它们的名字相同,参数不同因而采用的调用形式也不同。第一个构造方法不需要任何参数,调用时系统自动地给出统一的固定的长方形的宽和高(这里我们设定为20和30)。第二个构造方法需要两个整形参数,根据用户给出的长方形的宽和高创建长方形对象。第三个构造方法需要一个长方形参数,创建出与这个长方形具有同样的宽和高的长方形对象。在Rectangle类中,width和height都是protected的,不宜直接存取。为了使用方便,我们定义出width()和height()方法来获得一个特定长方形的宽和高,再将取得的数值传递给新创建的对象。像这样在一类中有两个或两个以上同名方法的现象叫Overloading,是多态的一种表现。这样同名方法应该有且必须有不同的参数表,调用时编译系统就是根据参数的匹配情况,包括个数和类型,来决定实际使用哪一个方法的。如果两同名方法的参数表也相同,会造成混淆,编译时将得到出错信息:
  Duplicate method declaration
  (重复的方法声明)
  为了实际创建出对象,我们要使用new。系统执行遇到new,才根据new后面跟随的构造方法名和参数表,选择合适的构造方式,分配内存,创建对象并初始化。一个类若没有显示地定义构造方法,使用new时将调用它的父类的构造方法,这种上溯可一直到达Object类,而Object类的构造方法是语言预先定义好的。
  相对于构造方法,在对象被撤销时调用的方法是finalizer。对所有的类,它的原始定义形式都是一样的:
  void finalize();
没有返回值,而且没有任何参数。一般来说,由于Java的内存管理是由系统自动完成,通常不需要我们重写这个方法,而让它自然而然地从父类(最终也就是从Object类)继承。只有当某些资源需要自动归还时,才需要将这一方法重写。

  4.1.4 重写(Overriding)和重载(Overloading)
  方法的重写Overriding和重载Overloading是Java多态性的不同表现。前者是父类与子类之间多态性的一种表现,后者是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写(Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。这在例4.1中已经可以看到。下面再给出两个简单的例子,分别显示Overriding和Overloading。
  例4.2 Overriding的例示
  class Father{
   void speak(){
    System.out.println("I am Father!";//父类定义的speak方法
   }
  }
  class Son extends Father{
   void speak(){
    System.out.println("I am Son!";//子类重写的speak方法
   }
  }
  public class Check{
   public static void main(String args[]){
    Son x=new Son();
    x.speak();//调用子类的speak方法
   }
  }
  //output of class Check!
  I am Son!
  从这个例子我们可以看到,类Son中的speak()方法重写了其父类Father中一模一样的方法,而它的对象x调用speak()方法的结果是与Son中定义致的。
  例4.3 Overloading例示。
  class Father{
   void speak(){  //无参的speak方法
    System.out.println("I am Father.";
   }
   void speak (String s){  //有参的speak方法
    System.out.println("I like"+s+".";
   }
  }
  public class Check{
   public static void main(String args[]){
    Father x=new Father();
    x.speak();//调用无参的speak方法
    x.speak("music";//调用有参的speak方法
   }
  }
  //out put of class Check
  I am Father
  I like music.
  这个例子中类的Father定义了两个speak方法,在类Check中又两次调用,一次无参,一次有参,打印出两行不同的字符串。注意Java在打印字符串时,字符串间的连接用符号“+”来完成。

  Overriding是父类与子类之间多态性的一种表现;Overloading是一个类中多态性的一种表现。

 

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
12#
 楼主| 发表于 2006-6-27 23:51 | 只看该作者
  4.1.5 几个特殊的变量:null,this和super
  Java中有三个特殊的变量:null,this和super,这三个变量是所有的类都可以使用的,用来指示一些特定的对象。
  null相当于“空”,可以用来代指任何对象,但没有实例。如
    Rectangle r=null;
创建了一个Rectangle的变量r,但并没有一个Rectangle的实例对象由r来代表。r就如同一个可放置Rectangle的盒子,只是这个盒子现在是空的。
  this用以指代一个对象自身。它的作用主要是将自己这个对象作为参数,传送给别的对象中的方法。它的使用形式是这样的:
  class Painter{
    ...
    void drawing(Father y){
    ...
    }
  }
  class Father{
    ...
    void draw(Painter x)
    {...
     x.drawing(this);/*将自身传递给x的drawing方法*/
      ...
    }
  }
  class Test{
   ...
   Father f=new Father();
   Painter p=new Painter();
   f.draw(p);
   ...
  }
例中调用Father类的draw方法时,使用语句
  f.draw(p);
又Father类中定义draw方法时以this为参数调用了类Painter的drawing方法:
  x.drawing(this);
因而实际上调用了Painter类对象p的drawing方法,而将Father类对象f作为参数传递给drawing方法.
  super用来取用父类中的方法和变量数据。它的用法如在下面的例子中所示。
  例4.4在类中使用super的例示。
  /* Check.java */
  class Father{
   void speak(){
    System.out.println("I am Father.";
   }
   void speak(String s){
    System.out.println("I like "+s+".";
   }
  }
  class Son extends Father{
   void speak(){
    System.out.println("My father says.";
    super.speak();//相当于调用Father类的speak()方法
    super.speak("hunting";
     //相当于调用Father类的speak(String s)方法
   }
  }
  class Check{
   public static void main(String args[]){
    Son s=new Son();
    s.speak();
   }
  }
  //Check.java的执行结果:
  My father says:
  I am Fater.
  I like hunting.
  在这个例子中,类Son的speak()方法语句
  super.speak();
  super.speak("hunting";
实际调用了Son的父类Father中的speak()和speak(String s)方法,以实现执行结果后两行的输出。使用父类的变量形式也很类似。
  super.变量名
  super和this的另一个重要用途是用在构造方法中。当一个类中不止一个构造方法时,可以用this在一个构造方法中调用中一个构造方法。若想调用父类的构造函数,则直接使用super。例如我们可心如下定义例4.1中类Rectangle的子类ColorRectangle:
  public class ColorRectaqngle extends Rectangle{
   int color;
   ColorRectangle(int w,int h,int c){
    super(w,h);
    color=c;
   }
   ...
  }
  与父类Rectangle相比,类ColorRectangle增加了color成员变量代表长方形的颜色。在它的构造方法中,用语句
    super(w,h);
调用了类Rectangle的构造方法
    Rectangle(int w,int h);
设定长方形的长和宽,然后就只需设定长方形的颜色:
    color=c;
这样大大提高了代码的重用性。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
13#
 楼主| 发表于 2006-6-27 23:52 | 只看该作者
4.2 Java的包
  在Java中,包的概念和目的都与其它语言的函数库非常类似,所不同的只是其中封装的是一组类。为了开发和重用的方便,我们可以将写好的程序类整理成一个个程序包。Java自身提供了21个预先设定好的包,下面列出其中主要的几个,其余读者参看Java的API:
  java.lang    提供基本数据类型及操作
  java.util    提供高级数据类型及操作
  java.io     提供输入/输出流控制
  java.awt     提供图形窗口界面控制
  java.awt.event  提供窗口事件处理
  java.net     提供支持Internet协议的功能
  java.applet   提供实现浏览器环境中Applet的有关类和方法
  java.sql     提供与数据库连接的接口
  java.rmi     提供远程连接与载入的支持
  java.security  提供安全性方面的有关支持
  我们可以引用这些包,也可以创建自己的包。

  4.2.1 包的声明
  为了声明一个包,首先必须建立一个相应的目录结构,子目录名与包名一致。然后在需要放入该包的类文件开头声明包,形式为:
  package 包名;
这样这个类文件中定义的所有类都被装入到你所希望的包中。例如
  package Family;
  class Father{
   ...//类Father装入包Family
  }
  class Son{
   ...//类Son装入包Family
  }
  class Daughter{
   ...  //类Daughter装入包Family
  }
不同的程序文件内的类也可以同属于一个包,只要在这些程序文件前都加上同一个包的说明即可。譬如:
  //文件 Cat.java
  package Animals;
  class Cat{/*将类Cat放入包Animals中*;
  ...
  }
  //文件Dog.java
  package Animals;
  class Dog{  /*将类Dog放入包Animals中*/
  ...
  }

  4.2.2 包的使用
  在Java中,为了装载使用已编译好的包,通常可使用以下三种方法:
  (1) 在要引用的类名前带上包名作为修饰符。如:
  Animals.Cat cat=new Animals.Cat();
其中Animals是包名,Cat是包中的类,cat是类的对象。
  (2)在文件开头使用import引用包中的类。如:
  import Animals.Cat;
  class Check{
   Cat cat=new Cat();
  }
同样Animals是包名,Cat是包中的类,cat是创建的Cat类对象。
  (3)在文件前使用import引用整个包。如:
  import Animals.*;
  class Check{
   Cat cat=new Cat();
   Dog dog=new Dog();
   ...
  }
Animals整个包被引入,Cat和Dog为包中的类,cat和dog为对应类的对象。
  在使用包时,可以用点“.” 表示出包所在的层次结构,如我们经常使用的
  import java.io.*;
  import java.applet.*;
实际是引入了/java/io/或/java/applet/这样的目录结构下的所有内容。需要指出的是,java.lang这个包无需显式地引用,它总是被编译器自动调入的。使用包时还要特别注意系统classpath路径的设置情况,它需要将包名对应目录的父目录包含在classpath路径中,否则编译时会出错,提示用户编译器找不到指定的类。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
14#
 楼主| 发表于 2006-6-27 23:53 | 只看该作者
4.3 一个邮件类(Mails)的例子
  下面我们给出一个较大的例子,让读者在实例中进一步熟悉Java的类和包。
  这里所有的类都放在包ch4package中,先定义出一个虚基类Mails,然后派生出它的两个子类Parcel(包裹)和Remittance(汇款)。Show类用于实际执行,允许用户创建自己的邮件,然后显示出所有的邮件信息。为了方便地存取邮件,还定义了类ShowMails。接下来我们逐一介绍这经些类。
  例4.5 类Mails程序文件。
  1:package ch4package;
  2: public abstract class Mails{
  3:  protected String fromAddress;
  4:  protected String toAddress;
  5:  public abstract void showMe();
  6: }
  类Mails是一个虚类,不能产生自己的实例对象,而只是描述了邮件最基本的特性。类文件的开头首先用
    package cha4package;
表明Mails类是放于ch4package这个包里的。然后程序第二行为Mails的类声明。
    public abstract class Mails
  用修饰符abstract指出这是个虚类。第三至第四行Mails类中定义了两个变量:
    protected String fromAddress;
    protected String toAddress;
fromAddress和toAddress ,分别代表邮件的寄出地址和送往地址,都是protected类型的,这样cha4package包外的类不能直接引用,保证了信息的隐藏。第五行Mails类定义了方法
showMe(),用于显示一个邮件自身的有在信息:
    public abstract voi showMe();
声明时以abstract修饰,意味着这是一个抽象方法,只给出原型,具体实现要由Mails类的非虚子类通过Overriding完成。
  接下来是Mails的两个非虚子类。
  例4.6 类Parcel和类Remittance程序文件。
  //Parcel.java
  1: package ch4package;
  2:  public class Parcel extends Mails{//邮件类的子类Parcel类
  3:   protected int weight;
  4:   Parcel(String address1,String address2,int w){//构造方法
  5:   fromAddress=address1;
  6:   toAddress=address2;
  7:   weight=w;
  8:  }
  9:  public void showMe(){
  10:  System.out.print("Parcel:";
  11:  System.out.println("\tFrom:"+fromAddress+"\tTo:"+toAddress);
  12:  System.out.println("\tWeigth:"+weight+"g";}
  13: }
  //Remittance.java
  1: package ch4package;
  2:  public class Remittance extends Mails{//邮件类的子类Remittance
  3:   protected int money;
  4:   Remittance(String address1,String address2,int m){//构造方法
  5:   fromAddress=address1;
  6:   toAddress=address2;
  7:   money=m;
  8:  }
  9:  public void showMe(){//显示邮件信息
  10:  System.out.println("Remittance:";
  11:  System.out.println("\tFrom:"+fromAddress+"\tTo:"+toAddress);
  12:  System.out.println("\tMoney:"+money+" Yuan";
  13: }
  14:}

这里是邮件的两个子类:包裹Parcel和汇款Remittance。以类Parcel为例详细说明。首先在程序开头写出:
  package ch4package;
  一方面将类Parcel装入包ch4package,另一方面方便类Parcel使用包ch4package中的其它类,如已定义的Mails类。接下来类Parcel声明时用
  extends Mails
表明自己是Mails的一个子类。在第三行Parcel声明了一个weight变量,用来代表包裹的重量。加上从父类Mails继承下来的变量fromAddress和toAddress,类Parcel一共有三个成员变量:
  寄出地址 fromAddress,寄达地址toAddress和重量weight
  相对应的,它的构造方法Parcel也必须有三个参数,分别传递给三个成员变量。构造方法的定义如第四行至第八行所示。由于Parcel类不是虚类,所以必须在其中重写完成它的父类Mails中声明的抽象方法showMe。Parcel的showMe()方法仅仅是将自己的邮件类型和三个变量的信息在屏幕上显示出来。
  类Remittance与Parcel非常相似,只是它定义的变量为money,用来代表汇款的金额。它也必须具体完成方法showMe。
  下面我们看到的是用于存取邮件的类ShowMails。
  例4.7 类ShowMails程序文件。
  1: package ch4package;
  2: import java.lang.*;
  3: public class ShowMails{
  4:  protected Mails showList[];//邮件数组序列
  5:  protected static final int maxMails=50;//最大邮件个数
  6:  protected int numMails;//当前邮件个数
  7:  ShowMails(){
  8:   showList=new Mails[maxMails];
  9:   numMails=0;
  10: }
  11: public void putMails(Mails mail){
  12:  if(numMails<maxMails){
  13:   showList[numMails]=mail;//加入邮件
  14:   numMails++;//修改计数
  15:  }
  16: }
  17: public Mails getMails(int index){//获取邮件
  18:  if((0<=index)&&(index<numMails)) return showList[index];
  19:  else return null;
  20: }
  21: public void showAll(){//展示邮件
  22:  if(numMails>0)
  23:   for (int i=0;i<numMails;i++){
  24:    System.out.print("Mail NO"+(i+1)+":";//邮件序号
  25:    showList.showMe();//邮件具体信息
  26:   }
  27:  else
  28:   System.out.println("No mails.";
  29: }
  30: public int mailnum(){
  31:  return numMails;
  32: }
  33:}
  程序第四行至第六行类ShowMails定义了三个成员变量:
    showList[],maxMails和numMails
  变量showList[]是类Mails的一个数组。但由于Mails本身是个虚类,因而showList[]的元素不可能是Mails的对象,它实际上是用来存放Mails的两个子类Parcel和Remittance的对象的。一般说来,一个被声明为类A的的变量,总可以被赋值为任何类A的子类的实例对象。这与父子类之间的类型转换的原则是一致的:父类到子类的转换可以隐式地自动进行,而子类到父类的转换则需要显式地加以说明。
  变量maxMails用来指出showList[]中最多可容 纳的邮件数,它对ShowMails的所有对象都应是固定且一致的。因此它被声明为tatatic和final的,为所有对象共享且不可更改。变量numMails则用来作为showList[]中实际邮件个数的计数。
  对应ShowMails的三个成员变量,我们在ShowMails()构造方法中只需做两件事:实际创建类mails的数组showList[],然后将邮件计数numMails置零。
  第11行开始的方法putMails和第17行开始的方法getMails分别完成对showList[]中邮件的存取。第30行的mailnum方法则返回当时的邮件计数值。putMails方法接受一个邮件类参数,并把它加入到当前邮件序列的末尾。getMails方法接受一个整型参数作为邮件序号,根据该序号找出当前邮件序列中对应邮件返回。当给定的邮件号index不在有效范围时,以据该序号找出当前邮件序列中对应邮件返回。当给定的邮件号index不在有效范围时,以
    return null;(19行)
返回一个定值。这一句看上去并没有完成什么实质性的工作,但如果省略则编译时会出错。因为getMails方法的返回值已声明为Mails类,这就要求在任何情况下都返回一个符合这一要求的值。而空变量null可与任何类型匹配,恰好能适合这样的要求。
  第21行的方法showAll显示showList[]中所有邮件的信息。每一邮件首先显示自己的邮件号。因为showList[]数组的下标从0开始,为了符合人们的日常习惯,将每一个下标加1后再作为邮件号输出。各个邮件的显示是调用邮件的showMe()方法来实现的。因为showMe()方法已经在虚类Mails中定义了,所以不管showList[]中的实际元素是Parcel还是Remittance,编译器总能顺利地连接调用相应的代码。Java面向对象特性中的动态绑定(Dynamic Binding),保证了无需在编译前确定地知道showList[]每一个数组元素的类型,就能成功地实现这样的链接。
  最后给出的类是实际执行的Shos类。
  例4.8 类Show程序文件
  1: package ch4package;
  2: import java.io.*;
  3:
  4: public class Show{
  5: public static ShowMails board=new ShowMails();//邮件库变量
  6:  public static void main(String args[])throws IOException{
  7:    boolean finished=false;
  8:   BufferedReader in =new BufferedReader(new InputStreamReader(System.in));
  9:   while(!finished){//添加邮件
  10:    System.out.print("\nDo you want to add mails(Y/N)?";
  11:    System.out.flush();
  12:    char ch=in.readLine().charAt(0);
  13:    if('Y'==Character.toUpperCase(ch)){//输入地址
  14:    System.out.println("Address information:";
  15:    System.out.print("\tFrom:";
  16:    System.out.flush();
  17:    String address1=in.readLine();
  18:    System.out.print("\tTo:";
  19:    System.out.flush();
  20:    String address2=in.readLine();
      //选择邮件各类(包裹或汇款)
  21:    System.out.print("Choose the mail type:1-Parcel 2-Remittance ");
  22:    System.out.flush();
  23:    ch=in.readLine().charAt(0);
  24:    if('1'==ch){//输入包裹重量
  25:     System.out.print("Parce\tWeight:");
  26:     System.out.flush();
  27:     int w=getInt();
  28:     Parcel pa=new Parcel(address1,address2,w);
  29:     board.putMails(pa);
  30:    }
  31:    if('2'==ch){//输入汇款金额
  32:     System.out.print("Remittance\tMoney:");
  33:     System.out.flush();
  34:     int m=getInt();
  35:     Remittance re=
          new Remittance(address1,address2,m);
  36:     board.putMails(re);
  37:    }
  38:   }
  39:   else finished=true;
  40:  }
  41:  System.out.println(" ");
  42:  board.showAll();//输出所有邮件信息
  43: }
   //键盘输入获取整数
  44: public static int getInt() throws IOException{
  45:  BufferedReader in= new BufferedReader
       (new InputStreamReader(System.in));
  46:  String st=in.readLine();
  47:  Integer i=new Integer(st);
  48:  return i.intValue();
  49: }
  50:}
  由于涉及交互,类Show中用到了许多输入输出语句,我们在程序第2行用
    import java.io.*;
引入Java的IO包。这个包封装了大量有关输入输出的方法,具体内容将在第七章中详细介绍。这里我们只需要弄清楚所用到的输入/出语句的功能。
  在输入/出中,总有可能产生输入输出错误,Java反这引起错误都归入IOException(IO异常)因为我们不打算在程序中加入对这些异常的处理,所以需要在每个方法的参数表后用关键字throws“扔出”这些异常,如第6行
    public static void main(String args[])throws IOException
这样异常发生时,将自动中止程序运行并进行标准处理。请参看第五章的内容。
  程序的输入来源是一个BufferedReader类的对象in,它的声明在第8行:
  BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
因而具有BufferedReader中定义的所有输入功能。
  in.readLine()
是读入一行输入,并返回一字符串。而
  charAt(i)
是String类的一个方法,取得指定字符串的第i个元素作为字符型返回。这两上方法边用,则可取得想要的输入。而在输入前用
  System.out.flush();
将缓冲清空,以保证输入的正确性。
  System.out.print
  System.out.println
都是输出语句,不同的只是后者在输出结束后自动换行。类System和getInt()中用到的类都是Interger(注意不是int!)都在Java的lang名中定义,我们将在第六章详细介绍。
  在了解以上的基本输入输出后,这个程序就变得较等了。为了方便起见,我们不失一般性的将Show类的所有成员都定义为static的,这样,类Show就不同志需要特别定义的构造方法了。在第5行声明的变量board是ShowMails类的对象,用来建立邮件库:
  public static ShowMails board=new ShowMails();
第44行开始的getInt方法用来从键盘输入获得一个整数。第6行开始的main方法则是程序的主体。它实现的功能是不断询问是否要加入新邮件,肯定回答时要求选择邮件类型并输入相应信息。据此创建邮件子类对象并加入board中,直至得到不定回答退出。最后显示此时已有的邮件信息。邮件的加入和显示都通过简单的
    board.pubMails()
    board.showAll()
调用ShowMails的方法来实现的,简洁明了而层次清晰。这就是面向对象进行数据封装和重用的优点所在。要执行类Show,我们需要将例4.5~例4.8的文件依次输入、编译。最后用解释器java执行类Show。下面给出的是Show的运行结果,其中加下划线“_”的是键盘输入。
  例4.9 类Show运行结果。
  D:\java01>java ch4package.Show

  Do you want to add mails(Y/N)?n //询问有是否添加邮件

  No mails.             //显示没有邮件

  D:\java01>java ch4package.Show

  Do you want to add mails(Y/N)?y//询问有是否添加邮件
  Address information:      //要求输入地址信息
  From:NanJing
  To:BeiJing
  Choose the mail type:1-Parcel 2-Remittance 1//要求选择邮件类型
  Parce Weight:100//要求输入包裹重量

  Do you want to add mails(Y/N)?y
  Address information:
  From:ShangHai
  To:TianJing
  Choose the mail type:1-Parcel 2-Remittance 2
  Remittance Money:400//要求输入汇款金额

  Do you want to add mails(Y/N)?n

  Mail NO1:Parcel://输出所有邮件信息
     From:NanJing To:BeiJing
  Weigth:2g
  Mail NO2:Remittance:
  From:ShangHai To:TianJing
  Money:400 Yuan

  D:\java01

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
15#
 楼主| 发表于 2006-6-27 23:53 | 只看该作者
4.4 Java的接口
  4.4.1 引进接口的目的
  Java的接口也是面向对象的一个重要机制。它的引进是为了实现多继承,同时免除C++中的多继承那样的复杂性。前面讲过,抽象类中包含一个或多个抽象方法,该抽象类的子类必须实现这些抽象方法。接口类似于抽象类,只是接口中的所有方法都是抽象的。这些方法由实现这一接口的不同类具体完成。在使用中,接口类的变量可用来代表任何实现了该接口的类的对象。这就相当于把类根据其实现的功能来分别代表,而不必顾虑它所在的类继承层次。这样可以最大限度地利用动态绑定,隐藏实现细节。接口还可以用来实现不同类之间的常量共享。
  为了说明接口的作用,我们不妨假设有一系列的图形类,其中一部分在图形中加入了文字,成为可编辑的,它们应当支持最普遍的编辑功能:
  cut,copy,paste和changeFont
将这些方法的原型统一组合在一个EditShape接口中,就可以保证方法名的规范统一和使用的方便。我们画出这个假想的类和接口的继承关系图,可以更直观地了解。

                    Object
                     ↓
                    Shape
        ┌────────────┼─────────────┐
        ↓            ↓             ↓
       Circle          Rectangle          Triangle
       ↙ ↘          ↙ ↘            ↙ ↘
  PaintCircle TextCircle PaintRectangle TextRectangle PaintTriangle TextTrangle
           ↑           ↑               ↑
           └───────────┼───────────────┘
                      EditShape
        图4.1 Shape 和 EditShape

  以图中类Circle的两个子类为例。类PaintCircle未实现EditShape接口,不支持上述编辑功能。而类TextCircle既是Cricle的子类,又实现了EditShape接口,因而不但具有Circle类的图形牲,又支持EditShape定义的编辑功能。而在TextCircle,TextRectangle和TextTriangle中,支持这些编辑功能的方法是同名同参的(与EditShape的定义一致),这又提供了使用上的方便。

  4.4.2 接口的声明和使用
  Java的接口类似于抽象类,因而它的声明也和抽象类类似,只定义了类中方法的原型,而没有直接定义方法的内容。它的声明格式为:
  [接口修饰符] interface 接口名 [extends 父类名]
    {...//方法的原型定义或静态常数}
  接口修饰符可以是public或abstract,其中abstract缺省时也有效。public的含义与类修饰符是一致的。要注意的是一个编译单元,即一个.java文件中最多只能有一个public的类或接口,当存在public的类或接口时,编译单必须与这个类或接口同名。
  被声明的变量总是被视为static和final的,因而必须在声明时给定初值。被声明的方法总是abstract的,abstarct缺省也有效。与抽象类一样,接口不需要构造方法。接口的继承与为是一样的,当然一个接口的父类也必须是接口。下面是一个接口的例子:
  interface EditShape{
   void cut();
   void copy();
   void paste();
   void changeFont();
  }
在使用时,为了将某个接口实现,必须使用关键字implements。格式是这样的:
  [类修饰符] class 类名 [extends 父类名] [implements 接口名表]
其中,接口名表可包括多个接口名称,各接口间用逗号分隔。“实现(implements)“了一个接口的非抽象类必须写出实现接口中定义的方法的具体代码,同时可以读取使用接口中定义的任何变量。
  例4.10 接口的实现
  class TextCircle extends Circle implements EditShape
  {...
   void cut()     {...//具体实现代码}
   void copy()    {...//具体实现代码}
   void paste()    {...//具体实现代码}
   void changeFont   {...//具体实现代码}
   ...
  }

  4.4.3 多继承
  在Java中,类之间只允许单继承,但我们可以把一个类实现的接口类也看作这个类的父类。类从它实现的接口那里“继承”了变量和方法,尽管这些变量是静态常量,这些方法是未实现的原型。如果一个类实现的接口类不止一个,那么所有这些接口类都被视为它的“父类”。这样,实现了一个或多个接口的类就相当于是从两个(加上该类原有意义上的父类)或两个以上的类派生出来的。Java的多继承正是建立在这种意义之上。通过接口的继承,相当于只选择了一部分需要的特征汇集在接口中由不同的类共享并继承下去,而不必通过父子类间的继承关系将所有的方法和变量全部传递给子类。所以我们又可以把Java的这种多继承称为“有选择的多继承”。这种多继承与一般的多继承相比,更为精简,复杂度也随之大大降低。
  在多继承时,一个子类可能会从它的不同父类那里继承到同名的不同变量或方法,这往往会引起两义性问题,即不知道子类中这样的变量或方法究竟是继承了哪一个父类的版本,在Java中,为了防止出现这样的两义性问题,规定不允许一个子类继承的父类和实现的接口类中定义同名的不同变量,否则编译该子类时将出错,无法通过。而对于方法,由于接口类中定义的总是abstract的方法原型,而没有实际代码,所以不会出现类似的两义性问题。相反,常会存在这样的情况:当接口类中要求实现的方法子类没有实现,而子类的父类中定义有同名方法时,编译器将子类从父继承的该方法视为对接口的的实现。这样的继承和实现都被认为是合法的。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
16#
 楼主| 发表于 2006-6-27 23:54 | 只看该作者
4.5 实现了接口的邮件类例子
  这一节我们将4.3节邮件类的例子加以改进和扩展,加入有关接口的内容,以说明接口和多继承的概念。
  首先定义一个名为MailPost的接口,其中没有定义变量,而是给出两个有关邮寄方法原型。
  calPrice()计算邮费并以浮点数形式返回;
  post()完成邮寄。
  例4.11 接口MailPost。
  //MailPost.java
  package ch4package;
  public interface MailPost{
   public float claPrice();
   public void post();
  }
  接下来在包裹Parcel和汇款Remittance的基础上分别派生出可邮寄的包裹和汇款:PostParcel和PostRemit两个子类。
  例4.12 子类PostParcel和PostRemit。
  ---------------------------------
  //PostParcel.java
package ch4package;
import java.lang.*;

public class PostParcel extends Parcel implements MailPost{
protected int postage;
protected boolean postable;
protected boolean posted;
PostParcel(Ttring address1,String address2,int w,intp){
//构造方法
super(address1,address2,w);
postage=p;
postable=false;
posted=false;
}
public float calPrice(){//计算邮资
return((float)0.05*weight);
}
public void post(){//邮寄包裹
float price=calPrice();
postable=(price<=postage);
posted=true;
}
public void showMe(){//显示邮件信息
float price=calPrice();
System.out.println("Postable Parcel:";
System.out.println("\tFrom:"+fromAddress+\tTo"
+toAddress);
System.out.println("\tWeigth+weigth+"g\tPostage:"
+postage+"Yuan";
if(posted){
if(postable)System.out.println("\tIt has been
posted !";
else{
System.out.println("\tIt needs more postage:";
System.out.println("\tThe current postage
is:"+postage+"Yuan";
System.out.println("\t\tThe price is:"+price+"Yuan";
}
}
}
}
//PostRemit.java
package ch4package;
import java.lang.*;

public class PostRemit exteds Remittance implements MailPost{
protected int postage;
portected boolean postable;
protected boolean posted;
PostRemit(String address1,String address2,int m,int p){
//构造方法
super(address1,address2,m);
postage=p;
postable=false;
posted=false;
}
public float calPrice(){//计算邮资
float price=cealPrice();
postable=(price<=postage);
posted=true;
}
public void showMe(){//显示邮件信息
float price=calPrice();
System.out.println("Postable Remit:";
System.out.println("\tFrom:"+fromAddress+"\tTo:"
+toAddress);
System.out.println("\tMoney:"+money+"Yuan"+"\tPostage:"
+postage+"Yuan";
if(posted){
if(postable)System.out.println("\tIt has been
posted!";
else{
System.out.println("\tIt needs more postage:");
System.out.println("\t\tThe current postage is:"
+postage+"Yuan");
System.out.println("\t\tThe price is:"
+price+"Yuan");
}
}
}
}

  ---------------------------------
  这两个类都实现了接口MailPost。由于两个类非常相似,我们仍然重点讲解其中一个:类PostParce。
  PostParcel仍是包ch4package中的一员,它是类Parcel的子类(extends Parcel),又实现了接口MailPost(implements MailPost):
    public class PostParcel extends Parcel implements MailPost
  在Parcel的基础上,它新增加了三个变量:
    postage,posted,postable
其中整型的postage用来记录邮寄人提供的邮资,布尔型的posted和postable分别用来记录是否被尝试邮寄过以及邮寄是束成功。在PostParcel的构造方法中,第9行语句
    super(address1,address2,w);
调用了它的父类Parcel的构造方法,设定它从Parcel中继承的变量寄出地址、寄达地址和重量的初值。这就是我们在前面提到过的super变量在构造方法中的用途:调用父类的相应构造方法。这样做的一个好处是可以重用父类的代码,然后PostParcel就只需设定邮资,并将posted和postable初值都置为false。
  PostParcel和PostRemit都实现了接口MailPost,国而在它们的定义中,都必须给出方法calPrice()和post()的具体实现。在PostParcel中,为了简单起见,邮费只是根据重量每克收到0.05元,而不考虑寄达的距离,如语句第15行:
    return ((float)0.05*weight);
在post()方法中,将计算所得邮资与瑞有邮费加以比较,若邮费已够将postable设为true,包裹可邮寄;否则postable为false,包裹不可邮寄。无论postable取值如何,都已试图邮寄,所以将posted置为true。处理过程见第18行至20行。
  最后一个方法是showMe()。在这里,PostParcel重写(Overriding)了它的父类Parcel中的同名方法。当包裹尚未被试图邮寄过,则在基本信息后附加有关的邮寄信息,若未邮寄成功,给出所需最费提示。
  PostRemit类的基本构成与PostParcel是一致的,读者可以自己试着读懂它的源文件。
  在包ch4package中,类Mails,Parcel,Remittance以及ShowMails都无需改动,只有最后的可执行类Show需要相应的修改。它的源程序如下。
  例4.13 可执行类Show程序文件。
  -------------------------
//Show.java

  1: package ch4package;
import java.lang.*;
  2: import java.io.*;
  3:
  4: public class Show{
  5: public static ShowMails board=new ShowMails();
  6:  public static void main(String args[])throws IOException{
  7:    boolean finished=false;
  8:   BufferedReader in =new BufferedReader(new InputStreamReader(System.in));
  9:   while(!finished){//添加邮件
  10:    System.out.print("\nDo you want to add mails(Y/N)?");
  11:    System.out.flush();
  12:    char ch=in.readLine().charAt(0);
  13:    if('Y'==Character.toUpperCase(ch)){
  14:    System.out.println("Address information:");
  15:    System.out.print("\tFrom:");//输入地址信息
  16:    System.out.flush();
  17:    String address1=in.readLine();
  18:    System.out.print("\tTo:");
  19:    System.out.flush();
  20:    String address2=in.readLine();
      //选择邮件种类
  21:    System.out.print("Choose the mail type:1-Parcel
2-Remittance ");
  22:    System.out.flush();
  23:    ch=in.readLine().charAt(0);
  24:    if('1'==ch){//输入包裹重量
  25:     System.out.print("Parcel\tWeight:");
  26:     System.out.flush();
  27:     int w=getInt();
//是否寄出邮件
System.out.print("Do you want to post it(Y/N?");
System.out.flush();
ch=in.readLine().charAt(0);
if('Y'==Character.toUpperCase(ch)){//输入邮资
System.out.println("You want to post in,then
input your postage:");
System.out.flush();
int p=getInt();
//可邮寄包裹
PostParcel pa=new
PostParcel(address1,address2,w,p);
board.putMails(pa);
}
//不可邮寄包裹
else{Parcel pa=new Parcel(address1,address2,w);
board.putMails(pa);}
}
if('2'==ch){
System.out.print("Remittance\tMoney:");
System.out.flush();
int m=getInt();
System.out.print("Do you want to post it(Y/N)?");
System.out.flush():
ch=in.readLine().charAt(0);
if('Y'==Character.toUpperCase(ch)){
System.out.println("You want to post it,then input
postage:");
System.out.flush();
int p=getInt();
//可邮寄汇款
PostRemit re=new PostRemit(address1,address2,m,p);
board.putMails(re);
}
//不可邮寄汇款
else{Remittance re=new Remittance(address1,address2,m);
board.putMails(re);}
}
}
else finished=true;
}
System.out.println("");
board.showAll();//显示邮件信息
post();
}
public static int getInt() throws IEOxception{
BufferedReader in=new BufferedReader
(new InputStreamReader(System.in));
String st=in.readLine();
Integer i=new Integer(st);
return i.intValue();
}
private static void post()throws ClassCastException,IOException{
int n\board.mailnum();
if(n!=0){
System.out.println("You have "+n+" mails");
boolean end=false;
//检查邮寄情况
while(!end){
System.out.print("\nInput the mail NO you want to check the
result(输0退出):");
System.out.flush();
int i=getInt();
if(i!=0){
try{
Mails obj=board.getMails(i-1);
post((MailPost)obj);
obj.showMe();
}catch(ClassCastException ex){
System.out.println("Mail is not postable!");}
}
else end=true;
}
}
}
private static void post(MailPost obj){
obj.calPrice();
obj.post();
}
}
  -------------------------
  与第三节例4.8中类的Show相比,改动后的Show的main方法增加了询问是否要将邮件设为可邮寄类型的功能以及相应的处理段,并调用Post()方法邮寄邮件并给出邮寄情况说明。类Show定义了两个post方法来实惠邮寄。这两个方法虽同名,但参数不同,完成的功能也大相径庭。
  第72行至92行的第一个post方法没有参数。它首先给出现有邮件数量,然后根据输入的邮件号通过ShowMails的getMails方法取得邮件,再调用第二个post方法实际将邮件寄出;当输入的邮件号为零时结束。在调用第二个post方法时,需要将邮件显式转换为接口类MailPost:
  83:Mails obj=bord.getMails(i-1);
  84ost((MailPost)obj);
因为PostParcel和PostRemit都实现了接口MailPost,都支持这样的转换,就可以通过种形式从功能上将它们统一起来。如果该邮件所属的类没有实现接口MailPost ,如类Parcel或类Remittance,这样的类型转换就不能实现,将引发类型转换异常(ClassCastException),不再转去调用post方法,而由catch结构给出“邮件无法被邮寄”的报错信息:
  86:}catch(ClassCastException ex){
  87: System.out.println("Mail is not postable!");}
其中的try-catch结构是Java中异常处理的典型结构。
  第二个post方法带一个MailPost接口类的参数,它实际调用接口定义的方法calPrice和post将邮件寄出。
  下面我们来看一个Show的执行实例,其中带下划线“_”的部分为执行的键盘输入。
  例4.14 Show的执行结果。
  --------------------
  --------------------
  当启动Show的运行后,首先依照提示创建三个邮件对象,其中第一个是不可邮寄包裹后两个分别是可邮寄的包裹和汇款。停止添加邮件后顺序显示现有邮件信息,包括邮件号、邮件类别、地址信息、重量/金额以及已付邮资,并提示现有邮件总数。此时我们可依次检查邮件是否可寄出:
  输入邮件号“1”,由于此包裹不是可邮寄包裹类,给出报告:邮件不可寄出;
  输入邮件号“2”,该邮件是可邮寄包裹,且通过邮资计算已付足,给出报告:邮件可寄出;
  输入邮件号“3”,该邮件是可邮寄汇款,但欠缺邮资,给出报告:邮件需补足邮资,然后列出应交邮费与实交邮费比较。
  最后输入数字“0”,结束本次执行。
  这样我们就完成了对第三节中邮件类的扩充和改进,最终得到的包ch4package中所有类和接口的层次继承关系,如图4.2所示。读者可以对照这个图理清它们的继承和实现关系。
             Object
        ┌─────┼─────┐
        ↓     ↓     ↓
       Mails  ShowMails  show
    ┌───┴───┐
    ↓       ↓
   Parcel    Remittance
    ↓       ↓
  PostParcel  PostRemit
      ↖  ↗
      MailPost
    图4.2 包ch4package的类和接口层次

本章小结
  在这一章中,我们真正接触到Java的面向对象的机构:类、包和接口,介绍了如何运用它们实现继承、封装、多态和动态绑定经及这种实现的好处。
  在下一章中,将介绍Java的另外两个基本而重要的机制:线程(Thread)和异常(Exception),使大家了解Java的同步和出错处理。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
17#
 楼主| 发表于 2006-6-27 23:54 | 只看该作者
第五章 线程与异常处理(上)
  上一章我们介绍了Java的面向对象的机制:类、包和接口。本章我们将介绍一下Java的另外两个机制:多线程(Multithread)和异常处理(Exception)。本章前半部分是关于Thread这一基本类以及一套先进的同步原语的介绍,它们使得利用Java编写多线程大为方便。在本章的后半部分我们将介绍Java的异常处理机制(Exception),异常处理机制提高了程序的健壮性。另外,本章中间将介绍一个Java的debugger工具Jdb的使用,Jdb工具对于调试多线程程序尤其有好处。

5.1 多线程(Multithread)
  5.1.1 线程的基本概念
  在介绍多线程之前,我们先来了解一些相关的基本概念。一般来说,我们把程序的一次执行称为进程(process)。一个进程包括一个程序模块和该模块一次执行时所处理的数据。每个进程与其它进程拥有不同的数据块,其内存地址是分开的。进程之间的通信要通过寻址,一般需使用信号、管道等进行通信。线程(thread)是指进程内部一段可独立执行的有独立控制流的指令序列。子线程与其父线程共享一个地址空间,同一个任务中的不同线程共享任务的各项资源。
  多进程与多线程是多任务的两种类型。以前的操作系统,如Win31,只运行多进程,而Win95及WinNT则支持多线程与多进程。Java通过提供Package类(Java.lang.package)支持多进程,而提供Thread类来支持多线程。
  多线程与多进程的主要区别在于,线程是一个进程中一段独立的控制流,一个进程可以拥有若干个线程。在多进程设计中各个进程之间的数据块是相互独立的,一般彼此不影响,要通过信号、管道等进行交流。而在多线程设计中,各个线程不一定独立,同一任务中的各个线程共享程序段、数据段等资源,如图5.1。
  正如字面上所表述的那样,多线程就是同时有多个线程在执行。在多CPU的计算机中,多线程的实现是真正的物理上的同时执行。而对于单CPU的计算机而言,实现的只是逻辑上的同时执行。在每个时刻,真正执行的只有一个线程,由操作系统进行线程管理调度,但由于CPU 的速度很快,让人感到像是多个线程在同时执行。
  多线程比多进程更方便于共享资源,而Java又提供了一套先进的同步原语解决线程之间的同步问题,使得多线程设计更易发挥作用。用Java设计动画以及设计多媒体应用实例时会广泛地使用到多线程,在后面几章你将看到多线程的巨大作用,当然,现在必须先学习一些多线程的基本知识,慢慢地你就体会到它的优越性。

  5.1.2 线程的状态
  如同进程有等待、运行、就绪等状态一样,线程也有其状态。
  当一个线程通过new被创建但还未运行时,称此线程处于准备状态(new状态)。当线程调用了start()方法或执行run()方法后,则线程处于可运行状态。若在等待与其它线程共享资源,则称线程处于等待状态。线程的另一个状态称为不可运行(not runnable)状态,此时线程不仅等分享处理器资源,而且在等待某个能使它返回可运行状态的事件,例如被方法suspend()挂起的进程就要等待方法resume()方可被唤醒。当调用了stop()方法或线程执行完毕,则线程进入死亡(dead)状态。线程的各个状态之间的转换关系见图5.2。


  5.1.3 创建线程
  在了解基本概念后,下面学习如何在Java中创建多线程。
  Java通过java.lang.Thread类来支持多线程。在Thread类中封装了独立的有关线程执行的数据和方法,并将多线程与面向对象的结构合为一体。
  Java提供了两种方法创建线程,一种是继承Thread类,另一种则是实现接口Runnable。
  1.继承Thread类
  通过继承Thread类创建线程十分简单,只需要重载run()方法提供执行入口就可以,下面我们通过例5.1来解释说明。
  例5.1 ThreadTest1.java。

import java.lang.Thread;
import java.lang.System;
import java.lang.Math;
import java.lang.InterruptedException;

class ThreadTest1{
public static void main(String args[])
  throws java.io.IOException{
  System.out.println("If want to show the result,press return";
  MyThread thread1=new MyThread("thread1";
  MyThread thread1=new MyThread("thread2";//创建了两个线程thread1和thread2
  thread1.start();//开始执行线程
  thread2.start();
  char ch;
  while((ch=(char)System.in.read()) != '\n');//不断循环,等待输入回车符
  thread1.tStart();//改变thread1和thread2中的循环控制变量的值
  thread2.tStart();//以下部分保证main()方法是最后一个结束的
  while((thread1.isAlive())|(thread2.isAlive()));
  /*{
  you can do anything that you want to do here.
  }
  */
  System.out.println("The test is end.";
}
}

//类MyThread继承了类Thread
class MyThread extends Thread{
private boolean keepRunning=true;
public MyThread(String id){//类MyThread的构造方法
  super(id);
}
void randomWait(){//让线程处于等待状态
  try{
    sleep((long)(3000*Math.random()));
  }
  catch(InterruptedException x){
    System.out.println("Interrupted!";
  }
}
public void tStart(){
  keepRunning=false;
}
public void run(){//重写了类Thread中的方法run(),main()中调用Thread的方法start()后将自动调用此方法
int i=0;
while(keepRunning) i++;//i代表循环次数
//输出结果
for(int j=0;j<=3;i++){
  randomWait();
  System.out.println("I am"+getName()+"—— I have run"+i+"times.";
  i++;
  }
  System.out.println(getName()+" is dead!";
}
}

  这个程序中创建了两个线程thread1和trhrad2。每个线程将打印一些内容。当线程死亡时,将打印出线程死亡信息。试着执行一下这个程序,你将发现每次的结果都不尽相同。
  运行结果:(略)
  下面我们分析一下这个程序。为创建Thread,第一行你必须写import java.lang.Thread。行6~25中书写的类ThreadTest1包含了一个main()方法(行7~24)。行8的throws java.io.IOException暗示了main()方法中可以产生IOException(有关异常处理后面几节将详细介绍),这主要是为了调用方法System.in.read()实现输入功能。main()方法中创建了两个MyThread的对象,即行10的thread1与行11的thread2。
  行28~55中书写的类MyThread是Thread类的子类。在类MyThread中重写了方法run()(行43~54)。在此种构造线程的方法中,这是必须的。行29~33定义了MyThread类的构造方法MyThread(String id)。行32和行40定义了方法randomWait()和tStart()。
  在ThreadTest1的main()方法中,当行13调用thread1.start()与thread2.start()后,线程thread1与thread2进入可运行状态,分别自动执行其run()方法,而main()方法也继续执行,此时相当于有三个线程在同时执行。
  看一下程序,此时thread1与thread2在执行第44~54行的run()方法,为断循环并累计循环次数,而main()则在等待循环直至入为回车符。 输入回车符,main()方法结束循环,继续执行,调用了MyThread中方法tStart()(41~43行),这样结束了Thread1与Trhead2在run()方法中的循环,开始执行输出。由于thread1,thread2与main()三个线程轮流占有CPU,所以显示了各自结果,这也是为何多次执行结果不同的原因,此时体现了多线程的功能。
  请注意一下Run()中调用了MyThread类中自定义的方法randomWait()(33~40行)。在randomWait()中调用了方法sleep(),sleep()方法是Thread类中的方法,它让正在执行的线程小睡片刻,进入不可运行状态(not runnable状态),当时间到时,线程会回复到runnable状态。当线程执行sleep()时,有可能会被打断,因而程序中加了一段处理InterruptedException的中断处理和打印信息,这样加强了程序的健壮性。
  另外,人们会注意到,在main()中有一段循环并未完成什么功能,这是为了简化程序,其实那段时间中你可以做你想完成的任何工作。但请注意,在执行时,最后一个结束的必须是main()方法,而不可以是其它,否则执行结束将不返回C:提示符,这也是在程序中为何调用了isAlive()方法进行判别的原因。调用类Thread的isAlive()方法可以测试线程是否仍在运行状态(此外指还未死亡)。
  至此,你已真正了解了你的第一个关于Thread的程序,其实Thread中还有很多方法在此未被使用,后面将会进一步介绍。下面先介绍一下创建Thtead的另一个方法:利用实现接口Runnable创建Thread。
  2.实现接口Runnable
  使用用类java.lang.Runnable中的接口Runnable也可创建线程。下面的例子与例5.1实现的功能相同,只是它利用接口Runnable来实现。
  例5.2 ThreadTest2.java。
import java.lang.Thread;
import java.lang.System;
import java.lang.Math;
import java.lang.InterruptedException;
import java.lang.Runnable;

class ThreadTest2{
public static void main(String args[])
  throws java.io.IOException{
  System.out.println("If want to show the result,press return";//创建了两个MyClass类的对象//class1和class2,MyClass类实现了接口Runnable
  MyClass class1 = new MyClass("thread1";
  MyClass class2 = new MyClass("thread2";//创建了两个MyClass类的对象class1和class2,MyClass类实现了接口Runnable
  Thread thread1=new Thread(class1);
  Thread thread2=new Thread(class2);//将对象class1和class2作为参数传给Thread类的构造函数,创建了两个线程thread1和thread2。
  thread1.start();//开始执行线程
  thread2.start();
  char ch;
  while((ch=(char)System.in.read()) != '\n');//不断循环,等待输入回车符
  class1.tStart();//改变thread1和thread2中的循环控制变量的值
  class2.tStart();//以下部分保证main()方法是最后一个结束的
  while((thread1.isAlive())||(thread2.isAlive()));
  /*{
  you can do anything that you want to do here.
  }
  */
  System.out.println("The test is end.");
}
}

//类MyClass实现了接口Runnable
class MyClass implements Runnable{
boolean keepRunning=true;
String name;
public MyClass(String id){//类MyClass的构造方法
  name=id;
}
void randomWait(){//让线程处于等待状态
  try{
    Thread.currentThread().sleep((long)(3000*Math.random()));//注意:接口Runnable中没有方法sleep(),所以必须先调用Thread的类方法currentThread()来获取一个Thread的对象,然后再调用方法seleep()
  }
  catch(InterruptedException x){
    System.out.println("Interrupted!");
  }
}
public void tStart(){
  keepRunning=false;
}
public void run(){//与程序ThreadTest1.java类似
int i=0;
while(keepRunning) i++;//i代表循环次数
//输出结果
for(int j=0;j<=3;j++){
  randomWait();
  System.out.println("I am "+name+"—— I have run "+i+" times.");
  i++;
  }
  System.out.println(name+" is dead!");
}
}


  运行结果:(略)
  ThreadTest2创建thread1与thread2的方法(11~14行)与ThreadTest1不同,ThreadTest2直接创建了一个Thread的对象,并将Myclass的对象作为参数传给Thread的构造方法。任何实现了Runnable接口的类的对象都可以作Thread构造方法的参数。在main()方法中,其余部分程序ThreadTest2与ThreadTest1.java相同。
  ThreadTest2的MyClass类(31~59行)实现了接口Runnable,注意MyClass的构造方法实现了name这一类变量,实现run()时不需要调用Thread的方法getName(),但在实现randomWait()时要使用Thread.currentThread().Sleep()(39行),因为Runnable接口并未提供方法sleep(),因而实现时必须调用Thread的类方法currentThread()来调用sleep()。
  事实上,无论用继承Thread的方法或用实现接口Runnable的方法来实现多线程,在程序书写时区别不大,只需概念清楚略加注意便可。使用继承Thread类的方法比较简单易懂,实现方便。但如果你创建的Thread需要是某个其它类的子类时,使用继承Thread的方法就会出麻烦。比如,实现Applet时,每个applet必须是java.applet.Applet的子类,此时想要实现多线程,只有通过使用Runnable接口,当然,使用Runnable接口来实现线程,在书写时会比较麻烦,因为你将不得不多做一些工作才可调用Thread的方法。
  3.线程同步
  在使用多线程时,由于可以共享资源,有时就会发生冲突。举一个简单的例子,有两个线程thread1负责写,thread2负责读,当它们操作同一个对象时,会发现由于thread1与thread2是同时执行的,因此可能thread1修改了数据而thread2读出的仍为旧数据,此时用户将无法获得预期的结果。问题之所以产生主要是由于资源使用协调不当(不同步)造成的。以前,这个问题一般由操作系统解决,而Java提供了自己协调资源的方法。
  Java提供了同步方法和同步状态来协调资源。Java规定:被宣布为同步(使用Synchronized关键字)的方法,对象或类数据,在任何一个时刻只能被一个线程使用。通过这种方式使资源合理使用,达到线程同步的目的。
  我们将程序5.1的类MyThread中的方法run()改成如下所示(见程序片段5.3),并加入一个类SynchronizedShow实现同步,大家可以运行看到执行结果:每次执行Show()的只有一个线程。
  例5.3 ThreadTest3.java片段
  public void run(){
    int i=0;
    while(keepRunning) i++;//i代表循环次数
    //输出结果
    SynchronizedShow.show(getName(),i);
    SynchronizedShow.println(getName()+"is dead!");
  }
  class SychronizedShow{
    //方法show(String,int)被宣布为同步的方法,因此每次只有一个线程能调用这个方法
    public static synchronized void show(String,name,int i){
     int k;
     k=i;
     for(intj=0;j<=3;j++){
      MyThread t=(Mythread) Thread.currentThread();
      t.randomWait();
      System.out.println("I am"+name+"—— I have run"+k+" times.");
      k++;
     }
    }
  }
  运行结果(略)
  另外,利用Synchronized可以锁定对象。
  例如:Synchronized(某个对象A){
  //程序块
  }
在此程序块中,对于相同的对象A,在任何时候只可以有一个线程在此代码中执行,但对于不同的对象还是有很多个线程同时执行的。用同样的方法也可以协调类数据,例如:
  Synchroinzed(new欲锁定的类().getmethod()){
  //程序块
  }
方法getmethod()是用来获取类数据的,这样通过利用Synchronized这一关键字,我们可以自由协调对象实体的各种数据。
  除了简单使用Synchronized这一关键字外,Java有一套复杂的同步机制,其基本原理采用了C.A.R.Hoare提出的,并已被广泛使用的监视规则和条件变量规则。在Java中,所有的类与对象都和管程(monitor)联系在一起。当一个对象获得管程(monitor)时,它就可以执行同步方法,直至它让出管程(monitor),其它对象方可进入同步方法。当一个方法被完成时,它将自动让管理(monitor) ,另外执行某些方法例如wait(),suspend()等时也会让出管程(monitor)。
  读者可以试着将例5.1的main()方法改成以下形式(风险5.4)执行一下。人们会发现与前面利用Synchronized的结果类似,原因是thread1执行suspend()后被挂起直至resume()方被唤醒。因此用这种方法也可以实现同步。
  例5.4 Thread Test4.java的程序片断。
  public static void main(String args[])
   throws java.io.IOException{
   System.out.println("If want to show the result,press return");
   MyThread thread1=new MyThread("thread1");
   MyThread thread2=new MyThread("thread2");
   thread1.start();
   thread2.start();
   char ch;
   while((ch=(char)System.in.read())!='\n');
   thread1.tStart();
   //将线程thread1挂起
   thread1.suspend();
   thread2.tStart();
   while(thread2.isAlive());
   //线程thread2进入死亡状态后,释放线程thread1
   thread1.resume();
   while(thread1.isAlive());
   /*{
     you can do anything that you want to do here.
    }
   */
   System.out.println("The test is end.");
  }
  运行结果:(略)
  4.进一步学习
  上几节我们学习了定义Thread的两种方法,线程的同步的基本知识。在本节中我们将进一步给出有关线程的一些更详细的内容。
  (1)若干常用的方法
  首先,我们给出一些Thread类中最常用的方法供大学参考。
  ■currentThread()
  这是一个类方法,返回当前正在执行的线程。
  ■isAlive()
  判别一个线程是否仍然活着,包括这个线程正在执行或有机会被执行。返回一个布尔值。
  ■suspend()
  悬挂起某个线程,并使得这个线程只可被resume()方法激活。
  ■resume()
  与suspend()配合使用。唤醒线程。
  ■yeild()
  强迫线程交出执行权利供其它线程使用。
  ■stop()
  使线程进入死亡(dead)状态。
  这些方法是线程中最常被使用到的方法,若要详细了解更多的内容可以查阅Java的API。
  (2)线程优先级与调度
  由于我我们一般使用的计算机是单CPU的,所以在执行多线程程序时需进行线程调度。线程调度是由线程的优先级决定的。高优先级的线程总是先运行的。Java采用的是抢占式(preemptive)的调度方式,即当高优先级的线程进入可运行(runnable)状态时,会抢占低优先级的线程的位置,并开始执行。当同时有两个或两个以上的线程具有高优先级并进入可运行状态,Java的调度会自动在这些线程间交替调度执行。
  在Java中,Thread类中预定义了三个常量:
  MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY,
  一个线程的优先级应在MAX_PRIORITY与MIN_PRIORITY之间。NORM_PRIORITY是缺省的优先级值,一般是MIN_PRIORITY与MAX_PRIORITY的平均值。
  在Java中,Thread类提供了方法设置和获取优先级。
  setPriority(int)  用于设置线程的优先数
  setPriority()   用于获取线程的优先数
  (3)线程组(Thread Group)
  线程组是包括了许多线程的对象集。每个线程有自己特定的线程组。一个线程在创建时就属于某个线程组,直至其执行结束,此线程不可更改其所属的线程组。Thread类中提供了构造方法使创建线程时同时决定其线程组。Thread类总共提供了六种构造方法:
  Thread();
  Thread(String);
  Thread(Runnable);
  Thread(Runnable,String);
  Thread(ThreadGroup,String);
  Thread(ThreadGroup,Runnable,String);
  前四种缺省了线程组,表示所创建的线程属于main线程组,后两种则指定了所创建的线程的线程组。线程可以访问自己所在的线程组,但不能访问本线程组的父类。对线程组进行操作就是对线程组中的各个线程同时进行操作。
  线程组的构造方法:
  ThreadGroup(String groupName)
创建名为groupName的线程组,该线程组的父类为当前线程所在程组。
  ThreadGroup(ThreadGroup parent,String groupName)
创建名为groupName的线程组,该线程组的父类是parent。
  (4)wait()和notify()方法
  Java在类java.lang.Object中定义了wait()和notify()方法,调用它们也可以实现线程之间的同步。
  ■public final void wait(long millseconds) throws InterruptedException
  调用此方法时,被调对象进入等待状态,直到被唤醒或等待时间到。
  ■public final void notify()
  唤醒一个对象内处于等待状态的对象。
  ■public find void Allotify()
  唤醒一个对象内所有处于等待状态的对象。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
18#
 楼主| 发表于 2006-6-27 23:55 | 只看该作者
5.2 Debugger的使用
  至此我们已经书写了不少程序,大家可能发觉出错要调试很困难,其实java工具包中的Java debugger为用户提供了方便的调试机制。虽然jdb的界面不是很漂亮,但很有用,尤其对于调试多线程程序。
  Java的debugger需要jdb命令激活。如下执行:
  C:\>jdb
在执行之前,请用带-g参数的javac对程序进行编译,本节我们使用了前一章中check.java,第一步先重新编译方法如下:
  C:\synetjava\java\exmples>javac -g check.java
  用下面的方法可以进入jdb,可以直接在jdb后紧接要调试的类名,也可缺省,在进入jdb后利用load载入要调试的类。
  C:\MyDemo\dawn>jdb Check
  Initializing jdb...
  0xe8d370:class(Check)
在Check这一类名前的16进制数是Check类在Java运行时的标识。
  进入了jdb后,可以使用help来获取所需的使用信息:
  > help
  ** command list **
  run [class [args]]     -- start execution of application's main class

  threads [threadgroup]   -- list threads
  thread <thread id>     -- set default thread
  suspend [thread id(s)]   -- suspend threads (default: all)
  resume [thread id(s)]   -- resume threads (default: all)
  where [thread id] | all  -- dump a thread's stack
  wherei [thread id] | all -- dump a thread's stack, with pc info
  up [n frames]       -- move up a thread's stack
  down [n frames]      -- move down a thread's stack
  kill <thread> <expr>   -- kill a thread with the given exception object
  interrupt <thread>    -- interrupt a thread

  print <expr>       -- print value of expression
  dump <expr>        -- print all object information
  eval <expr>        -- evaluate expression (same as print)
  set <lvalue> = <expr>   -- assign new value to field/variable/array element
  locals           -- print all local variables in current stack frame

  classes          -- list currently known classes
  class <class id>     -- show details of named class
  methods <class id>    -- list a class's methods
  fields <class id>    -- list a class's fields

  threadgroups       -- list threadgroups
  threadgroup <name>    -- set current threadgroup

  stop in <class id>.<method>[(argument_type,...)]
               -- set a breakpoint in a method
  stop at <class id>:<line> -- set a breakpoint at a line
  clear <class id>.<method>[(argument_type,...)]
               -- clear a breakpoint in a method
  clear <class id>:<line> -- clear a breakpoint at a line
  clear          -- list breakpoints
  catch <class id>    -- break when specified exception thrown
  ignore <class id>    -- cancel 'catch' for the specified exception
  watch [access|all] <class id>.<field name>
               -- watch access/modifications to a field
  unwatch [access|all] <class id>.<field name>
               -- discontinue watching access/modifications to a field
  trace methods [thread] -- trace method entry and exit
  untrace methods [thread] -- stop tracing method entry and exit
  step           -- execute current line
  step up         -- execute until the current method returns to its cal
  ler
  stepi          -- execute current instruction
  next           -- step one line (step OVER calls)
  cont           -- continue execution from breakpoint

  list [line number|method] -- print source code
  use (or sourcepath) [source file path]
               -- display or change the source path
  exclude [class id ... | "none"]
              -- do not report step or method events for specified classes
  classpath -- print classpath info from target VM

  monitor <command>   -- execute command each time the program stops
  monitor        -- list monitors
  unmonitor <monitor#>  -- delete a monitor
  read <filename>    -- read and execute a command file

  lock <expr>      -- print lock info for an object
  threadlocks [thread id] -- print lock info for a thread

  disablegc <expr>    -- prevent garbage collection of an object
  enablegc <expr>     -- permit garbage collection of an object

  !!            -- repeat last command
  <n> <command>      -- repeat command n times
  help (or ?)       -- list commands
  version         -- print version information
  exit (or quit)      -- exit debugger

  <class id>: full class name with package qualifiers or a
  pattern with a leading or trailing wildcard ('*').
  <thread id>: thread number as reported in the 'threads' command
  <expr>: a Java(tm) Programming Language expression.
  Most common syntax is supported.

  Startup commands can be placed in either "jdb.ini" or ".jdbrc"
  in user.home or user.dir
  >
  利用命令stop in <class id>.<method>可以在方法中设置断点,用命令run运行方法。
  >stop in Check.main
  Breakpoint set Check.main
  >run Check
  running...
  main[1]
  Breakpoint hit: Check.main(Chech:18)
  main[1] list
  14   }
  15  }
  16  class Check{
  17   public static void main(String args[]]){
  18  =>  Son s=new Son();
  19     s.speak();
  20   }
  21   }
  main[1]
  =>所指的地方即为断点处,然后使用step命令进行步调执行,结果提示断点设置到了类son中用list显示,就可以清楚地看到断点所在位置。
  main[1]step
  main[1]
  Breakpoint hit:Son.<init>(Son:9)
  main[1]list
  5    void speak(String s){
  6      System.out.println("I like "+s+".";
  7    }
  8   }
  9   =>class Son extends Father{
  10   void speak(){
  11      System.out.println("My father sys:";
  12      super.speak();
  13      super.speak("hunting";
  用cont命令可以继续执行下去,本例十分简单,所以立即给出了运行结果。我们还可以试一试help中显示的其它命令,methods check显示了check类中定义的方法,memory显示了java运行时刻提供的内存等等,用户可以自己去试用一下。
  main[1]cont
  My father syas:main[1]
  I am Father.
  I like hunting.
  //显示check类中定义的方法
  main[1]methods Check
  void main(String[])
  void <init>()
  //显示java运行时刻提供的内存main[1] memory
  Free:295928,total:1777656
  //列出线程组
  main[1]threadgrouts
  1.(java.lang.ThreadGroup)0xe600b8 system
  2.(java.lang.ThreadGroup)0xe655e0 main
  3.(java.lang.ThreadGroup)0xe8ddd8 Check.main
  //列出指定线程组中的线程
  main[1]threads system
  Group system:
  1.(java.lang.Thread)0xe600d0  Frinalizer therad suspended
  2.(java.lang.Thread)0xe65570  Debugger agent running
  3.(sun.tools.debug.BreakpointHandler)0xe8b080 Breakpoint handler cond. waitin
  Group main:
  4.(java.lang.Thread)0xe600a8 main suspended
  Group Check.main:
  事实上,Jdb工具现在还存在不少缺陷正待改进,使用时有时会出现一些莫名其妙的错误,所以使用时请大家最好联网,便于查询。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
19#
 楼主| 发表于 2006-6-27 23:55 | 只看该作者
第六章 语言类库Java.lang(上)
  从这一章开始,我们将陆续介绍Java基本类库。在Java开发工具集JDK1.0.2版本中,包含8个包(package),分别包含了语言、输入输出、窗口工具、Applet、常用工具等文献的201个类,到了1.1.1版更增加到21个包,478个类。这些类是Java的开发者们向我们提供的软件资源。根据面向对象程序设计的代码重用原则,我们可以在自己的软件中恰当地使用这些类,从而避免从最底层的、我们不太熟悉的操作做起。
  在这一章中,我们将对语言类库作较详细的介绍。本章的第一节将讲述引用类库和方法的细节,这是为不熟悉面向对象编程的读者准备的。事实上它不过集中地重复了我们前面零散提到的概念。较有经验的读者可以略过这一节。

6.1 如何利用类库
  当你在机器上安排JDK时,可以看到一个压缩文件class.zip。这个文件缺省置于JDK安装目录的lib子目录下。我们无需对它解压缩,而且解开后浪费空间——但它包含着对我们编程工作的强有力支持,也即Java类库。
  让我们用java.lang来做例子,解释对类库中类的运用。
  首先必须对程序中出现的任何类注明来源,除非它是在该程序中被定义的。“注明来源”的目的是让编译器和解释器知道此类在哪里,以便引用。注明的方法有两种,其一是在程序文件的开头,用import语句:
  import <类名>;
  如:import java.lang.Class;
  若要引用的类较多,且在同一个package之内,则可以使用‘*’来做通配符。如:
  import java.lang.*;
  这种注明方法要注意两点。第一,类名之前的package名是必须的,否则类将找不到。例如未例中的java.lang.Class中,java.lang就是package名。对于没有注明package名的类,java将默认它属于一个缺省的package,这样路径就不对了。第二,‘*’通配符与我们常用的通配符含义不完全相同。不允许用
  import java.lang.T*;
之类的语句。*只能用来代替完整的类名。
  注明来源的方法之二是在用到该类的地方直接把类名连同package名加上去,如
  public class MyThread extends java.lang.Thread{
   ...
  }
这里的MyThread类是Thread类的子类,而Thread类是java.lang包中的类。
  仅需用以上两种方法之一,你就完成了利用某个类的基础工作。应当提醒的是,即使是程序中出现的异常,也要注明来源。例如:
  import java.io.IOException;
注明了输入输出异常的来源,这样程序中才能处理这种异常。
  当编译时出现“class xx not found in ...”之类的错误时,请检查这个类是否被注明了来源;如果是,再检查是否拼写类名时拼错了,尤其要注意字母的大小写。这两条简单的检查可以解决大部分这类错误。
  其次,正确地引用类的方法。
  这个工作更为简单。当你要使用一个方法时,考虑一下它的参数、返回值以及是不是static方法(类方法)。如果它是类方法,那么你可以用<类名>.<方法名>(<参数表>来引用它,如
  Thread.sleep(200);//sleep是类方法
  否则,你需先得到一个该类的对象,再用<对象名>.<方法名>(<参数表>来引用它,如
  StringBuffer sbuf3=new StringBuffer(10);
  subf3.setLength(9);
如果要接收返回值,就用相应类型的变量去接收它。如
  StringBuffer sbuf2=new StringBuffer("This is the initial value";
  sbuf2=sbuf2.append(" ".insert(0," ";
其中第一行创建了一个对象,第二行引用了它的append()方法。这样得到的返回值仍是一个StringBuffer类的对象,我们又对它运用insert()方法,最后得到的结果用sbuf2来接收。
  这里需要解释一下如何得到一个对象。通常的作法是利用构造函数,用new来实现,但有些类是没有构造函数的,需用特殊的途径得到对象,如我们将在本章中介绍的Runtime类;有些类则根本就不能得到其对象,如Math类。因此处理时要具体问题具体分析。
  类库中还有相当一部分成员是异常。对它们的处理在“异常”一章中提到过,要么在方法的开头“抛出”(throws),要么用try-catch语句捕捉。这些读者已经熟悉,就不再举例了。
  在运用类和方法时,拼写错是最常见的错误之一,尤其是字母的大小写问题。其实类和方法的命名都是有规律的,一般可以望文生义地推理出其功能。类名的第一个字母总是大写,方法的第一个字母总是小写。名字由多个单词组成时,单词间无分隔符,但除第一个单词外每个单词的开头字母总是大写。记住这一点,或许会给你省不少麻烦呢。
  java.lang包中的类较多。常用的类继承关系如图6.1(省略了接口和Exception和Error的众多子类)。
  ┌──────┐
  │Object│
  └──┬───┘        ┌──────┐
     ├────────────┤Number│
     │            └─┬────┘
     │              ├─Number
     ├─Boolean      ├─Byte
     ├─Class        ├─Float
     ├─Character    ├─Integer
     ├─ClassLoader  ├─Long
     ├─String       └─Short
     ├─Runtime
     ├─StringBuffer
     ├─System
     ├─Void
     ├─Compiler
     ├─Math
     ├─SecurityManger
     ├─Process
     │          ┌─────────┐
     ├──────────┤Throwable│
     │          └───┬─────┘
     ├─Thread       ├Error
     └─ThreadGroup  └Exception
     图6.1 java.lang包中常用类继承关系图
  从图6.1可以看出,java.lang包中的常用类可以大致分为几种。有把各个基本类型进行封装而得到的“包装类”,如Byte,Char,Integer,Double,Boolean等;有与线程有关的类,如Thread,ThreadGroup;有与Java环境有关的类,如ClassLoader,SecurityManager,Compiler;有错误类和异常类,除了图上已有的Error和Exception之外,还有这两个类的许多派生类;另外还有运行时系统类Runtime,进程类Process,系统类System等等。
  这些类的继承关系较为简单。从图中可见,大多数常用类是Object类的子类,Number类是各种数类的父类,而Error和Exception都是Throwable的子类。Error又派生出许多类,表示各种错误,由Exception则派生出多个异常类(图中未标出)。
  本章后面的小节将分别介绍java.lang类库中的类。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
20#
 楼主| 发表于 2006-6-27 23:55 | 只看该作者
6.2 关于字符串
  在这一节中我们把注意力集中于两个类:String和StringBuffer。
  对字符串我们并不陌生。但Java对字符串的处理有些异乎寻常,使用了两个类。String类封装了有关字符串的操作。这里的字符串是常量,创建后就不可改变。StringBuffer类则是动态可变的字符串缓冲,它提供了一系列方法,把不同类型(如整型、布尔型等)数据插入缓冲或追加在缓冲的末尾。让我们通过一个例子来看二者的区别和使用(例6.1)。
  例6.1 StringDemo.java。
  1: import java.lang.*;//原文如此,实际上不应该有
  2: import java.io.*;
  3:
  4: public class StringDemo{
  5:  public static void main(String args[]) throws IOException{
      //String对象的哈希值
  6:   String s1=new String("This is the initial value.";
  7:   System.out.println("s1's value:" + s1 + " HashCode:" + s1.hashCode());
  8:   String s2="This is the initial value.";
  9:   System.out.println("s2's value:" + s2 + " HashCode:" + s2.hashCode());
  10:  s2=" "+s2+"";
  11:  System.out.println("s2's value:"+s2+" HashCode:"+s2.hashCode());
      //StringBuffer对象的哈希值
  12:  StringBuffer sbuf1=new StringBuffer(s1);
  13:  System.out.println("sbuf1's value:"+sbuf1+" HashCode:"
  14:  +sbuf1.hashCode());
  15:  StringBuffer sbuf2=new StringBuffer(s1);
  16:  System.out.println("sbuf2's value:"+sbuf2+" HashCode:"
  17:  +sbuf2.hashCode());
  18:  sbuf2=sbuf2.append(" ".insert(0,"";
  19:  System.out.println("sbuf2's value:"+sbuf2+" HashCode:"
  20:  +sbuf2.hashCode());
      //String 类的方法演示
      //改变大小写的方法
  21:  System.out.println("UpperCase:"+s1.toUpperCase()+"\nLowerCase:"
  22:  +s1.toLowerCase());
      //替换字符
  23:  String s3=s1.replace('i','I');
  24:  System.out.println("Replace 'i' to 'I',we got s3:"+s3);
      //判等
  25:  if(s3.equals(s1))System.out.println("s3 equals s1";
  26:  else System.out.println("s3 doesn't equals s1";
      //忽略大小写判等
  27:  if(s3.equalsIgnoreCase(s1))
  28:  System.out.println("s3 equalsIgnoreCase s1";
  29:  else System.out.println("s3 doesn't equalsIgnoreCase s1";
      //去除首尾空格
  30:  System.out.println("s2after trim)"+s2.trim());
      //求特定字符在串中的位置取给定位置的字符
  31:  System.out.println(s1.charAt(s1.indexOf('h')));
      //求子串
  32:  System.out.println(s1.substring(s1.indexOf("is",0),
  33:  s1.lastIndexOf("is"));
      //转化成字符数组
  34:  char ch[]=s1.toCharArray();
      //从字符数组的一段得到字符串
  35:  System.out.println(String.valueOf(ch,3,5));
  36:  double d=123.456;
      //从双精度数得字符串
  37:  System.out.println(String.valueOf(d));
  38:  boolean b1=true;
  39:  int i=100;
  40:  long l=200000;
      //StringBuffer的方法演示
  41:  StringBuffer sbuf3=new StringBuffer(10);
      //插入和追加
  42:  System.out.println("sbuf3:"+sbuf3.append(b1).insert(2,i).insert(5,l));
  43:  try{
      //设置缓冲区长度
  44:   sbuf3.setLength(9);
  45:   System.out.println("sbuf3's value(after setLength):"+sbuf3);
  46:   }catch(StringIndexOutOfBoundsException ex){
  47:   System.out.println("String index out of bounds.";
  48:  }
  49: }
  50:}
  这个例子结果运行如下:
  C:\bookDemo\ch6>javac StringDemo.java
  C:\bookDemo\ch6>java StringDemo
  E:\Inetpub\wwwroot\ecapital\java\tutorial\java01>java StringDemo
  s1's value:This is the initial value. HashCode:769637276
  s2's value:This is the initial value. HashCode:769637276
  s2's value: This is the initial value. HashCode:1476514748
  sbuf1's value:This is the initial value. HashCode:2438296
  sbuf2's value:This is the initial value. HashCode:2092911
  sbuf2's value:This is the initial value. HashCode:2092911
  UpperCase:THIS IS THE INITIAL VALUE.
  LowerCase:this is the initial value.
  Replace 'i' to 'I',we got s3:ThIs Is the InItIal value.
  s3 doesn't equals s1
  s3 equalsIgnoreCase s1
  s2after trim)This is the initial value.
  h
  is
  s is
  123.456
  sbuf3:tr100200000ue
  sbuf3's value(after setLength):tr1002000
  这个例子可能有些琐碎,但其中包含了关于这两个类的许多要点。先看第一部分(第1至20行)。
  便子的第一部分着重显示String与StringBuffer在静态、动态方面的不同。hashcode()方法重写(String类)/继承(StringBuffer类)了Object类中的同名方法,作用是求出对应字符串/字符串缓冲类对象的哈希值。哈希希值与对象一一对应,可以唯一地标识对象。对照执行结果我们发现,两个相同的字符串的哈希值是相同的,而两个StringBuffer对象,尽管用同样的字符串为参数创建,哈希值却不同。原来,Java把String对象当作常量来处理,两个串常量既然值相同,就认作是一个。而StringBuffer则不同,它就像变量一样,尽管两个对象目前值相同却必须分开存储,以备发生改变。
  读者看到第10行
  s2=""+s2+"";
一句或许会有疑问:既然s2是String对象,它又不可改变,这里值怎么会变了呢?原因在于,提供静态字符串后,若不允许对它做任何操作,这个类本身的意义就不大了。如何解决这个问题呢?编译器做了这样的幕持球工作:它将String对象转化为StringBuffer对象,实施完操作后再变回String对象。比如,上面提到的语句可解释为:
  s2=new StringBuffer("".append(s2).append("").toString();
  所以,虽然名字相同,此s2却不是原先的s2了,它的哈希值已发性了改变。而我们对StringBuffer对象的悠却没有改变其哈希值。这充分表明了二者“静态”和“动态”的区别。
  例子和第一段末尾(行18)用了append()和insert()方法。这两个方法是StringBuffer的主要功能所在。这两个方法有许多重载形式,比如,append()方法可在字符缓冲的末尾追加对象(Object类的对象)、字符串(String类对象)、字符数组(char[])字符数组的一部分(用两个int 值指明始末位置)、布尔值、字符、整数、浮点数、双精度浮点数等。insert()则把许多种对象插入字符缓冲之中。例子第三段第42行中给出了一些例子,演示如何将布尔值、整数、长整数放入缓冲区。
  例子第二段中演示了String类的部分方法,我们无法也不必要列出该类的所有方法——读者可以轻易地从网上得到Java类库的文档来查阅——只将例子中用到的作一解释,以期起到指导读者使用的作用。
  toUpperCase()和toLowerCase()是进行大/小写转换的方法。replace()方法将字符串中的某字符(第一参数)替换成另一字符(第二个参数)。它将对串中所有该字符的出现都进行替换。
  equals()方法是比较两个字符串是否相等的,也即比较每个字符是否对应相等。
  equalsIgnoreCase()也是判等方法,但它忽略字符大小写民的不同。trim()方法去年字符串开头和结尾的空白字符。
  indexOf()方法求出某个字符在字符串中第一次出现的位置,它的参数是int型的,返值也是整型。它还有一个重载形式是以两上int型变量为参数,这时,第一个int指待查字符,第二个int指起始查找位置。如语句
  indexOf('h',3);
指从第3个字符起查找'h'字符第一次出现的位置。如果等查找字符在串中并不存在,将返回-1。此外,indexOf()还可以用来查找某一子串在字符串中的位置,与前面两种重载形式相对应,也是两种形式,不同在于前面的待查找字符换成待查找的String对象即可。
  indexOf()方法用来寻找给定字符(串)在字符串中第一次出现的位置,对应地,lastIndexOf()方法则寻找给定字符(串)中字符串中最后一次出现的位置。它也有四种重载形式。
  charAt()方法用来取字符串中给定位置的字符,因此,参数为整型,返值为字符型。注意,用于指明位置的int值必须大于等于零,小于字符串长。否则,将出现StringIndexOutOfBoundsException,即字符串下标越界异常(由于它属于运行时异常,程序可以不处理它)。
  例子的第三段(41至50行)演示了StringBuffer的方法。除了前面提过的append()和insert()方法之外,还用了一个setLength()方法,该方法强制限定字符缓冲的大小。若设小了,会发生字符的丢失(我们的例子中有意制造了这种情况),设大了,多出来的字符会置为零值。该方法也可能出现StingIndexOutOfBoundsException。
  最后附加一点说明。上面的例子对于JDK1.0.2与JDK1.1.1版本中为20527408和20527520。
  以上例子覆盖了这两个类的常用方法。我们没有演示的,读者也可举一反三。你将发现使用这两个类将为串操作带来莫大的方便。

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表