您的位置: 网站首页 > 程序开发 > Java程序设计 > 第3章 对象和类 > 【3.4 类的组织】

3.4 类的组织

 

3.4 

Java应用软件比较大时,就会有许多Java文件,如果这些Java文件放在一个文件夹中,管理起来就比较困难,解决此问题的方法是包。

3.4.1 

包是Java 提供的文件组织方式。一个包对应一个文件夹;一个包中可以包括很多类文件;包中还可以有子包,形成包等级,Java把类文件放在不同等级的包中。这样,一个类文件就会有两个名字:一个是类文件的短名字,另外一个是类文件的全限定名。短名字就是类文件本身的名字,全限定名则是在类文件的名字前面加上包的名字。

使用包不仅方便了类文件的管理,而且扩大了Java命名空间。不同的程序员可以创建相同名称的类,只要把它们放在不同的包中就可以方便地区分,不会引发冲突。

Java规定:同一个包中的文件名必须唯一,不同包中的文件名可以相同。Java语言中的这种包等级和Windows中用文件夹管理文件的方式完全相同,差别只是表示方法不同。

应用程序可以由若干个包组成,每个包可以包含若干个类和接口,包中也可以有子包。

1.包的建立方法

Java包通过package 语句建立,其基本语法如下:

package<顶层包名>[.子包名]

例如:

package cn.edu.jmu.graphics;

在定义类或接口的源文件开始处,通过package 语句可以将相应的类或接口放入package所声明的包里。包是相关类和接口的集合,提供了访问级别控制和命名空间管理。

2.包的使用方法

将类和接口组织成包的目的是为了能够更有效地使用包中的类。要使用已编译好的包,必须先使用import语句把这些包装载到用户的源代码文件中。包可以通过以下3种方法进行装载。

1)装载整个包。

可以直接利用import语句载入整个包。此时,整个包中的所有类都可以加载到当前程序之中,例如:

import graphicPackage.*;

这个语句必须位于源程序中的任何类和接口定义之前。有了这个语句,就可以在该源程序中的任何地方使用这个包中的类,如CircleRectangle等。

2)装载一个类或接口。

如果只需要某个包中的一个类或接口,这时可以只载入这个类或接口,而不需要装载整个包。装载一个类或接口可使用语句:

import graphicPackage.Circle;

这个语句只载入praphicPackage包中的Circle类。

3)直接使用包名作类名的前缀。

如果没有使用import语句装载某个包,但又想使用它的某个类,可以直接在所需要的类名前加上包名作为前缀。例如,要声明Rectangle类的一个对象rectG,可以使用语句:

graplicPackage.Rectangle rectG;

除了用import语句装载包之外,Java 的运行系统总是要载入默认的包(包括没有名字的包)供用户使用。例如,运行系统总是为用户自动载入java.lang包。有时,载入的不同包中不同的类可能有相同的名字,在使用这些类时就必须排除二义性。排除二义性类名的方法很简单,就是在类名之前冠以包名作前缀。

3.包的访问权限

通过声明类的访问级别,可以控制对类的访问权限。

类的访问级别分为默认级和public 级。定义类的默认访问级别不需要任何关键字,被声明为默认级的类只对同一个包中的类是可视的。也就是说,只有同一个包内的类实例可以访问这个类,外界不能访问它。如果用关键字public 定义类,不但同一个包里的其他类可以访问这个类,其他包中的类也可以访问它。换句话说,同一个包中的类,相互之间有不受限制的访问权限;而在不同包中,只有public 类可被访问。

public修饰符不只用于定义类的访问控制级别,也可以应用于接口、方法或变量。public接口同public类一样,可以由应用程序中的任何类访问;而且public方法或public变量对任何可以访问它的类或接口都是可视的。

public修饰符之外,用于访问控制的修饰符还有protectedprivateprotectedprivate 仅用来定义方法或变量的访问控制级别。protected方法或protected变量仅对同一个包内的类或不同包中的子类来说是可视的,而private方法和private变量对外部定义的类均不可视。表3-1表示了访问控制修饰符的使用范围和相应访问级别。需要说明的是:通常不建议采用默认方式定义方法或成员变量的访问级别。

3-1  访问控制表

   

使用范围

   

   

   

   

Private

方法、变量

Yes

 

 

 

protected

方法、变量

Yes

Yes

Yes

 

Public

类、接口、方法、变量

Yes

Yes

Yes

Yes

默认

Yes

Yes

 

 

对于没有指定包名的Java源文件,系统认为它们都属于一个默认的包。如果没有把自己的Java类放入某个包中,那么任何默认包里的对象都可以访问它,并且不局限于同一个子目录下。因此,通常应当在每个Java源文件的顶部使用package 语句,指明它所属的包。

4.包的命名方式

包提供了新的命名空间,即使所定义的类使用与其他包中的类相同的名字,只要同名类所属的包不同名,就不会引起冲突。原因是这些类的全程限定名称不同。类的全程限定名包含了类的各层包名。这实质上是应用了面向对象的概念,将类封装于包中。

Java是跨平台的网络编程语言,用Java语言编写的类或应用程序常在网络中使用。对于世界各地应用Java语言的程序员来说,他们极有可能对不同的类使用了相同的名字。如果定义一个类,它的名字是Rectangle,这样的类名就与Java API中的类重名,因为在java.awt包中已经存在一个Rectangle 类。不过它们之间不会发生冲突,因为这两个Rectangle类的全程限定名并不相同,分别是graphics.Rectanglejava.awt.Rectangle。使用类的全程限定,通常情况下可以有效避免类重名的问题,但也有可能出现两个不相关的类所属包名相同的特殊情况。也就是说,在这种情况下会因为类的全程限定名完全相同而引起类同名的冲突。

Java建议反转Internet域名为包名。如果域名为www.jmu.edu.cn,包命名则可以cn.edu.jmu开始,例如建立包cn.edu.jmu.timer,创建类cn.edu.jmu.timer.Time

对于大型公司的各个部门,可以自然地按照部门的子域名对包进行划分,并且可以照此方法对部门中的不同项目组、不同项目细分下去。这样,在有很多项目同时运作的情况下,也能根据包名判断它所属的部门、项目、项目模块以及基本功能。

3.4.2  包和类的导入

为了能使用Java中已提供的类,则需要用import语句来引入所需要的类。import语句的格式为:

import package1[.package2…]. (classname |*);

其中,package1[.package2]表明包的层次,与package语句相同,它对应于文件目录;classname则指明所要引入的类,如果要从一个包中引入多个类,则可以用星号(*)来代替。例如:

import java.awt.*;

import java.util.Date;

注意:星号形式可能会增加编译时间,特别是在引入多个大包时。因此,明确地命名想要用到的类而不是引入整个包是一个好的方法。另外,星号形式对运行时间性能和类的大小绝对没有影响。

Java编译器为所有程序自动引入包java.lang,因此不必用import语句引入它包含的所有的类,但是若需要使用其他包中的类,必须用import语句引入。

另外,在Java程序中使用类的地方,都可以指明包含它的包,这时就不必用import语句引入该类了;只是这样要敲入大量的字符,因此一般情况下不使用。但是,如果引入的几个包中包括名字相同的类,则当使用该类时必须指明包含它的包,使编译器能够载入特定的类。例如,类Date包含在包java. util中,则可以用import语句引入它以实现它的子类myDate

import java.util.*;

class myDate extends Date{

    ……

}

也可以直接引入该类:

class myDate extends java.util.Date{

    ……

}

两者是等价的。

3.4.3  构造方法

当用运算符new为一个对象分配内存时,要调用对象的构造方法;而当创建一个对象时,必须用运算符new为它分配内存。而且,构造方法只能由new运算符调用。用构造方法进行初始化,避免了在生成对象后每次都要调用对象的初始化方法。如果没有实现类的构造方法,则Java运行系统会自动提供默认的构造方法,它没有任何参数。

【例3-8关于Person类的程序示例。

class Person{

    String firstName = "";

    String lastName = "";

        Person(){

        this.lastName = "Karl";

        this.firstName = "Marx";

    }

    Person(String firstname, String lastname){

        this.firstName = firstname;

        this.lastName = lastname;

    }

        public String getName(){

         return firstName + " " + lastName;

    }

        public void setName(String firstname, String lastname){

         firstName = firstname;

         lastName = lastname;

    }

}

这个类有以下两个构造方法。

Person(){

        this.lastName = "Karl";

        this.firstName = "Marx";

}

Person(String firstname, String lastname){

        this.firstName = firstname;

        this.lastName = lastname;

}

一个没有参数,另外一个有参数。在构造Person类的对象时,将根据构造时的参数调用不同的Person构造方法对对象进行初始化。如果以:

Person p = new Person("Groucho","Marx");

创建对象时,则以下的构造方法将被调用。

Person(String firstname, String lastname){

    this.firstName = firstname;

    this.lastName = lastname;

}

在这个方法中,p.firstName被赋值为Groucho,而p.lastName被赋值为Marx。但是,如果以:

Person p = new Person();

构造对象时,以下的构造方法将被调用。

Person(){

        this.lastName = "Karl";

        this.firstName = "Marx";

}

需要注意的是:构造函数没有返回类型,即使是void类型也没有。这是因为一个类的构造方法的返回值的类型就是这个类本身。对构造方法同样也有访问权限的限制。

3.4.4  thissuper的使用

thissuperJava的两个关键字,它们用在方法体中作为两个特殊的变量前缀和方前缀。this用来指明当前对象的成员变量或方法,以区分于同名的局部变量和其他同名的方法,而super则用于指出父类的变量和方法。

1this的使用场合

一个对象中的方法一般可以直接访问同一对象的成员变量。但是,有时候方法体内部定义的变量、方法的入口参数和对象的成员变量名字相同,那么就需要将三者区别清楚。因此,专门用this来指明当前对象的成员变量或当前对象的方法。例如:

class ThreeColor{

      int h, s, b;

      ThreeColor(int h, int s, int b){

        this.h=h; //这个语句使方法的入口参数值赋于成员变量。

        this.s =s;

        this.b =b;

      }

}

上述例子中,类ThreeColor有一个构造方法ThreeColor,而构造方法的3个入口参数和成员变量的名字相同。为此,要将两者区分,即在方法体内部将this加在各个变量前。每当创建对象需要调用构造方法ThreeColor时,会将构造方法的入口参数作为对象的成员变量。可见,使用this可以使程序可读性提高。

2super的使用场合

Java中,由父类派生子类,这样,子类的成员变量可能和父类的成员变量名字相同,子类的方法也可能和父类的方法一样。当需要调用父类的同名方法或使用父类的同名变量时,在子类中可用关键字super作前缀来指明父类的成员变量和方法。

【例3-9程序示例。

class SuperCa

{

        int x;

        SuperCa()

        {

            x=5;

            System.out.println("SuperCa x=" + x);

        }

        void doCa()

        {

            System.out.println("SuperCa.doCa()");

        }

}

class SubCa extends SuperCa

{

        int x;

        SubCa()

        {

            super();//调用父类的构造方法

            x=8;

            System.out.println(“subCa x="+x);

        }

        void doCa()

        {

            super.doCa();//调用父类的方法

            System.out.println("in subCa.doCa()");

            System.out.println("super.x="+super.x);

        }

}

本例在两种情况下用了super。一是子类调用父类的构造方法,用了super( )语句;二是子类SubCa调用父类的方法doCa时,用了super.doCa()语句。