当Java应用软件比较大时,就会有许多Java文件,如果这些Java文件放在一个文件夹中,管理起来就比较困难,解决此问题的方法是包。
包是Java 提供的文件组织方式。一个包对应一个文件夹;一个包中可以包括很多类文件;包中还可以有子包,形成包等级,Java把类文件放在不同等级的包中。这样,一个类文件就会有两个名字:一个是类文件的短名字,另外一个是类文件的全限定名。短名字就是类文件本身的名字,全限定名则是在类文件的名字前面加上包的名字。
使用包不仅方便了类文件的管理,而且扩大了Java命名空间。不同的程序员可以创建相同名称的类,只要把它们放在不同的包中就可以方便地区分,不会引发冲突。
Java规定:同一个包中的文件名必须唯一,不同包中的文件名可以相同。Java语言中的这种包等级和Windows中用文件夹管理文件的方式完全相同,差别只是表示方法不同。
应用程序可以由若干个包组成,每个包可以包含若干个类和接口,包中也可以有子包。
Java包通过package 语句建立,其基本语法如下:
package<顶层包名>[.子包名];
例如:
package cn.edu.jmu.graphics;
在定义类或接口的源文件开始处,通过package 语句可以将相应的类或接口放入package所声明的包里。包是相关类和接口的集合,提供了访问级别控制和命名空间管理。
将类和接口组织成包的目的是为了能够更有效地使用包中的类。要使用已编译好的包,必须先使用import语句把这些包装载到用户的源代码文件中。包可以通过以下3种方法进行装载。
(1)装载整个包。
可以直接利用import语句载入整个包。此时,整个包中的所有类都可以加载到当前程序之中,例如:
import graphicPackage.*;
这个语句必须位于源程序中的任何类和接口定义之前。有了这个语句,就可以在该源程序中的任何地方使用这个包中的类,如Circle和Rectangle等。
(2)装载一个类或接口。
如果只需要某个包中的一个类或接口,这时可以只载入这个类或接口,而不需要装载整个包。装载一个类或接口可使用语句:
import graphicPackage.Circle;
这个语句只载入praphicPackage包中的Circle类。
(3)直接使用包名作类名的前缀。
如果没有使用import语句装载某个包,但又想使用它的某个类,可以直接在所需要的类名前加上包名作为前缀。例如,要声明Rectangle类的一个对象rectG,可以使用语句:
graplicPackage.Rectangle rectG;
除了用import语句装载包之外,Java 的运行系统总是要载入默认的包(包括没有名字的包)供用户使用。例如,运行系统总是为用户自动载入java.lang包。有时,载入的不同包中不同的类可能有相同的名字,在使用这些类时就必须排除二义性。排除二义性类名的方法很简单,就是在类名之前冠以包名作前缀。
通过声明类的访问级别,可以控制对类的访问权限。
类的访问级别分为默认级和public 级。定义类的默认访问级别不需要任何关键字,被声明为默认级的类只对同一个包中的类是可视的。也就是说,只有同一个包内的类实例可以访问这个类,外界不能访问它。如果用关键字public 定义类,不但同一个包里的其他类可以访问这个类,其他包中的类也可以访问它。换句话说,同一个包中的类,相互之间有不受限制的访问权限;而在不同包中,只有public 类可被访问。
public修饰符不只用于定义类的访问控制级别,也可以应用于接口、方法或变量。public接口同public类一样,可以由应用程序中的任何类访问;而且public方法或public变量对任何可以访问它的类或接口都是可视的。
除public修饰符之外,用于访问控制的修饰符还有protected和private。protected或private 仅用来定义方法或变量的访问控制级别。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 语句,指明它所属的包。
包提供了新的命名空间,即使所定义的类使用与其他包中的类相同的名字,只要同名类所属的包不同名,就不会引起冲突。原因是这些类的全程限定名称不同。类的全程限定名包含了类的各层包名。这实质上是应用了面向对象的概念,将类封装于包中。
Java是跨平台的网络编程语言,用Java语言编写的类或应用程序常在网络中使用。对于世界各地应用Java语言的程序员来说,他们极有可能对不同的类使用了相同的名字。如果定义一个类,它的名字是Rectangle,这样的类名就与Java API中的类重名,因为在java.awt包中已经存在一个Rectangle 类。不过它们之间不会发生冲突,因为这两个Rectangle类的全程限定名并不相同,分别是graphics.Rectangle和java.awt.Rectangle。使用类的全程限定,通常情况下可以有效避免类重名的问题,但也有可能出现两个不相关的类所属包名相同的特殊情况。也就是说,在这种情况下会因为类的全程限定名完全相同而引起类同名的冲突。
Java建议反转Internet域名为包名。如果域名为www.jmu.edu.cn,包命名则可以cn.edu.jmu开始,例如建立包cn.edu.jmu.timer,创建类cn.edu.jmu.timer.Time。
对于大型公司的各个部门,可以自然地按照部门的子域名对包进行划分,并且可以照此方法对部门中的不同项目组、不同项目细分下去。这样,在有很多项目同时运作的情况下,也能根据包名判断它所属的部门、项目、项目模块以及基本功能。
为了能使用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{
……
}
两者是等价的。
当用运算符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类型也没有。这是因为一个类的构造方法的返回值的类型就是这个类本身。对构造方法同样也有访问权限的限制。
this和super是Java的两个关键字,它们用在方法体中作为两个特殊的变量前缀和方法前缀。this用来指明当前对象的成员变量或方法,以区分于同名的局部变量和其他同名的方法,而super则用于指出父类的变量和方法。
一个对象中的方法一般可以直接访问同一对象的成员变量。但是,有时候方法体内部定义的变量、方法的入口参数和对象的成员变量名字相同,那么就需要将三者区别清楚。因此,专门用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可以使程序可读性提高。
在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()语句。