Decode360's Blog

业精于勤而荒于嬉 QQ:150355677 MSN:decode360@hotmail.com

  BlogJava :: 首页 :: 新随笔 :: 联系 ::  :: 管理 ::
  302 随笔 :: 26 文章 :: 82 评论 :: 0 Trackbacks
 
类和对象
 
 
 
 
    Java语言是一种面向对象的程序设计语言,类和对象是面向对象程序设计的基本概念。类是相似对象中共同属性和方法的集合体。对象是类的实例。包是Java组织和管理类的一种方法。类的封装性是面向对象程序设计的一个重要特点。
 
 
1、面向对象程序设计
 
1.面向对象程序设计的基本概念
    Java语言是一种面向对象的程序设计语言。面向对象程序设计(Object Oriented Programming,简称OOP)是一种集问题分析方法、软件设计方法和人类的思维方法于一体的、贯穿软件系统分析、设计和实现整个过程的程序设计方法。面向对象程序设计方法的出发点和追求的基本目标,是使我们分析、设计和实现一个软件系统的方法尽可能接近我们认识一个事物的方法。面向对象程序设计的基本思想是:对软件系统要模拟的客观实体以接近人类思维的方式进行自然分割,然后对客观实体进行结构模拟和功能模拟,从而使设计出的软件尽可能直接地描述客观实体,从而构造出模块化的、可重用的、维护方便的软件。
    在现实世界中,客观实体有两大类:
    (1)我们身边存在的一切有形事物和抽象概念都是客观实体。有形事物的例子如一个教师、一件衣服、一本书、一个饭店、一座楼、一个学校等;抽象概念的例子如学校校规、企业规定等;
     (2)我们身边发生的一切事件都是客观实体,例如一场足球比赛、一次流感侵袭、一次到医院的就诊过程、一次到图书馆的借书过程,等等。
    不同的客观实体具有各自不同的特征和功能。例如,饭店具有饭店的特征和功能,学校具有学校的特征和功能;又例如,就诊过程具有就诊过程的特征和功能,借书过程具有借书过程的特征和功能。
    现实世界中的一切客观实体都具有如下特征:
    •有一个名字用来惟一地标识该客观实体;
    •有一组属性用来描述其特征;
    •有一组方法用来实现其功能。

    例如,作为书店客观实体,每个书店有不同的点名、负责人等属性(特征),书店的入库、出库、图书上架等是其基本的方法(功能)。
    面向对象程序设计方法下的软件系统设计方法,首先是把问题中涉及的客观实体分割出来,并设计成称为类的可重复使用的模块,然后定义出一个个实例化的类(称为对象),再按照问题的要求,设计出用各个对象的操作完成的软件系统。
 
2.
    在现实世界中,类这个术语是对一组相似客观实体的抽象描述。例如,有A书店、B书店、C书店等。而书店类则是对书店这类客观实体所应具有的共同属性和方法的抽象描述。即书店类不是具体的描述,而是抽象的描述。
    在面向对象方法中,具体的客观实体称为对象,类是对具有相同属性和相同方法的一组相似对象的抽象,或者说,类所包含的属性和方法描述了一组对象的共同的属性和方法。
    面向对象方法中软件设计的主体是类。类是相同属性和方法的封装体,因此类具有封装性;子类可以在继承父类所有属性和方法的基础上,再增加自己特有的属性和方法,因此类具有继承性;在一个类层次中,定义为根类的对象可被赋值为其任何子类的对象,并根据子类对象的不同而调用不同的方法,因此类具有多态性。类的这种封装性、多态性和继承性,是面向对象程序设计的三个最重要的特点。
 
3.对象
    类是具有相同属性和方法的一组相似对象的抽象描述,但在现实世界中,抽象描述的类是并不具体存在的,例如,现实世界中只存在具体的A书店、B书店、C书店,并不存在抽象的书店。我们把按照类这个模板所定义的一个个具体的对象称作类的实例,或称作对象。
    首先,一个具体对象必须具有具体的属性值,如A书店对象就必须具有如下属性:书点名为A书店,负责人为张三等。其次,任何对象都具有相应类所规定的所有方法。
 
 
2、

    面向对象方法中,软件设计的最主要部分是设计类。类的设计可以划分为类声明和类主体设计两部分。
 
1、类声明
 
1.类声明的格式
    类声明的格式如下:
    [<修饰符>] class<类名>[extends<父类名>] [implements<接口名表>]
    {
      类主体
    }

    其中,class是定义类的关键字,<类名>是所定义的类的名字,<父类名>是已经定义过的类名,<接口名表>是已经定义过的若干个接口名,当接口名多于一个时,用逗号分隔开。方括号表示该项是可选项。本章只讨论基本的类设计方法,含有父类和接口的类将在下一章讨论。
 
2.类的修饰符
   
类声明的<修饰符>分为访问控制符类型说明符两部分,分别用来说明该类的访问权限以及该类是否为抽象类或最终类。
    ( 1)访问控制符public和默认
    当访问控制符为public时,表示该类被定义为公共类。公共类表示该类能被任何类访问。由于类都放于某个包中,包中的类互相能访问,而不在一个包中的类互相不能直接访问。如果要在一个包中访问另一个包中的类,就必须用import语句导入所需要的类到该包中,但Java语言规定,被导入的类必须是用public修饰的类。
    当没有访问控制符public时,即是默认类(或称缺省类)。默认类表示该类只能被同一个包中的类访问,而不能被其他包中的类访问。Java语言规定,一个Java文件中可以有很多类,但最多只能有一个公共类,其他都必须定义为默认类。
    例如: public  class  Teacher就声明了一个公共类Teacher,该类可以通过import语句导入到其他包的类中,并能被其他所有的类访问。又例如,class Student就声明了一个默认类Student,该类只能被同一个包中的其他类访问。
    ( 2)类型说明符abstract和final
    当类型说明符为abstract时,表示该类为抽象类,抽象类不能用来定义对象,抽象类通常设计成一些具有类似成员变量和方法的子类的父类。
    当类型说明符为final时,表示该类为最终类,最终类不能用来再派生子类。
    访问控制符和类型说明符一起使用时,访问控制符在前,类型说明符在后。例如,public abstract class Teacher就声明了一个公共抽象类Teacher。
 
 
2、类主体设计

   
在上节讨论面向对象程序设计时曾说过,类由成员变量和成员方法组成。因此,类主体设计包括类的成员变量设计和类的成员方法设计两部分。由于Java语言中的所有方法必定属于某个类,即方法一定是成员方法,所以成员方法可简称为方法。我们首先给出一个简单的日期类设计,然后分别讨论类成员变量和类方法的设计。

【例3.1】设计一个日期类。要求方法应包括设置日期、显示日期和判断是否是闰年。
类设计如下:
public class Date                   //类声明
{
  //以下为类成员变量声明
  private int year;                   //成员变量,表示年
  private int month;                  //成员变量,表示月
  private int day;                    //成员变量,表示日
    
  //以下为类方法声明和实现
  public void SetDate(int y,int m,int d)     //设置日期值
    {                                  
      year = y;
      month = m;
      day = d;
    } 
    
  public void Print()                        //输出日期值
    {
      System.out.println("date is "+year+'-'+month+'-'+day);
    } 
        
  public boolean IsLeapYear()                //判断是否为闰年
    {                                  
      return (year%400==0) | (year%100!=0) & (year%4==0);
    }
}

1.声明成员变量
    声明一个成员变量就是声明该成员变量的名字及其所属的数据类型,同时指定其他一些附加特性。声明成员变量的格式为:
    [<修饰符>] [static] [final] [transient] <变量类型>  <变量名>;
    其中,<修饰符>有private、public和protected三种。当不加任何修饰符时,定义为默认修饰符。
    private修饰符表示该成员变量只能被该类本身访问,任何其他类都不能访问该成员变量。
    protected修饰符表示该成员变量除可以被该类本身和同一个包的类访问外,还可以被它的子类(包括同一个包中的子类和不同包中的子类)访问。
    public修饰符表示该成员变量可以被所有类访问。
    不加任何访问权限限定的成员变量属于默认访问权限。默认访问权限表示该成员变量只能被该类本身和同一个包的类访问。
    上述修饰符实现了类中成员变量在一定范围内的信息隐藏。这既符合程序设计中隐藏内部信息处理细节的原则,也有利于数据的安全性。
    static指明该成员变量是一个类成员变量
    final指明该成员变量是常量
    transient指明该成员变量是临时变量。 transient很少使用。
    类成员变量是一个类的所有对象共同拥有的成员变量。关于类成员变量的用途和使用方法我们将通过后面的例子说明。
    因此,例3.1中的成员变量year、month和day都是int类型的private成员变量,即这三个成员变量只能被该类本身访问,任何其他类都不能访问该成员变量。
 
2.声明方法
    声明成员方法的格式为:
    [<修饰符>] [static] <返回值类型> <方法名>  ([<参数列表>]) 
    {
      <方法体>
    }

    其中,<修饰符>和成员变量的修饰符一样,有private、public和protected三种,另外,还有默认。
    各个修饰符的含义也和成员变量修饰符的含义相同。static指明该方法是一个类方法。关于类方法的用途和使用方法我们将通过后面的例子说明。
    方法声明中必须给出方法名和方法的返回值类型,如果没有返回值,用关键字void标记。方法名后的一对圆括号是必须的,即使参数列表为空,也要加一对空括号。
    例如:
    public void SetDate(int y,int m,int d)
    上述语句声明了方法名为SetDate的public方法,其返回值为空,参数有三个,分别为y、m和d,这三个参数的数据类型均为int。
 
3.方法体
    方法体是方法的具体实现。方法体的设计即是第2章讨论的变量定义、赋值语句、if语句、for语句等根据方法体设计要求的综合应用。例如,
    public void SetDate(int y,int m,int d)     //设置日期值
    {                                  
      year = y;        //给成员变量year赋值y
     month = m;       //给成员变量month赋值m
     day = d;         //给成员变量day赋值d
    }
 
4.成员变量和变量
   
初学者经常会混淆成员变量和变量,一个最简单的区别方法是:定义在类中的都是成员变量,定义在方法内的都是变量。另外,还有定义在方法参数中的虚参变量,如例3.1 SetDate()中的y、m和 d。关于方法中定义的变量的例子见后面的设计举例。
    成员变量和变量的类型既可以是基本数据类型(如int、long等),也可以是已定义的类。
 
3、构造方法

    在类的方法中,有一种特殊的方法专门用来进行对象的初始化,这个专门用来进行对象初始化的方法称为构造方法。构造方法也称作构造函数。一般来说,一个类中至少要有一个构造方法。
    构造方法在语法上等同于其他方法。因此构造方法的设计方法和前面说的其他方法的设计方法类同。但构造方法的名字必须与其类名完全相同,并且没有返回值,甚至连表示空类型(void)的返回值都没有。构造方法一般应定义为public。
    构造方法用来在对象创建时为对象的成员变量进行初始化赋值。其实现过程是:在创建对象时,将调用相应类中的构造方法为对象的成员变量进行初始化赋值。
    例如,我们可以为上述日期类设计一个如下的构造方法:
    public Date(int y, int m, int d)          //构造方法
    {
      year = y;
      month = m;
      day = d;
    }

 
 
3、对象
    类是一类相似对象的抽象描述,一个软件系统是对具体问题的客观事物进行模拟或描述的,因此需要具体的描述。对象是类的实例化,对象就是软件系统中对具体问题的客观事物进行的具体模拟或具体描述。
 
1、main方法
 
    类是作为许多相似对象的抽象描述设计的,如果要使用这个类时,就必须创建这个类的对象。那么,对象创建应该是在同一个类中呢?还是应该在另一个类中呢?答案是两者都可以,但最好是在另一个类中。这样没有对象定义的纯粹的类设计部分就可以单独保存在一个文件中,就不会影响该类的重复使用。
    Java语言规定,一个类对应一个.class文件,一个程序文件中可以包含一个或一个以上的类,但其中只允许一个类被定义成public类型。类中可以没有main方法。但是要运行的类中必须有main方法。程序就是从main方法开始执行的。
    下面的例子是把对象创建在同一个类中的main方法中。
【例3.2】打印某个日期,并判断该年是否是闰年。
public class Date                     //类声明
{
  private int year;                   //成员变量,表示年
  private int month;                  //成员变量,表示月
  private int day;                    //成员变量,表示日
  public Date(int y, int m, int d)          //构造方法
  {
    year = y;
    month = m;
    day = d;
   }
    
  //以下为其他类方法
  public void SetDate(int y, int m, int d)     //设置日期值
  { 
    year = y;
    month = m;
    day = d;
  } 
    
  public void Print()                          //输出日期值
  {
    System.out.println("date is "+year+'-'+month+'-'+day);
  } 
        
  public boolean IsLeapYear()                 //判断是否闰年
  {                                  
    return (year%400==0) | (year%100!=0) & (year%4==0);
  }
 
  public static void main(String args[])   //main()方法
  {
    Date a = new Date(2004, 8, 5) ;        //创建对象
    a.Print();
    if(a.IsLeapYear())
      System.out.println(a.year + " 是闰年");
    else
      System.out.println(a.year + " 不是闰年");
  }
}

main方法必须放在类中,且格式必须为:public static void main(String args[])

2、对象的创建和初始化
 
    在例3.2中,语句Date a = new Date(2004, 8, 5); 
    实现了定义对象a和为对象分配内存空间,并初始化对象a的成员变量数值为:
    year = 2004; month = 8; day = 5;
    上述方法是把定义对象和创建对象这两个步骤结合在了一起,并同时进行了对象的初始化赋值。这是最简单、也是最经常使用的对象定义、创建和初始化方法。
    对象的定义和创建过程也可以分开进行,即首先定义对象,然后为对象分配内存空间,并可同时进行初始化赋值。
 
1.定义对象
    定义对象的语句格式为:
    <类名> <对象名>;
    例如下面语句就定义了一个Date类的对象a:
    Date a; 
    对象和数组一样,也是引用类型。即对象定义后,系统将给对象标识符分配一个内存单元,用于存放实际对象在内存中的存放位置。由于在对象定义时,对象在内存中的实际存放位置还没有给出,所以,此时该对象名的值为空(null)。上述语句后对象a的当前状态如下图的(a)所示:
    java2-1.gif
 
2.为对象分配内存空间和进行初始化赋值
    和为数组分配内存空间一样,为对象分配内存空间也使用new运算符。为对象分配内存空间的语句格式为:
    <对象名>=  new <类名> ([<参数列表>]);
    其中,new运算符申请了对象所需的内存空间,new运算符返回所申请的内存空间的首地址。系统将根据<类名><参数列表>调用相应的构造方法为对象进行初始化赋值(即把参数值存入相应的内存单元中)。赋值语句把new运算符分配的连续地址的首地址赋给了对象名。正因为构造方法名和类名完全相同,所以这里的类名既用来作为new运算符的参数向系统申请对象所需的内存空间,又作为构造方法名为对象进行初始化赋值。例如:
    a = new Date(2004, 8, 5) ; 
    就先向系统申请了Date类对象所需的内存空间(其首地址由对象名a指示),又用参数2004、8和5调用了构造方法Date(2004, 8, 5),为对象a进行了初始化赋值。上述语句后对象a的当前状态如上图中的(b)所示。

    程序设计时最经常使用的方法,是在定义对象的同时为对象分配内存空间和进行初始化赋值。例如,
    Date a = new Date(2004, 8, 5) ;
 
3.对象的使用
    一旦定义并创建了对象(创建对象是指为对象分配了内存单元),就可以在程序中使用对象。
    对象的使用主要有三种情况,分别是:使用对象的成员变量或方法,对象间赋值和把对象作为方法的参数
    (1) 使用对象的成员变量或方法
    一旦定义并创建了对象,就可以使用对象的成员变量或方法。
    例如,例3.2的main()方法中a.year就使用了对象a的成员变量year。
    又例如,例3.2的main()方法中a.Print()就使用了对象a的成员方法Print()。
    另外,还可以修改对象的成员变量的数值,例如,如果在例3.2的main()方法中增加如下语句,就把对象a的成员变量year的数值修改为2005,
    a.year = 2005;
    ( 2)对象间赋值
    对象可以像变量一样赋值。例如,如果在例3.2的main()方法中增加如下语句,则对象b的值和对象a的值相同。
    Date b;
    b = a;
    但和变量赋值不一样的是,对象赋值并没有真正把一个对象的数值赋给另一个对象,而是让另一个对象名(如对象名b)存储的对象的首地址和这个对象名(如对象名a)存储的对象的首地址相同。即对象的赋值是对象的首地址的赋值。例如,上述语句就把对象a的首地址值赋给了对象b,因此,对象b和对象a表示的是同一个对象。
    仔细分析上图所示的对象的存储结构,就可以理解对象间赋值的实现方法。
    ( 3)把对象作为方法的参数
    对象也可以像变量一样,作为方法的参数使用。但系统实现两者的方法不同,变量作为方法的实参时,系统把实参的数值复制给虚参;而对象作为方法的实参时,系统是把实参对象名(该对象必须已创建)指示的对象的首地址赋给了虚参对象名。其实现方法实际上和对象间赋值的实现方法相同。
 
4.垃圾对象的回收
    从上面的讨论可知,对象和变量在很多方面有些类同,例如,对象和变量都需要分配内存空间。但是,变量的内存空间是系统在变量定义时自动分配的,当变量超出作用范围时,系统将自动回收该变量的内存空间。
    而对象的内存空间是在用户需要时,用new运算符创建的。对象也有作用范围,我们把超出作用范围的对象(或称不再被使用的对象)称作垃圾对象。那么,谁来负责这些垃圾对象的回收呢?答案是,在Java中,收集和释放内存是一个叫做自动垃圾回收线程的责任。线程的概念将在第10章讨论,这里可以把自动垃圾回收线程理解为一个系统自己开启、并与用户程序并行运行的一个服务程序。自动垃圾回收线程在系统空闲时自动运行,这个线程监视用户程序中所有对象的有效作用范围,当某个对象超出其作用范围时,该线程就对这样的对象做上垃圾对象标识,并在适当的时候一次性回收这些垃圾对象。
    所以,用户程序只需考虑为对象分配内存空间,不需考虑垃圾对象内存空间的回收
 
5.实例成员变量与类成员变量
    类有两种不同类型的成员变量:实例成员变量类成员变量。类成员变量也称作静态成员变量。
 
    (1) 实例成员变量
    类定义中没用关键字static修饰的成员变量就是实例成员变量,不同对象的实例成员变量其值不相同。
    例3.2中类Date的成员变量定义语句:
    private int year;
    private int month;
    private int day;
    就定义了三个private类型的实例成员变量year、month和day。
    例如,若有如下对象定义:
    Date a = new Date(2003, 1, 1) ;
    Date b = new Date(2004, 5, 10) ;
    则对象a和对象b的实例成员变量数值就不同。如a.year的数值为2003,而b.year的数值为2004。
 
    (2) 类成员变量
    用关键字static修饰的成员变量称为类成员变量,一个类的所有对象共享该类的类成员变量。类成员变量可以用来保存和类相关的信息,或用来在一个类的对象间交流信息。
【例3.3】用类成员变量表示当前该类共有多少个对象被定义。
public class ObjectNumber     //类声明
{
  private static int number = 0;   //类成员变量
    
  public ObjectNumber()     //构造方法
  {                                  
    number++;
  } 
    
  public void Print()      //对象个数输出
  {
    System.out.println(number);
  } 
        
  public static void main(String args[])
  {
    ObjectNumber a = new ObjectNumber() ;   //创建对象
    System.out.print("当前对象个数为:");
    a.Print();         //对象个数输出
    ObjectNumber b = new ObjectNumber() ;   //创建对象  
    System.out.print("当前对象个数为:");
    b.Print();         //对象个数输出
  }
}
输出结果为:
当前对象个数为:1
当前对象个数为:2

 
6.实例方法与类方法
    类有两种不同类型的方法:实例方法与类方法。类方法也称作静态方法。
    ( 1)实例方法
    没用关键字static修饰的方法就是实例方法。实例方法只能通过对象来调用。实例方法既可以访问类成员变量,也可以访问类变量。例3.2中类Date的SetDate()方法、 Print()方法和IsLeapYear()方法都是实例方法。这些实例方法体中都访问了实例成员变量。
    (2) 类方法
    用关键字static修饰的方法称为类方法。类方法通过类名来调用(也可以通过对象来调用)。类方法只能访问类变量,不能访问实例变量。类方法主要用来处理和整个类相关的数据。虽然类方法既可以用类名来调用,也可以用对象来调用,但用类名调用类方法程序的可读性更好。

【例3.4】用类方法处理当前该类共有多少个对象被定义。
程序设计如下:
public class ObjectNumber2         //类声明
{
  private static int number = 0;   //类成员变量
    
  public ObjectNumber2()           //构造方法
  {
    number++;
  } 
    
  public static void Print()       //类方法
  {
    System.out.println("当前对象个数为:" + number);
  } 
        
  public static void main(String args[])
  {
    ObjectNumber2 a = new ObjectNumber2() ;  //创建对象
    ObjectNumber2.Print();         //对象个数输出
//  a.Print();//可以,但不提倡
    ObjectNumber2 b = new ObjectNumber2() ;  //创建对象  
    ObjectNumber2.Print();         //对象个数输出
//  b.Print();//可以,但不提倡
    }
}
输出结果为:
当前对象个数为:1
当前对象个数为:2

 
7.方法的重写
     类的各种方法(包括构造方法和其他方法)都允许重写(也称做重载)。所谓方法重写(overloading),是指一个方法名定义了多个方法实现。方法重写时要求,不同的方法,其参数类型或参数个数要有所不同。若类的某个方法被重写,该类的对象在访问该方法时,以对象调用该方法的参数个数和参数类型与类的同名方法进行匹配,对象调用该方法的参数个数和参数类型与类定义中哪个方法的参数个数和参数类型完全一样,则调用类中的哪个方法。
【例3.5】方法重写示例。
public class Date2                    //类声明
{
  private int year;                   //成员变量,表示年
  private int month;                  //成员变量,表示月
  private int day;                    //成员变量,表示日
     
  public Date2(int y, int m, int d)   //构造方法
  {
    year = y;
    month = m;
    day   = d;
  } 
    
  public Date2()                      //构造方法
  {
    year = 2004;
    month = 8;
    day   = 10;
  } 
    
  public void Print()                 //输出日期值
  {
    System.out.println("date is "+year+'-'+month+'-'+day);
  } 
    
  public void Print(int y)            //输出日期值
  {
    System.out.println("date is "+y+'-'+month+'-'+day);
  } 
            
  public static void main(String args[])
  {
    Date2 a = new Date2(2003,1,1) ;   //创建对象
    Date2 b = new Date2() ;           //创建对象  
    a.Print();
    b.Print(2000);
  }
}
程序运行输出:
date is 2003-1-1
date is 2000-8-10

    上述例子中,构造方法和Print()方法都重写了两个。第一个构造方法有三个参数,第二个构造方法没有一个参数;第一个Print()方法没有参数,第二个Print()方法有一个参数。在main()方法中,定义对象a时有三个参数,所以和第一个构造方法匹配,定义对象b时没有参数,所以和第二个构造方法匹配;对象a调用方法Print()时没有参数,所以和第一个Print()方法匹配,对象b调用方法Print()时有一个参数,所以和第二个Print()方法匹配。
    必须注意的是,方法重写时必须做到:要么参数个数不同,要么参数类型不同。否则,系统无法识别与重写的哪个方法匹配。但是,如果两个重写方法仅返回值的类型不同则不允许
 
 
4、
    面向对象程序设计的一个特点就是公共类资源可以重用。这样,在设计一个软件系统过程中设计的许多公共类(除包含有main方法的公共类外),就可以在以后的软件系统设计中重复使用。但是,当应用软件比较大时,就有许多Java文件,这些Java文件统统放在一个文件夹中,给以后的软件资源重用带来了许多不便。Java解决此问题的方法是包。
    包(package)是Java提供的文件(即公共类)的组织方式。一个包对应一个文件夹,一个包中可以包括许多类文件。包中还可以再有子包,称为包等级。
    Java语言可以把类文件存放在可以有包等级的不同的包中。这样,在软件系统设计时,就可以把相关的一组文件(即相关的一组公共类)存放在一个文件夹中,当文件夹太大时,还可以设计子文件夹按更细的分类方法存放相关文件,从而可以大大方便日后的软件资源重用。Java语言规定,同一个包中的文件名必须惟一,不同包中的文件名可以相同。Java语言的包等级和Windows的文件组织方式完全相同,只是表示方法不同。
 
1、包的建立方法
 
1.定义文件所属的包
    简单的包的定义语句格式为:
    package <包名>;
    其中,package是关键字,<包名>是包的标识符。package语句指出了该语句所在文件所有的类属于哪个包。
    Java语言规定,如果一个 Java文件中有package语句,那么package语句必须写在Java源程序的第一行。例如,下面的Java源程序MyClass.java中的类MyClass将属于包MyPackage。
    package MyPackage;
    public class MyClass;
    {
     ……
    }

 
2.创建包文件夹
    程序员自定义的包(如前面例子的MyPackage包)必须通知系统其文件夹所在的路径,这就需要用户首先创建包的文件夹并设置包的路径。创建包文件夹的方法是:
    ( 1)创建与包同名的文件夹
    例如,我们可以在D盘根目录下创建一个与包同名的文件夹d:\MyPackage。注意,这里的包名MyPackage必须与package语句后的包名大小写完全一致
    ( 2)设置包的路径
    用户必须通过设置环境变量classpath,把用户自定义包所在的路径添加到环境变量classpath中。例如,作者设置的包MyPackage其路径为d:\ ,所以要把d:\添加到环境变量classpath中。
    环境参数设置语句应改写为:
    set classpath=.;c:\jdk1.3.1\lib;d:\;
    环境参数设置说明:
    ①分号(;)用来分隔开各项。因此,上述的设置共有三项。
    ②c:\jdk1.3.1\lib是作者计算机上安装的JDK1.3.1版本的系统包的路径。
    ③新添加的d:\是用户自定义包文件夹的上一级路径。
    ④新添加的路径d:\也可放在圆点(.)(表示当前工作路径)前,则操作时只需把当前路径下编译成功的.class文件复制到自定义包文件夹中;如果路径d:\放在圆点(.)后,则操作时需把当前路径下编译成功的.class文件移动到自定义包文件夹。

    当多个Java源程序文件中都有package语句,且package语句后的包名相同时,则说明这些类同属于一个包。
    一个包还可以有子包,子包下还可以有子子包。在这种情况下,可以具体指明一个类所属包的完整路径。所以,完整的package语句格式为:
    package  <包名>[.<子包名>[.<子子包名>…]];
    其中,在package语句中,圆点(.)起分隔作用;而在Windows的目录中,圆点(.)和反斜杠(\)等义,即加一个圆点(.)就表示下一级目录。
    当然,要把一个类放在某个子包或子子包中,前提条件是已经创建了与子包或子子包同名的目录结构也相同的文件夹。
    ( 3)把编译生成的.class文件移入包中
    用户的源程序文件(即.java文件)通常存放在另外的文件夹中,.java文件编译后产生的.class文件也存放在和.java文件相同的文件夹中。用户在编译.java文件成功后,需要把编译成功的.class文件移入用户自定义的包中。要保证包中有相应的.class文件,而当前工作目录下没有。
    例如,当上述的 MyClass.java文件编译成功后,需要设计人员自己把MyClass.class文件移入到d:\ MyPackage文件夹中,否则系统会因找不到类而给出出错信息。
 
2、包的使用方法
 
    包中存放的是编译后的类文件(.class文件)。用户可以在以后的程序中,通过import语句导入这些类,从而使用包中的这些类。
    import语句的使用分两种情况:
     (1)导入某个包中的某个类;
     (2)导入某个包中的全部类。
    这两种情况分别用如下两种形式的import语句:
    import  MyPackage.MyClass; //导入包MyPackage中的MyClass类
    import  MyPackage.*;       //导入包MyPackage中的全部类,但不包含其子包

    要说明的是,Java中的包是按类似Windows文件的形式组织的,Windows文件用反斜杠(\)表示一条路径下的子路径,而Java用圆点(.)表示一个包的子包。
【例3.6】设计一个日期类及其测试程序。
要求:把日期类放在包MyPackage中,以便于以后重复使用。
程序设计如下:

//Date1.java文件
package MyPackage;                     //定义类所属的包
public class Date1                     //类声明
{
  public int year,month,day;           //成员变量,表示年、月、日
  public Date1(int y, int m, int d)    //构造方法
  {
    year = y;
    month = m;
    day = d;
  } 
  public void print()                  //输出日期值
  {
    System.out.println("日期是:" + year + '-' + month +  '-'+day);
  }
}
 
// UseDate1.java文件
import MyPackage.Date1;                //导入MyPackage中的Date1类
public class UseDate1
{
  public static void main(String args[])
  {
    Date1 a = new Date1(2004,5,10) ;   //创建对象
    a.print();
  }
}
程序运行结果:
日期是:2004-5-10

程序设计说明:因UseDate1.java文件和Date1.java文件不在一个包中,所以,UseDate1.java文件要用import语句导入文件中使用的类。
总结编写、运行上述带有自定义包Java程序的操作步骤如下:
(1)创建文件夹。如在本地计算机的d盘创建文件夹MyPackage(d:\MyPackage)
(2)在环境变量中添加自定义包的路径。如在autoexec.bat文件的classpath参数中添加d:\(注意:若在Windows98下,则设置完成后要运行一下该批处理文件)
(3)编译包中类的.java文件。如在DOS下执行命令:javac Date1.java
(4)把编译成功的.class文件移入包中。如把当前工作路径下的Date1.class文件移动到文件夹d:\MyPackage中
(5)编译导入包的.java文件。如在DOS下执行命令:javac UseDate.java
(6)运行导入包的.class文件。如在DOS下执行命令:java UseDate

 
 
3、包的访问权限
 
    关于包中类的访问权限已在3.2.1节讨论,关于包中类的成员变量和方法的访问权限已在3.2.2节讨论。本节分相同包中的类和不同包中的类两种情况举例说明。
 
1.相同包中的类和类的成员的访问权限
【例3.7】相同包中的访问权限举例。
程序设计如下:
//文件B1.java
package MyPackage;         //文件中定义的两个类都在同一个包
class C1                   //C1声明为缺省类
{
  int number;              //默认成员变量number
  protected int age;       //protected成员变量age
    
  C1(int n, int a)         //构造方法
  {
    number = n;
    age = a;
  }
  public void output()     // C1类的public方法
  {
    System.out.println("number = " + number + "\n" + "age = " + age);
  }
}
 
public class B1            //B1声明为public类          
{
  public void output()     //B1类的方法output()
  {
    C1 s1 = new C1(0,0);  //B1类可以访问同一个包中的默认类C1
    s1.number = 1;         //同一包的对象可以访问默认类的默认成员变量
    s1.age = 25;           //同一包的对象可以访问默认类的protected成员变量
    s1.output();           //同一包的对象可以访问默认类的public方法
  }
}
 
//文件D1.java
//类D1在当前工作路径下
import MyPackage.B1;       //导入MyPackage包中的B1类
public class D1
{
 public static void main(String args[])
 {
    B1 t1 = new B1();
    t1.output();
  }
}
程序的运行结果为:
number = 1
age = 25

程序说明:D1类中只能定义B1类的对象,不能定义C1类的对象(因C1类定义为默认类);但B1类中可以定义C1类的对象(因两个类在同一个包中)。

 
2.不同包中的类和类的成员的访问权限
 
【例3.8】不同包中的访问权限举例。
要求:把例3.7中的C1类和B1类分别放在两个不同的包中。
程序设计如下:
//文件C2.java
package MyPackage.MyPackage1;
public class C2
{
  public int number; 
  public int age;   
   
  public C2(int n, int a) 
  {
    number = n;
    age = a;
  }
  public void output() 
  {
    System.out.println("number = " + number + "\n" + "age = " + age);
  }
}
 
//文件B2.java
package MyPackage;
import MyPackage.MyPackage1.C2;
public class B2             
{
 public void output() 
  {
    C2 s1 = new C2(0,0); 
    s1.number = 1;  
    s1.age = 25;    
    s1.output();     
  }
}
 
//文件D2.java
import MyPackage.B2; 
public class D2           
{
 public static void main(String args[])
 {
    B2 t1 = new B2();
    t1.output(); 
  }
}
程序的运行结果和例3.7的相同。

程序设计说明:
    (1)把例3.7程序稍做改变,把C2类放在MyPackage.MyPackage1包中(当然,要建立相应的文件夹),把B2类放在MyPackage包中。当然,B2.java文件中要用import语句导入C2类。此时,由于B2类和C2类不在同一个包中,而C2类定义为默认类,所以编译时语句:
    C2 s1 = new C2(0,0);将出错
    把C2类定义为public类后,则不会出现上述错误。但是,由于B2类和C2类不在同一个包中,所以编译时语句:
    s1.number = 1;
    s1.age = 25;将出错
    这是由于C2类的age成员变量定义为protected,number定义为默认,而修饰为protected和默认的成员变量不允许其他包中的C2类的对象调用;当把C2类的age和numbe成员变量的修饰符改为public,编译成功。
     (2)如果把C2类放在MyPackage包中,把B2类放在MyPackage.MyPackage1包中,则编译时会出错。这是由于JDK规定:在一个树型结构的包中,上层包可以导入下层包,而下层包不可以导入上层包。在下层包的类中要使用上层包的类时,要在类前面加上包名。
 
4、系统定义的包
 
    Java语言提供了许多方便用户程序设计的基础类,这些系统定义的类以包的形式保存在系统包文件夹中。如果要使用这些包中的类,必须在源程序中用import语句导入。其导入方法和前面介绍的导入自定义包方法类同。例如,要进行图形用户界面设计,需要导入系统包java.awt中的所有类,所以要在源程序中使用如下导入语句:
    import  java.awt.*;     //导入java.awt包中的所有类
    又例如,在进行图形用户界面设计时,还需要进行事件处理,因此需要导入图形用户界面包java.awt中的所有类和事件处理包java.awt.event中的所有类,所以要在源程序中使用如下导入语句:
    import java.awt.*;     //导入java.awt包中的所有类
    import java.awt.event.*;   //导入java.awt.event包中的所有类
    读者也许会想,从Java语言包的组织形式看,显然,java.awt.event包是java.awt包的子包,那么,仅有第一条导入语句似乎就可以了,第二条导入语句似乎没有必要。
    读者需要注意:第一条导入语句只导入了java.awt包中的所有类,并没有导入java.awt包的子包,因此,也就没有导入子包中的类。
 
 
5、内部类
 
    一个类被嵌套定义于另一个类中,称为内部类(Inner Classes)或内隐类。包含内部类的类称为外部类。
    内部类中还有一种更特殊的形式——匿名类,匿名类和内部类的功能类似。这里我们只讨论内部类,不讨论匿名类。
    内部类与前面讨论的非内部类的设计方法基本相同,但除外部类外的其他类无法访问内部类。当一个类只在某个类中使用,并且不允许除外部类外的其他类访问时,可考虑把该类设计成内部类。
 
【例3.9】设计一个人员类,要求自动生成人员的编号。
设计思想:由于要自动生成人员的编号,因此可设计一个static成员变量,当每生成一个对象时,该成员变量自动加1;由于这样的处理只是作用于外部类,所以把该处理过程用内部类的方法来实现。
程序设计如下:
public class PeopleCount        //外部类PeopleCount
{
  private String Name;
  private int ID;               //外部类的私有成员变量
  private static int count = 0; //外部类的static私有成员变量    
  public class People           //内部类People
  {
    public People()             //内部类的构造方法
    {
      count++;                  //访问外部类的成员变量
      ID = count;               //访问外部类的成员变量
     }
     public void output()       //内部类的方法
     {
       System.out.println(Name + "的ID为:" + ID);
     }
   }
   public PeopleCount(String sn) //外部类的构造方法
   {
     Name = sn; 
   }
 
   public void output()          //外部类的方法
   {
     People p = new People();    //建立内部类对象p
     p.output();                 //通过p调用内部类的方法
   }
   public static void main (String args[])
   {
     PeopleCount p1 = new PeopleCount("张三");
     p1.output();
     PeopleCount p2 = new PeopleCount("李四");
     p2.output();        
   }
}
程序的运行结果为:
张三的ID为:1
李四的ID为:2

    程序设计说明:
    在外部类PeopleCount内嵌套定义了一个内部类People,当定义了外部类对象p1和p2后,调用p1或p2的方法output时,该方法将首先定义一个内部类对象,内部类对象的构造方法将先把外部类的static成员变量count加1,然后把count的值赋给人员编号成员变量PeopleID,然后输出PeopleID的值。
    外部类与内部类的访问原则是:在外部类中,一般通过一个内部类的对象来访问内部类的成员变量或方法;在内部类中,可以直接访问外部类的所有成员变量和方法(包括静态成员变量和方法、实例成员变量和方法及私有成员变量和方法)。
    内部类具有以下特性:
 内部类作为外部类的成员。Java将内部类作为外部类的一个成员,因此内部类可以访问外部类的私有成员变量或方法。
 内部类的类名只能用在外部类和内部类自身中。当外部类引用内部类时,必须给出完整的名称,且内部类的类名不能与外部类的类名相同。
    在实际的Java程序设计中,内部类主要用来实现接口。

 
 
6、类的封装性
 
    面向对象程序设计语言的一个重要特性是其封装性,Java语言是按类划分程序模块的,Java语言很好地实现了类的封装性。
    保证大型软件设计正确性和高效性的一个重要原则是模块化软件设计。这需要设计许多可重复使用的模块,然后在需要使用这些模块的地方调用这些模块。但是,如何划分好模块的界限,以保证模块的正确性是非常重要的问题。因为如果某人随意修改了已经被其他人使用的模块,必将使程序出错,并且这样的错误很难发现和修改。
    在面向对象程序设计中,保证模块正确性的基本方法是类的封装性类的封装性是指类把成员变量和方法封装为一个整体,这就划分了模块的界限。
    保证模块正确性的措施则是由信息的隐藏性来实现的。类包括成员变量和方法两部分。那些允许其他包程序访问和修改的成员变量可以定义为public类型。那些只允许同在一个包中的其他类以及该类的子类访问和修改的成员变量可以定义为protected类型。那些不允许其他类(内部类除外)访问和修改的成员变量可以定义为private类型。我们说,private类型和protected类型的成员变量有效地隐藏了类的不能被随意修改的成员变量信息,从而保证了共享的类模块的正确性。类的封装性和信息的隐藏性是双胞胎,两者是结合在一起的。
    同样,那些允许其他包程序访问的方法可以定义为public类型。那些只允许同在一个包中的其他类以及该类的子类访问的方法可以定义为protected类型。那些不允许其他类(内部类除外)访问的方法可以定义为private类型。类方法的不同类型定义,给调用者提供了权限明了的调用接口。
    和别的面向对象程序设计语言(如C++语言)相比,Java语言增加了包的概念。这样,同一个包中类之间的信息传递就比较方便。
 
7、设计举例
 
    本节给出一个较为复杂的程序设计举例。
【例3.10】设计一个包括矩阵加和矩阵减运算的矩阵类,并设计一个测试程序完成简单的测试。为简化设计代码,矩阵的元素值在构造方法中用随机函数随机给出。
程序设计如下:
//MyMatrix.java文件
public class MyMatrix      //矩阵类MyMatrix
{
 private int[][] table;    //矩阵元素表
   private int height;     //矩阵的行
   private int width;      //矩阵的列
 
   private void init(int m,int n)  //元素随机赋值方法
   {
      table=new int[m][n];   //分配矩阵元素数组内存空间
      for(int i=0; i<m; i++)
        for(int j=0; j<n; j++)
        {
           table[i][j]=(int)(Math.random() * 100); //元素随机赋值
        }
  }
 
   public MyMatrix(int n)    //构造方法,构造方阵
   {
       height = n;      
       width = n;
       this.init(height,width);  //调用元素随机赋值方法
   }
 
   public MyMatrix(int m,int n)   //构造方法,构造m行n列矩阵
   {
      height=m;
      width=n;
      this.init(height,width);   //调用元素随机赋值方法
   }
 
   public int getHeight()    //返回矩阵的行数方法
   {
      return height;
   }
 
   public int getWidth()    //返回矩阵的列数方法
   {
      return width;
   }
 
   public int[][] getTable()   //返回矩阵方法
   {
      return table;
   }
 
   public MyMatrix add(MyMatrix b)  //矩阵加方法
    {     
if(this.getHeight()!=b.getHeight()&&
this.getWidth()!=b.getWidth())
      {
        System.out.println("the two matrix don't macth");
        return null;
      }
      MyMatrix result=new MyMatrix(b.getHeight(),b.getWidth());
      for(int i=0;i<b.getHeight();i++)
        for(int j=0;j<b.getWidth();j++)
        {
           result.table[i][j]=this.table[i][j]+b.table[i][j];
        }
      return result;
   }
 
   public MyMatrix subtract(MyMatrix b)    //矩阵减方法
   {
      if(this.getHeight()!=b.getHeight()&&
this.getWidth()!=b.getWidth())
      {
        System.out.println("the two matrix don't macth");
        return null;
      }
 
      MyMatrix result=new MyMatrix(b.getHeight(),b.getWidth());
      for(int i=0;i<b.getHeight();i++)
        for(int j=0;j<b.getWidth();j++)
        {
           result.table[i][j]=this.table[i][j]-b.table[i][j];
        }
      return result;
   }
}
 
// TestMyMatrix.java文件
public class TestMyMatrix        //测试类
{
   public static void main(String[] args)
   {
     MyMatrix mm1=new MyMatrix(4,4);
     MyMatrix mm2=new MyMatrix(4,4);
     MyMatrix mm3=new MyMatrix(4,5);
     MyMatrix mm4=new MyMatrix(4,5);
 
     MyMatrix add_result=mm1.add(mm2);
     int[][] add_table=add_result.getTable();
     MyMatrix subtract_result=mm3.subtract(mm4);
     int[][] subtract_table=subtract_result.getTable();
 
     System.out.println("two matrix add result:");
     for(int i=0;i<add_result.getHeight();i++)
     {
       for(int j=0;j<add_result.getWidth();j++)
       {
          System.out.print(add_table[i][j]+"  ");
       }
       System.out.println();
     }
 
     System.out.println("two matrix subtract result:");
    for(int i=0;i<subtract_result.getHeight();i++)
    {
       for(int j=0;j<subtract_result.getWidth();j++)
       {
         System.out.print(subtract_table[i][j]+"  ");
       }
       System.out.println();
    }
   }
}
程序运行结果如下:
two matrix add result:
67  94  130  78
21  171  78  104
47  100  84  111
125  152  98  61
two matrix subtract result:
-15  88  37  -25  -21
56  -5  32  40  41
-56  31  -75  -21  -4
-17  -46  -18  2  -28




-The End-

posted on 2008-09-21 22:51 decode360-3 阅读(1005) 评论(0)  编辑  收藏 所属分类: Java

只有注册用户登录后才能发表评论。


网站导航: