三、Java类的基本构成 1.Java类的定义形式 一个完整的Java类通常由下面六个部分组成: 包定义语句 (一句) import语句 (若干句) 类定义{ 成员变量 构造方法 成员方法 } 其中:只有类定义和“{}”是不可或缺的,其余部分都可以根据需要来定义。 下面分别来学习各个部分的基本规则,看看如何写Java的类。 2.包 2.1包是什么? 在Java中,包是类、接口或其它包的集合,包主要用来将类组织起来成为组,从而对类进行管理。 2.2包能干什么? 包对于下列工作非常有用: (1):包允许您将包含类代码的文件组织起来,易于查找和使用适当的类。 (2):包不止是包含类和接口,还能够包含其它包。形成层次的包空间。 (3):它有助于避免命名冲突。当您使用很多类时,确保类和方法名称的唯一性是非常困难的。包能够形成层次命名空间,缩小了名称冲突的范围,易于管理名称。
为便于管理数目众多的类,Java语言中引入了“包”的概念,可以说是对定义的Java类进行“分组”,将多个功能相关的类定义到一个“包”中,以解决命名冲突、引用不方便、安全性等问题。 就好似当今的户籍制度,每个公民除有自己的名字“张三”、“李四”外还被规定了他的户籍地。假定有两个人都叫张三,只称呼名字就无法区分他们,但如果事先登记他们的户籍分别在北京和上海,就可以很容易的用“北京的张三”、“上海的张三”将他们区分开来。如果北京市仍有多个张三,还可以细分为“北京市.海淀区的张三”、“北京市.西城区.平安大街的张三”等等,直到能惟一标识每个“张三”为止。 JDK中定义的类就采用了“包”机制进行层次式管理,下图显示了其组织结构的一部分:
从图中可以看出,一个名为java的包中又包含了两个子包:io包和lang包。lang包中包含了System, String, Object三个类的定义。事实上,Java包中既可以包含类的定义,也可以包含子包,或同时包含两者。 简而言之:从逻辑上讲,包是一组相关类的集合;从物理上讲,同包即同目录。 但是千万注意:不能将一般的目录看作是包! 2.1 JDK中常用的包 java.lang —— 包含一些Java语言的核心类,包含构成Java语言设计基础的类。在此包中定义的最重要的一个类是“Object”,代表类层次的根,Java是一个单根系统,最终的根就是“Object”,这个类会在后面讲到。 Java并不具有“自由”的方法,例如,不属于任何类的方法,Java中的所有方法必须始终属于某个类。经常需要使用数据类型转换方法。Java在Java.lang包中定义了“包装对象”类,使我们能够实现数据类型转换。如Boolean、Character、Integer、Long、Float和Double,这些在后面会讲到。 此包中的其它类包括: l Math —— 封装最常用的数学方法,如正弦、余弦和平方根。 l String,StringBuffer —— 封装最常用的字符串操作。 你不必显示导入该包,该Java包通常已经导入。 java.awt —— 包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 javax.swing —— 完全Java版的图形用户界面(GUI)解决方案,提供了很多完备的组件,可以应对复杂的桌面系统构建。 java.net —— 包含执行与网络相关的操作的类,如URL, Socket, ServerSocket等。 java.io —— 包含能提供多种输入/输出功能的类。 java.util —— 包含一些实用工具类,如定义系统特性、使用与日期日历相关的方法。还有重要的集合框架。 2.2 Java中如何表达包— package语句 Java语言使用package语句来实现包的定义。package语句必须作为Java源文件的第一条语句,指明该文件中定义的类所在的包。若缺省该语句,则指定为无名包,其语法格式为: package pkg1[.pkg2[.pkg3…]]; //“[]”表示可选 Java编译器把包对应于文件系统的目录管理,因此包也可以嵌套使用,即一个包中可以含有类的定义也可以含有子包,其嵌套层数没有限制。package语句中,用‘.’来指明包的层次; 程序package的使用:Test.java package p1; public class Test{ public void display(){ System.out.println("in method display()"); } } Java语言要求包声明的层次和实际保存类的字节码文件的目录结构存在对应关系,以便将来使用该类时能通过包名(也就是目录名)查找到所需要的类文件。简单地说就是包的层次结构需要和文件夹的层次对应。 注意:每个源文件只有一个包的声明,而且包名应该全部小写。 具体来说,程序员要做以下工作: 2.3编译和生成包 如果在程序Test.java中已定义了包p1,就必须将编译生成的字节码文件Test.class保存在与包名同名的子目录中,可以选用下述两种方式之一: 采用下述命令编译: javac Test.java 则编译器会在当前目录下生成Test.class文件,再在适合位置手动创建一个名为p1的子目录,将Test.class复制到该p1目录下。 采用简化的编译命令,就是可以带包编译 javac -d destpath Test.java 归入该包的类的字节代码文件应放在java的类库所在路径的destpath子目录下。现在包的相对位置已经决定了,但java类库的路径还是不定的。事实上,java可以有多个存放类库的目录,其中的缺省路径为java目录下的lib子目录,你可以通过使用-classpath选项来确定你当前想选择的类库路径。除此之外,你还可以在CLASSPATH环境变量中设置类库路径。destpath为目标路径,可以是本地的任何绝对或相对路径。则编译器会自动在destpath目录下建立一个子目录p1,并将生成的.class文件自动保存到destpath/p1下。例如: javac -d .\ Test.java javac -d C:\test\ Test.java 2.4带包运行 运行带包的程序,需要使用类的全路径,也就是带包的路径,比如上面的那个程序,就使用如下的代码进行运行: java p1.Test 3.import 语句 为了能够使用某一个包的成员,我们需要在Java程序中明确导入该包。使用“import”语句可完成此功能。在java源文件中import语句应位于package语句之后,所有类的定义之前,可以有0~多条,其语法格式为: import package1[.package2…].(classname|*); java运行时环境将到CLASSPATH + package1.[package2…]路径下寻找并载入相应的字节码文件classname.class。“*”号为通配符,代表所有的类。也就是说import语句为编译器指明了寻找类的途径。 例,使用import语句引入类程序:TestPackage.java import p1.Test; //或者import p1.*; public class TestPackage{ public static void main(String args[]){ Test t = new Test(); //Test类在p1包中定义 t.display(); } } java编译器默认为所有的java程序引入了JDK的java.lang包中所有的类(import java.lang.*;),其中定义了一些常用类:System、String、Object、Math等。因此我们可以直接使用这些类而不必显式引入。但使用其它非无名包中的类则必须先引入、后使用。 3.1 Java类搜寻方式 程序中的import语句标明要引入p1包中的Test类,假定环境变量CLASSPATH的值为“.;C:\jdk6\lib;D:\ex”,java运行环境将依次到下述可能的位置寻找并载入该字节码文件Test.class: .\p1\Test.class C:\jdk6\lib\p1\Test.class D:\ex\p1\Test.class 其中,“.”代表当前路径,如果在第一个路径下就找到了所需的类文件,则停止搜索。否则依次搜索后续路径,如果在所有的路径中都未找到所需的类文件,则编译或运行出错。 4.访问修饰符 Java语言允许对类中定义的各种属性和方法进行访问控制,即规定不同的保护等级来限制对它们的使用。为什么要这样做?Java语言引入类似访问控制机制的目的在于实现信息的封装和隐藏。Java语言为对类中的属性和方法进行有效地访问控制,将它们分为四个等级:private, 无修饰符, protected, public,具体规则如下: 表 Java类成员的访问控制 变量和方法可以处于四个访问级别中的一个:公共,受保护,无修饰符或私有。类可以在公共或无修饰级别。 变量、方法或类有缺省(无修饰符)访问性,如果它没有显式受保护修饰符作为它的声明的一部分的话。这种访问性意味着,访问可以来自任何方法,当然这些方法只能在作为对象的同一个包中的成员类当中。 以修饰符protected标记的变量或方法实际上比以缺省访问控制标记的更易访问。一个protected方法或变量可以从同一个包中的类当中的任何方法进行访问,也可以是从任何子类中的任何方法进行访问。当它适合于一个类的子类但不是不相关的类时,就可以使用这种受保护访问来访问成员。 5.类定义 Java程序的基本单位是类,你建立类之后,就可用它来建立许多你需要的对象。Java把每一个可执行的成分都变成类。 类的定义形式如下: <权限修饰符> [一般修饰符] class <类名> { [<属性定义>] [<构造方法定义>] [<方法定义>] } 这里,类名要是合法的标识符。在类定义的开始与结束处必须使用花括号。你也许想建立一个矩形类,那么可以用如下代码: public class Rectangle{ ......//矩形具体的属性和方法 } 6.构造方法 6.1什么是构造方法? 类有一个特殊的成员方法叫作构造方法,它的作用是创建对象并初始化成员变量。在创建对象时,会自动调用类的构造方法。 6.2构造方法定义规则 Java中的构造方法必须与该类具有相同的名字,并且没有方法的返回类型(包括没有void)。另外,构造方法一般都应用public类型来说明,这样才能在程序任意的位置创建类的实例--对象。 6.3示例 下面是一个Rectangle类的构造方法,它带有两个参数,分别表示矩形的长和宽: public class Rectangle{ int width; int height; public Rectangle(int w,int h) { width=w; height=h; } } 6.4说明 每个类至少有一个构造方法。如果不写一个构造方法,Java编程语言将提供一个默认的,该构造方法没有参数,而且方法体为空。 注意:如果一个类中已经定义了构造方法则系统不再提供默认的构造方法。 7.析构方法 析构方法finalize的功能是:当对象被从内存中删除时,该成员方法将会被自动调用。通常,在析构方法内,你可以填写用来回收对象内部的动态空间的代码。 特别注意:当我们去调用析构方法的时候,并不会引起该对象实例从内存中删除,而是不会起到任何作用。 在Java编程里面,一般不需要我们去写析构方法,这里只是了解一下就可以了。 8.属性 8.1属性是什么? 简单点说,属性就是对象所具有的静态属性。 8.2定义规则 Java类中属性的声明采用如下格式: 访问修饰符 修饰符 类型 属性名称=初始值; 访问修饰符:可以使用四种不同的访问修饰符中的一种,包括public(公共的)、protected(受保护的),无修饰符和private(私有的)。public访问修饰符表示属性可以从任何其它代码调用。private表示属性只可以由该类中的其它方法来调用。protected将在以后的课程中讨论。 修饰符:是对属性特性的描述,例如后面会学习到的:static、final等等。 类型:属性的数据类型,可以是任意的类型。 属性名称:任何合法标识符 初始值:赋值给属性的初始值。如果不设置,那么会自动进行初始化,基本类型使用缺省值,对象类型自动初始化为null。 8.3说明 属性有时候也被称为成员变量、实例变量、域,它们经常被互换使用。 9.方法 9.1方法是什么? 方法就是对象所具有的动态功能。 9.2定义规则 Java类中方法的声明采用以下格式: 访问修饰符 修饰符 返回值类型 方法名称 (参数列表) throws 异常列表 {方法体} 访问修饰符:可以使用四种不同的访问修饰符中的一种,包括public(公共的)、protected(受保护的),无修饰符和private(私有的)。public访问修饰符表示方法可以从任何其它代码调用。private表示方法只可以由该类中的其它方法来调用。protected将在以后的课程中讨论。 修饰符:是对方法特性的描述,例如后面会学习到的:static、final、abstract、synchronized等等。 返回值类型:表示方法返回值的类型。如果方法不返回任何值,它必须声明为void(空)。Java技术对返回值是很严格的,例如,如果声明某方法返回一个int值,那么方法必须从所有可能的返回路径中返回一个int值(只能在等待返回该int值的上下文中被调用。) 方法名称:可以是任何合法标识符,并带有用已经使用的名称为基础的某些限制条件。 参数列表:允许将参数值传递到方法中。列举的元素由逗号分开,而每一个元素包含一个类型和一个标识符。在下面的方法中只有一个形式参数,用int类型和标识符days来声明:public void test(int days){} throws异常列表:子句导致一个运行时错误(异常)被报告到调用的方法中,以便以合适的方式处理它。异常在后面的课程中介绍。 花括号内是方法体,即方法的具体语句序列。 9.3示例 比如现在有一个“车”的类Car,“车”具有一些基本的属性,比如四个轮子,一个方向盘,车的品牌等等。当然,车也具有自己的功能,也就是方法,比如车能够“开动”——run。要想车子能够开动,需要给车子添加汽油,也就是说,需要为run方法传递一些参数“油”进去。车子跑起来过后,我们需要知道当前车辆运行的速度,就需要run方法具有返回值“当前的速度”。 package cn.javass.javatest; publicclass Car {// 车这个类 private String make;// 一个车的品牌 privateint tyre;// 一个车具有轮胎的个数 privateint wheel;// 一个车具有方向盘的个数 public Car() { // 初始化属性 make = "BMW";// 车的品牌是宝马 tyre = 4;// 一个车具有4个轮胎 wheel = 1;// 一个车具有一个方向盘 }
publicdouble run(int oil) { // 进行具体的功能处理 return 200.0; } } 9.4形参和实参 形参:就是形式参数的意思。是在定义方法名的时候使用的参数,用来标识方法接收的参数类型,在调用该方法时传入。 实参:就是实际参数的意思。是在调用方法时传递给该方法的实际参数。 比如:上面的例子中“int oil”就是个形式参数,这里只是表示需要加入汽油,这个方法才能正常运行,但具体加入多少,要到真正使用的时候,也就是调用这个方法的时候才具体确定,加入调用的时候传入“80”,这就是个实际参数。 形参和实参有如下基本规则: (1)形参和实参的类型必须要一致,或者要符合隐含转换规则 (2)形参类型不是引用类型时,在调用该方法时,是按值传递的。在该方法运行时,形参和实参是不同的变量,它们在内存中位于不同的位置,形参将实参的值复制一份,在该方法运行结束的时候形参被释放,而实参内容不会改变。 (3)形参类型是引用类型时,在调用该方法时,是按引用传递的。运行时,传给方法的是实参的地址,在方法体内部使用的也是实参的地址,即使用的就是实参本身对应的内存空间。所以在函数体内部可以改变实参的值。 9.5参数可变的方法 (学习了数组和包装类后再学习此内容) 从JDK5.0开始,提供了参数可变的方法。 当不能确定一个方法的入口参数的个数时,5.0以前版本的Java中,通常的做法是将多个参数放在一个数组或者对象集合中作为参数来传递,5.0版本以前的写法是: int sum(Integer[] numbers){…} //在别处调用该方法 sum(new Integer[] {12,13,20}); 而在5.0版本中可以写为: int sum(Integer... numbers){//方法内的操作} 注意:方法定义中是三个点 //在别处调用该方法 sum(12,13,20);//正确 sum(10,11); //正确 也就是说,传入参数的个数并不确定。但请注意:传入参数的类型必须是一致的,究其本质,就是一个数组。 显然,JDK5.0版本的写法更为简易,也更为直观,尤其是方法的调用语句,不仅简化很多,而且更符合通常的思维方式,更易于理解。 Java私塾跟我学系列——JAVA篇 网址:http://www.javass.cn 电话:010-68434236 |