第3章面向对象程序设计基础 3.1面向对象的基本概念 面向对象是一种程序设计方法,或者说它是一种程序设计规范,其基本思想是使用对象、类、继承、封装、消息等基本概念来进行程序设计。所谓“面向对象”就是以对象及其行为为中心,来考虑处理问题的思想体系和方法。采用面向对象方法设计的软件,不仅易于理解,而且易于维护和修改,从而提高了软件的可靠性和可维护性,同时也提高了软件模块化和可重用化的程度。 Java是一种纯粹的面向对象的程序设计语言。用Java进行程序设计必须将自己的思想转入面向对象的世界,以面向对象世界的思维方式来思考问题,因为Java程序乃至Java程序内的一切都是对象。 1. 对象的基本概念 对象是系统中用来描述客观事物的一个实体,是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。从更抽象的角度来说,对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用; 它是一组属性和有权对这些属性进行操作的一组服务的封装体。客观世界是由对象和对象之间的联系组成的。 在面向对象的程序设计方法中,对象是一些相关的变量和方法的软件集,是可以保存状态(信息)和一组操作(行为)的整体。软件对象经常用于模仿现实世界中的一些对象,如桌子、电视、自行车等。 现实世界中的对象有两个共同特征: 形态和行为。 例如,把汽车作为对象,汽车的形态有车的类型、款式、挂挡方式、排量大小等,其行为有刹车、加速、减速、改变挡位等,如图31所示。 图31汽车对象的形态和行为 软件对象实际上是现实世界对象的模拟和抽象,同样也有形态和行为。一个软件对象利用一个或多个变量来体现它的形态。变量是由用户标识符来命名的数据项。软件对象用方法来实现它的行为,它是跟对象有关联的函数。在面向对象设计的过程中,可以利用软件对象来代表现实世界中的对象,也可以用软件对象来模拟抽象的概念。例如,事件是一个GUI(图形用户界面)窗口系统的对象,可以代表用户按下鼠标按键或键盘上的按键所产生的反应。 例如,用软件对象模拟汽车对象,汽车的形态就是汽车对象的变量,汽车的行为就是汽车对象的方法,如图32(a)所示。 图32软件对象的变量和方法 再如,用软件计算圆的面积,描述圆面积的形态是圆的半径和圆的面积,计算圆面积的行为是圆的面积公式,因此,圆面积对象的变量是圆的半径和圆的面积,圆面积对象的方法是计算圆面积的公式及输出结果,如图32(b)所示。 2. 类的基本概念 对象是指具体的事物,而类是指一类事物。 把众多的事物进行归纳、分类是人类在认识客观世界时经常采用的思维方法。分类的原则是按某种共性进行划分。例如,客车、卡车、小轿车等具体机车都有相同的属性,有内燃发动机、车身、橡胶车轮、方向盘等,把它们的共性抽象出来,就形成了“汽车”的概念。但说到某辆车时,仅有汽车这个概念是不够的,还需说明究竟是小轿车还是大卡车。因此,汽车是抽象、不具体一个类的概念。具体的某辆汽车则是“汽车”这个类的对象,也称它是汽车类的一个实例。 由类来确定具体对象的过程称为实例化,即类的实例化结果就是对象,而对一类对象的抽象就是类。 类用class作为它的关键字。例如,要创建一个汽车类,则可表示为: class汽车变量: 类型、款式、挂挡方式、排量大小 方法: 刹车、加速、减速、改变挡位 当要通过汽车类创建一个轿车对象,并使用它的刹车行为方法时,则要用下面的格式进行实例化: 汽车轿车= new汽车(); //实例化汽车类,即创建轿车对象 轿车.刹车(); //引用汽车对象的刹车方法 这里,只是粗略地介绍了类和对象的概念,在后面的内容中将详细介绍类和对象的设计方法。 3.2类 类和对象是Java的核心和本质。它们是Java语言的基础,编写一个Java程序,在某种程度上来说就是定义类和创建对象。定义类和建立对象是Java编程的主要任务。 3.2.1类的定义 从本节开始就接触到类了。当然,这都是一些非常简单的类。类是组成Java程序的基本要素,本节将介绍如何创建一个类。 1. 类的一般形式 类由类声明和类体组成,而类体又由成员变量和成员方法组成。 publicclass类名 { 成员变量; 成员方法; } 图33所示为一个具体类的形式。 图33类的形式 2. 类声明 类声明由4部分组成: 类修饰符、类关键字class、声明父类和实现接口。其一般形式如下。 [public][abstract|final]class类名[extends父类名] [implements接口列表] {  } 各组成部分的具体说明如下。 1) 类修饰符 可选项public、abstract、final是类修饰符。类修饰符说明这个类是一个什么样的类。如果没有声明这些可选项的类修饰符,Java编译器将给出缺省值,即指定该类为非public、非abstract、非final类。类修饰符的含义分别如下。 public: 这个public关键字声明了类可以在其他类中使用。其缺省时,该类只能被同一个包中的其他类使用。  abstract: 声明这个类为抽象类,即这个类不能被实例化。一个抽象类可以包含抽象方法,而抽象方法是没有实现空的方法,所以抽象类不具备实际功能,只用于衍生子类。  final: 声明该类不能被继承,不能有子类。也就是说,不能用它通过扩展的办法来创建新类。 2) 类的关键字class 在类声明中,class是声明类的关键字,表示类声明的开始,类声明后面跟着类名。通常,类名要用大写字母开头,并且类名不能用阿拉伯数字开头。给类名命名时,最好取一个容易识别且有意义的名字,避免A、B、C之类的类名。 3) 声明父类 extends为声明该类的父类,表明该类是其父类的子类。一个子类可以从它的父类继承变量和方法。值得注意的是,Java和C++不一样,在extends之后只能有一个父类,即extends只能实现单继承。 创建子类格式如下。 class SubClass extends父类名 {  } 4) 实现接口 为了在类声明中实现接口,要使用关键字implements,并且在其后面给出接口名。当实现多接口时,各接口名以逗号分隔,其形式为: implements接口1,接口2, … 接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。一个类可以实现多个接口,以某种程度实现“多继承”。 3.2.2成员变量和局部变量 在Java语言中,变量按在程序中所处不同位置分为两类: 成员变量和局部变量。如果类体中的一个变量在所有方法外部声明,该变量称为成员变量。如果一个变量在方法内部声明,该变量称为局部变量。成员变量从定义位置起至该类体结束均有效,而局部变量只在定义它的方法内有效。 变量成员变量(在类体中定义,在整个类中都有效) 局部变量(在方法中定义,只在本方法中有效) 1. 成员变量 最简单的变量声明的形式为: 数据类型变量名; 这里,声明的变量类型可以是基本数据类型,也可以是引用数据类型。 数据类型基本数据类型(整型、浮点型、逻辑型、字符型) 引用数据类型(数组、类对象) 声明成员变量的更一般的形式为: [可访问性修饰符][static][final][transient][volatile]类型变量名 上述属性用方括号括起来,表示它们都是可选项,其含义分别如下。  [可访问性修饰符]: 说明该变量的可访问属性,即定义哪些类可以访问该变量。该修饰符可分为public、protected、package和private,它们的含义在后面将会详细地介绍。  [static]: 说明该成员变量是一个静态变量(类变量),以区别一般的实例变量。类变量的所有实例使用的是同一个副本。  [final]: 说明一个常量。  [transient]: 声明瞬态变量,瞬态变量不是对象的持久部分。  [volatile]: 声明一个可能同时被并存运行中的几个线程所控制和修改的变量,即这个变量不仅被当前程序所控制,而且在运行过程中可能存在其他未知程序的操作来影响和改变该变量的值,volatile关键字把这个信息传送给Java的运行系统。 成员变量还可以进一步分为实例变量和类变量,这些内容在后面章节讲述关键字static时再介绍。 2. 局部变量 在方法中声明的变量以及方法中的参数称为局部变量。局部变量除了作用范围仅适用于本方法之外,其余均与上面讨论的成员变量是一致。 class Data { int x=12,y=5; public void sum() { int s; s=x+y;//使用成员变量x=12,y=5 } } 在类Data中,x、y是成员变量,s是局部变量。成员变量x、y在整个类中有效,类中所有方法都可以使用它们,但局部变量s仅限于在sum方法内部使用。 局部变量和成员变量的作用范围如图34所示。其中,x、y是成员变量; a、b是局部变量。 3. 屏蔽成员变量 如果局部变量名与成员变量名相同,则成员变量被屏蔽。 例如: class Data { int x=12,y=5; public void sum() { int x=3; //局部变量x屏蔽了成员变量 int s; s=x+y; } } 图34变量的作用域 由于在sum方法内部也定义了变量x=3,这时成员变量x=12被屏蔽,变量x的值为3。y的值仍是5,因此s=3+5=8。 如果在sum方法内部还需要使用成员变量x,则要用关键字this来引用当前对象,它的值是调用该方法的对象。 class Data { int x=12,y=5; public void sum() { int x=3;//局部变量x int s; s=this.x+y;//在sum()方法使用成员变量,则用this来说明 } } 由于this.x是成员变量,因此s=12+5=17。 3.3成员方法 在Java中,必须通过方法才能完成对类和对象的属性操作。成员方法只能在类的内部声明并加以实现。一般在类体中声明成员变量之后再声明方法。 3.3.1方法的定义 1. 方法的一般形式 已知,一个类由类声明和类体两部分组成,方法的定义也由方法声明和方法体两个部分组成。方法定义的一般形式为: 返回类型方法名(数据类型1参数1,数据类型2参数2,…) { …(局部变量定义); …(方法功能实现); return(返回值); } 在方法声明中,返回类型可以是基本数据类型或引用数据类型,它是方法体中通过return语句返回值的数据类型,也称为该方法的类型。当该方法为无返回值时,需要用void作为方法的类型。 方法名是由用户定义的标识符。方法名后面有一对小括号,如果括号里面是空的,这样的方法就称为无参方法; 如果括号里面至少有一个参数(称为形式参数,简称形参),则称该方法为有参方法。方法的形参是方法与外界关联的接口,形参在定义时是没有值的,外界在调用一个方法时会将相应的实际参数值传递给形参。 用一对大括号括起来的语句构成方法体,完成方法功能的具体实现。方法体一般由三部分组成: 第一部分为定义方法所需的变量,方法内部定义的变量称为局部变量; 第二部分完成方法功能的具体实现; 第三部分由return语句返回方法的结果。 方法不允许嵌套定义,即不允许一个方法的定义放在另一个方法的定义中。 图35给出了一个简单main方法的代码。在本方法中实现在命令窗口中显示字符串str的内容。 图35一个main方法的代码 方法还可以有许多其他的属性,如参数、访问控制等。 2. 方法的返回值 在方法定义中,方法的类型是该方法返回值的数据类型。方法返回值是方法向外界输出的信息。根据方法功能的要求,一个方法可以有返回值,也可以无返回值(此时方法的类型为void型)。方法的返回值一般在方法体中通过return语句返回。 return语句的一般形式为: return表达式; 该语句的功能是将方法要输出的信息反馈给主调方法。 【例31】有参方法实例。编写一个方法模块,实现计算1+2+3+…+n的n项和的功能。 源程序如下。 1intmysum(int n) 2{ 3int i, s = 0; 4for(i=1; i<=n; i++) 5s = s + i; 6return s; 7} 方法说明: (1) 第1行int mysum(int n)是方法声明,其中mysum是方法名; 方法类型为int类型,表明该方法计算的结果为整型; 括号中的int n表示n是形式参数,简称形参,其类型为int,形参n此时并没有值。 (2) 第2~7行是方法体部分,用以实现求和的功能。 (3) 第6行是通过“return s; ”将求得的和值s返回作为mysum方法的值。 在一个方法中允许有多个return语句,但每次调用只能有一个return语句被执行,即只能返回一个方法值。 【例32】方法中有多个return的示例,求两个数中的较大数。 源程序如下。 1int max(int x, int y) 2{ 3if(x> y)return x; 4elsereturn y; 5} 3.3.2方法的调用 1. 方法调用的语法形式 为实现操作功能而编写的方法必须被其他方法调用才能运行。通常把调用其他方法的方法称为主调方法,被其他方法调用的方法称为被调方法。 方法调用的语句形式如下。 函数名(实际参数1,实际参数2, …,实际参数n); 也就是说,一个方法在被调用语句中,其参数称为实际参数。实际参数简称为实参,方法调用中的实参不需要加数据类型,实参的个数、类型、顺序要和方法定义时的形参一一对应。 对有参方法的调用,实际参数可以是常数、变量或其他构造类型数据及表达式,各实参之间用逗号分隔。对无参方法调用时则无实际参数。 定义有参方法时,形式参数并没有具体数据值,在被主调方法调用时,主调方法必须给出具体数据(实参),将实参值依次传递给相应的形参。 Java程序的运行总是从main()开始,main方法又称为主方法,它可以调用任何其他的方法,但不允许被其他方法调用。除了main方法以外,其他任何方法的关系都是平等的,可以相互调用。 【例33】方法调用示例,计算1+2+3+…+100的和。 算法设计: 在主函数中调用例31中计算前n项和的方法模块,将调用函数时,将函数的参数(实参)设置为100。这时,函数mysum的形参n得到具体值100。从而计算1+2+3+…+100的和。 源程序如下。 1import javax.swing.*; 2public classExample3_3 3{ 4public static void main(String[] args) 5{ 6int sum = mysum(100); 7JOptionPane.showMessageDialog(null, "1 + 2 + 3 + … + 100 = " + sum); 8System.exit(0); 9} 10 11static intmysum(int n) 12{ 13int i, s = 0; 14for(i = 1; i<= n; i++) 15s = s + i; 16return s; 17} 【例34】具有多个参数的方法示例。已知三角形的底和高,计算三角形面积。 源程序如下。 1import javax.swing.*; 2public classExample3_4 3{ 4public static void main(String[] args) 5{ 6float s = area(3, 4); 7JOptionPane.showMessageDialog(null, "三角形面积 = " + s); 8System.exit(0); 9} 10 11 static float area(int x, int h) 12{ 13float s; 14s = (x * h) / 2; 15return s; 16} 17} 【程序说明】 (1) 在程序的第11行定义了一个area方法,该方法有两个int类型的参数,分别代表三角形的底和高。在程序的第15行,返回float类型数值s,故方法area的返回类型为float类型。 (2) 在程序的6行,调用area方法,原方法中的形参就用具体整型数值替换(称为实参): area(3, 4); 方法中的第一个实参3赋值给形参x,第二个实参4赋值给形参h,经area方法运算后,得到返回值6.0。 实参与形参的传递关系如图36所示。 图36实参与形参的传递关系 图37方法声明与调用的运行结果 程序运行结果如图37所示。 2. 方法调用的过程 在Java语言中,程序运行总是从main()开始,按方法体中语句的逻辑顺序依次执行。如遇到方法调用时,就转去执行被调用的方法。当被调用的方法执行完毕,又返回主调方法中继续向下执行。 以例34为例,当调用一个方法时,整个调用过程分为4步进行,如图38所示。 图38所示函数的调用过程 (1) 方法调用,并把实参的值传递给形参。 (2) 执行被调用方法area的方法体,形参用所获得的数值进行运算。 (3) 通过return语句将被调用方法的运算结果输出给主调方法。 (4) 返回到主调方法的方法调用表达式位置,继续后续语句的执行。 3. 方法调用的传值过程 在Java语言中,调用有参方法时,是通过实参向形参传值的。 形参只能在被调方法中使用,实参则只能在主调方法中使用。形参是没有值的变量,发生方法调用时,主调方法把实参的值传送给被调方法的形参,从而实现主调方法向被调方法的数据传送。方法的调用过程也称为值的单向传递,是实参到形参的传递。因此在传递时,实参必须已经有值,并且实参的个数与类型必须与形参的个数及类型完全一致。 方法调用时实参数值按顺序依次传递给相应的形参,传递过程如图39所示。 图39方法参数按值依次传递 下面,进一步考察数据值从实参到形参的传递过程,如图310所示。 图310实参到形参的传递过程 (1) 主调方法为实参赋值,将实参值存放到内存中专门存放临时变量(又称动态局部变量)的区域中。这块存储区域称为堆栈。 (2) 当参数传递时,主调方法把堆栈中的实参值复制一个备份给被调方法的形参。 (3) 被调方法使用形参进行功能运算。 (4) 被调方法把运算结果(方法返回值)存放到堆栈中,由主调方法取回。此时,形参所占用的存储空间被系统收回。注意,此时实参值占用的存储单元被继续使用。 需要特别注意的是,当调用带有参数的方法时,由于实参的值会被复制一份传递给形参,因而在方法中对形参进行简单的重新赋值并不会改变实参。 【例35】观察实参在调用方法前后是否变化。 1public class Example3_5 2{ 3public static void test(int a) 4{ 5a++; 6System.out.println("a = " + a); 7} 8 9public static void main(String[] args) 10{ 11int a = 1; 12test(a); 13System.out.println("a = " + a); 14} 15} 程序运行结果为: a = 2 a = 1 【程序说明】 形参在方法中的值变为2,但方法调用结束后,实参的值并未改变,仍然是1。 3.3.3方法重载 方法重载是指多个方法享有相同的名字,但是这些方法的参数必须不同,或者是参数的个数不同,或者是参数类型不同。返回类型不能用来区分重载的方法。注意: 在设计重载方法时,参数类型的区分度一定要足够,不能是同一简单类型的参数,如int型和long型。 【例36】计算平面空间距离的计算公式分别是x2+y2和x2+y2+z2,使用一个方法名用方法重载实现的程序如下。 1/* 方法重载示例 */ 2import javax.swing.*; 3public class Example3_6 4{ 5static double distance(double x , double y) 6{ 7double d=Math.sqrt(x*x+y*y); 8return d; 9} 10static double distance(double x , double y ,double z ) 11{ 12double d=Math.sqrt(x*x+y*y+z*z); 13return d; 14} 15public static void main(String args[]) 16{ 17double d1 = distance(2,3); 18double d2 = distance(3,4,5); 19JOptionPane.showMessageDialog(null, 20"接收两个参数: 平面距离 d="+d1+"\n"+ 21"接收三个参数: 空间距离 d="+d2); 22System.exit(0); 23} 24} 【程序说明】 (1) 程序的第5行和第10行定义了两个方法名都是distance的方法,第一个distance方法有两个参数,第二个distance方法有三个参数。 (2) 程序的第17行调用了有两个参数的distance方法,第18行调用了有三个参数的receive方法。编译器会根据参数的个数和类型来决定当前所使用的方法。 (3) 从这个例子可以看出,重载显然表面上没有减少编写程序的工作量,但实际上重载使得程序的实现方式变得很简单,只需要使用一个方法名,就可以根据不同的参数个数选择该方法不同的版本。方法的重载与调用关系如图311所示。 图311重载与调用关系 程序运行结果如图312所示。 图312计算平面距离和空间距离 3.3.4构造方法 构造方法是一个特殊的方法,主要用于初始化新创建的对象。构造方法的方法名要求与类名相同,而且无返回值。在创建对象时,Java系统会自动调用构造方法为新对象初始化。另外,构造方法只能通过new运算符调用,用户不能直接调用。需要注意的是,在这里说构造方法无返回值,并不是要在构造方法名前加上void,构造方法名是不能有void的,如果在构造方法名前加了void,系统就不会自动调用该方法了。 一个类可以创建多个构造方法,当类中包含有多个构造方法时,将根据参数的不同的来决定要用哪个构造方法来初始化新创建对象的状态,达到方法重载的目的。 【例37】计算长方体的体积。 源程序如下。 1/* 构造长方体 */ 2class Box 3{ 4double width, height, depth; 5Box() 6{ 7width = 10; 8height = 10; 9depth = 10; 10} 11double volume() 12{ 13return width * height * depth; 14} 15} 16public class Example3_7 17{ 18public static void main(String args[]) 19{ 20Box box = new Box(); 21double v; 22v = box.volume(); 23System.out.println("长方体体积为: " + v); 24} 25} 【程序说明】 程序的第5行定义了构造方法,在第20行使用构造方法创建实例对象。 在一个类的程序中,也可以没有定义构造方法,Java系统会认为是定义了一个默认构造方法,默认构造方法是无任何内容的空方法。当编写类时,只有在需要进行一些特别初始化的场合,才需要定义构造方法。 【例38】使用默认构造方法设计一个计算长方体体积的程序。 源程序如下。 1/* 默认构造方法构造长方体类 */ 2class Box 3{ 4double width, height, depth; 5double volume()//计算长方体体积 6{ 7width = 10; 8height = 10; 9depth = 10; 10return width * height * depth; 11} 12} 13public class Example3_8 14{ 15public static void main(String args[]) 16{ 17Box box = new Box(); 18double v; 19v = box.volume(); 20System.out.println("长方体体积为: " + v); 21} 22} 【程序说明】 本程序与例37比较,它们都没有定义构造方法。在程序的第17行创建实例对象时,使用系统默认的默认构造方法。 3.4对象 在本章的一开始就介绍了类与对象的概念,已知,类是一个抽象的概念,而对象是类的具体化。类与对象的关系相当于普通数据类型与其变量的关系。声明一个类只是定义了一种新的数据类型,类通过实例化创建了对象,才真正创建了这种数据类型的物理实体。 一个对象的生命周期包括创建、使用和释放三个阶段。 1. 对象的创建 创建对象的一般格式为: 类名 对象名= new类名([参数列表]); 该表达式隐含了对象声明、实例化和初始化三部分。 1) 对象声明 声明对象的一般形式为: 类名 对象名; 对象声明并不为对象分配内存空间,而只是分配一个引用空间; 对象的引用类似于指针,是32位的地址空间,它的值指向一个中间的数据结构,它存储有关数据类型的信息以及当前对象所在的堆的地址,而对于对象所在的实际的内存地址是不可操作的,这就保证了安全性。 2) 实例化 实例化是为对象分配内存空间和进行初始化的过程,其一般形式为: 对象名= new构造方法(); 运算符new为对象分配内存空间,调用对象的构造方法,返回引用; 一个类的不同对象分别占据不同的内存空间。在执行类的构造方法进行初始化时,可以根据参数类型或个数不同调用相应的构造方法,进行不同的初始化,实现方法重构。 2. 对象的使用 对象要通过访问对象变量或调用对象方法来使用。 通过运算符“.”可以实现对对象自己的变量访问和方法的调用。变量和方法可以通过设定访问权限来限制其他对象对它的访问。 1) 访问对象的变量 对象创建之后,对象就有了自己的变量。对象通过使用运算符“.”实现对自己的变量访问。 访问对象成员变量的格式为: 对象名.成员变量; 例如,设有一个A类其结构如下。 class A { int x; } 想对其变量x赋值,则先创建并实例化类A的对象a,然后再通过对象给变量x赋值: A a = new A(); a. x=5; 2) 调用对象的方法 对象通过使用运算符“.”实现对自己的方法调用。 调用对象成员方法的格式为: 对象名.方法名([参数列表]); 例如,在例38中,定义了Box类。在Box类中定义了3个double类型的成员变量和一个volume方法,将来每个具体对象的内存空间中都保存有自己的3个变量和一个方法的引用,并由它的volume方法来操纵自己的变量,这就是面向对象的封装特性的体现。要访问或调用一个对象的变量或方法需要首先创建这个对象,然后用算符“.”调用该对象的某个变量或方法。 【例39】用带参数的成员方法计算长方体的体积。 1/* 用带参数的成员方法计算长方体体积 */ 2import javax.swing.*; 3class Box 4{ 5double volume(double width, double height, double depth ) 6{ 7return width * height * depth; 8} 9} 10public class Example3_9 11{ 12public static void main(String args[]) 13{ 14double v; 15Box box = new Box(); 16v = box.volume(3, 4, 5); 17JOptionPane.showMessageDialog(null, "长方体体积=" + v); 18System.exit(0); 19} 20} 【程序说明】 (1) 程序的第5行在Box类定义了一个带有3个参数、返回类型为double的volume方法。 图313计算长方体体积 (2) 程序的第15行在Example3_8类建立并实例化了Box类的对象box,在第16行,对象box调用自己的volume方法,这里必须对方法中的形参赋确定的数据值(实参)。 程序运行结果如图313所示。 【例310】用对象作为方法的参数计算圆柱体的体积。 1/* 对象作为方法的参数使用 */ 2class Circle 3{ 4double r; 5Circle(double r) 6{ 7this.r = r; 8} 9double area() 10{ 11return 3.14 * r * r; 12} 13} 14class Cylinder 15{ 16Cylinder(Circle a, double h) 17{ 18double v = a.area() * h; 19System.out.println("圆柱体的体积=" + v); 20} 21} 22class Example3_9 23{ 24public static void main(String args[]) 25{ 26Circle A = new Circle(5); 27Cylinder c = new Cylinder(A, 10); 28} 29} 【程序说明】 通过本例可以看到,类对象和变量一样,可以作为参数使用。 (1) 程序的第2~13行定义了一个圆类Circle,类中有一个计算圆面积的area()方法。 (2) 程序的第14~21行定义了一个圆柱体类Cylinder,其构造方法以圆类的对象a为参数,在方法内第18行调用对象a的area方法。 用类对象作为参数使用,即用引用型变量作参数,需要说明的是,引用型变量名不是表示变量本身,而是变量的存储地址。理解这一点很重要,因为如果改变了引用型变量的值,那它就会指向不同的内存地址。在C语言中,称其为指针。 3. 释放对象 当不存在对一个对象的引用时,该对象成为一个无用对象。Java的垃圾收集器自动扫描对象的动态内存区,把没有被引用的对象作为垃圾收集起来并释放。由于垃圾收集器自动收集垃圾操作的优先级较低,因此也可以用其他办法释放对象所占用的内存。 例如,使用系统的 System.gc(); 要求垃圾回收,这时垃圾回收线程将优先得到运行。 另外,还可以使用finalize方法将对象从内存中清除。finalize方法可以完成包括关闭已打开的文件、确保在内存不遗留任何信息等功能。finalize方法是Object类的一个成员方法,Object类处于Java类分级结构的顶部,所有类都是它的子类。其他子类可以重载finalize方法来完成对象的最后处理工作。 3.5面向对象的特性 Java语言中有3个典型的面向对象的特性: 封装性、继承性和多态性。下面将详细阐述。 3.5.1封装性 封装性就是把对象的属性和服务结合成一个独立的相同单位,并尽可能隐蔽对象的内部细节。例如,在银行日常业务模拟系统中,账户这个抽象数据类型把账户的金额和交易情况封装在类的内部,系统的其他部分没有办法直接获取或改变这些关键数据,只有通过调用类中的适当方法才能做到这一点。如调用查看余额的方法了解账户的金额,调用存取款的方法改变金额等。只要给这些方法设置严格的访问权限,就可以保证只有被授权的其他抽象数据类型才可以执行这些操作和改变当前类的状态。这样,就保证了数据安全和系统的严密。 在面向对象的程序设计中,抽象数据类型是用“类”这种面向对象的结构来实现的,每个类里都封装了相关的数据和操作。在实际的开发过程中,类在很多情况下用来构建系统内部的模块,由于封装性把类的内部数据保护得很严密,模块与模块间仅通过严格控制的界面进行交互,使模块之间耦合和交叉大大减少。从软件工程的角度看,大大降低了开发过程的复杂性,提高了开发的效率和质量,也减少了出错的可能性,同时还保证了程序中数据的完整性和安全性。 在Java语言中,对象就是对一组变量和相关方法的封装。其中,变量表明了对象的状态,方法表明了对象具有的行为。通过对象的封装,实现了模块化和信息隐藏。 1. 修饰符 为了对类对象封装,通过对类成员修饰符施以一定的访问权限,从而实现类中成员的信息隐藏。 类体定义中的一般格式为: class类名 { [变量修饰符]类型成员变量名; [方法修饰符]返回类型方法名(参数){…} } 变量修饰符有[public或protected或private]、[static]、[final]、[transient]和[volatile]。 方法修饰符有[public或protected或private]、[static]、[final或abstract]、[native]和[synchronized]。 2. 访问权限的限定 在类体成员定义的修饰符可选项中,提供了4种不同的访问权限。它们是private、default、protected和public。 1) private 在一个类中被限定为private的成员,只能被这个类本身访问,其他类无法访问。如果一个类的构造方法声明为private,则其他类不能生成该类的一个实例。 2) default 在一个类中不加任何访问权限限定的成员属于默认的(default)访问状态,可以被这个类本身和同一个包中的类所访问。 3) protected 在一个类中被限定为protected的成员,可以被这个类本身、它的子类(包括同一个包中以及不同包中的子类)和同一个包中的所有其他的类访问。 4) public 在一个类中限定为public的成员,可以被所有的类访问。 表31列出了这些限定词的作用范围。 表31Java中类的限定词的作用范围 限定词同 一 个 类同 一 个 包不同包的子类不同包非子类 private√ default√√ protected√√√ public√√√√ 3.5.2继承性 继承性是面向对象程序中两个类之间的一种关系,即一个类可以从另一个类即他的父类继承状态和行为。被继承的类(父类)也可以称为超类,继承父类的类称为子类。继承为组织和构造程序提供了一个强大而自然的机理。 一般来说,对象是以类的形式来定义的。面向对象系统允许一个类建立在其他类之上。例如,山地自行车、赛车以及双人自行车都是自行车,那么在面向对象技术中,山地自行车、赛车以及双人自行车就是自行车类的子类,自行车类是山地自行车、赛车以及双人自行车的父类。 Java不支持多重继承。 1. 子类的定义 子类定义的一般形式为: class SuperClass { …} class SubClass extends SuperClass {…} 通过继承可以实现代码复用。Java中所有的类都是通过直接或间接地继承java.lang.Object类得到的。继承而得到的类称为子类,被继承的类称为父类(又称超类)。子类不能继承父类中访问权限为private的成员变量和方法。子类可以重写父类的方法,及命名与父类同名的成员变量。 【例311】类的继承性示例,创建一个A类和它的子类B类,通过子类B的实例对象调用从父类A继承的方法。 1/* 类的继承 */ 2classA//A类为父类 3{ 4voidprint(int x,int y) 5{ 6int z=x+y; 7Strings="x+y="; 8System.out.println(s+z); 9} 10} 11classBextendsA 12{ 13Stringstr; 14voidbb() 15{ 16System.out.println(str); 17} 18} 19publicclassExample3_11 20{ 21publicstaticvoidmain(String args[]) 22{ 23B b = new B(); 24b.str = "Java学习"; 25b.bb(); 26b.print(3, 5); 27} 28} 【程序说明】 (1) 本程序创建了3个类: A、B和Example3_11。其中,A是超类; B是A的子类; 而Example3_11是运行程序的主类。 (2) 在程序的第26行,类B的对象b调用了prnt方法,但在类B中并没有定义这个方法,由于B的父类有这个方法,所以B的对象b可以调用prnt方法。 【例312】创建子类对象时,Java虚拟机首先执行父类的构造方法,然后再执行子类的构造方法。 1class A 2{ 3A() 4{ System.out.println("上层父类A的构造方法");} 5} 6class Bextends A 7{ 8B() 9{ System.out.println("父类B的构造方法");} 10} 11class Cextends B 12{ 13C() 14{ System.out.println("子类C的构造方法");} 15} 16publicclassExample3_12 17{ 18public static void main(String[] args) 19{ 20Cc = new C(); 21} 22} 程序运行结果为: 上层父类A的构造方法 父类B的构造方法 子类C的构造方法 【程序说明】 从本程序的运行结果可以看出: (1) 父类的构造方法也会被子类继承,且Java虚拟机首先执行父类的构造方法。 (2) 在多继承的情况下,创建子对象时,将从继承的最上层父类开始,依次执行各个类的构造方法。 2. 成员变量的隐藏和方法的重写 子类通过隐藏父类的成员变量和重写父类的方法,可以把父类的状态和行为改变为自身的状态和行为。 例如: class SuperClass { int x; … void setX(){ x = 0; } … } class SubClass extends SuperClass { int x; //隐藏了父类的变量x  void setX()//重写了父类的方法 setX() { x = 5; }  } 注意: 子类中重写的方法和父类中被重写的方法要具有相同的名字,相同的参数表和相同的返回类型,只是方法体不同。 如果子类重写了父类的方法,则运行时系统调用子类的方法; 如果子类继承了父类的方法(未重写),则运行时系统调用父类的方法。 【例313】子类重写了父类的方法,则在运行时,系统调用子类的方法。 1/* 子类重写了父类的方法 */ 2import java.io.*; 3class A 4{ 5void callme() 6{ 7System.out.println("调用的是A类中的callme方法"); 8} 9} 10class B extends A 11{ 12void callme() 13{ 14System.out.println("调用的是B类中的callme方法"); 15} 16} 17public class Example3_13 18{ 19public static void main(String args[]) 20{ 21A a = new B(); 22a.callme(); 23} 24} 【程序说明】 在程序的第22行,父类对象a引用的是子类的实例,所以Java运行时调用子类B的callme方法。 程序运行结果为: 调用的是B类中的callme方法 3. super关键字 Java中通过关键字super来实现对父类成员的访问,super用来引用当前对象的父类。Super的使用有以下3种情况。 (1) 访问父类被隐藏的成员变量或方法。 例如: super.variable; (2) 调用父类中被重写的方法。 例如: super.Method([paramlist]); (3) 调用父类的构造方法。由于子类不继承父类的构造方法,当要在子类中使用父类的构造方法时,则可以使用super来表示,并且super必须是子类构造方法中的第一条语句。 例如: super([paramlist]); 再如,设有一个类A: 1class A{ 2A(){ System.out.println("I am A class!");} 3A(String str){ System.out.println(str);} 4} 设B是A的子类,且B调用A的构造方法,则有 1classB extends A{ 2B() 3{super("Hello!");} 4public static void main(String[] args) 5{new B();} 6} 运行上述程序,看到B调用了A的构造方法,运行结果为: Hello! 4. this关键字 this是Java的一个关键字,表示某个对象。this可以用于构造方法和实例方法,但不能用于类方法。 (1) this用于构造方法时,代表调用该构造方法所创建的对象。 (2) this用于实例方法时,代表调用该方法的当前对象。 this的使用格式为: this.当前类的成员方法(); 或 this.当前类的成员变量; 下面是一个使用关键字this的例子。由于成员变量与函数的局部变量同名,在函数中为了分辨成员变量,则在成员变量前面用关键字this加以说明。 1class B 2{ 3int s, i; 4intmysum(int s) 5{ 6for(i = 1; i<=s; i++) 7this.s = this.s + i; 8return this.s; 9} 10} 3.5.3多态性 在面向对象程序中,多态是一个非常重要的概念。多态是指一个程序中同名的方法共存的情况,有时需要利用这种“重名”现象来提供程序的抽象性和简洁性。如前所述,一个实例由类生成,将实例以某种方式连接起来,就为模块提供了所需要的动态行为。模块的动态行为是由对象间相互通信达成的,多态的含义是一个消息可以与不同的对象结合,而且这些对象属于不同类。同一消息可以用不同方法解释,方法的解释依赖于接收消息的类,而不依赖发送消息的实例。多态通常是一个消息在不同的类中用不同方法实现的。 多态的实现是由消息的接收者确定一个消息应如何解释,而不是由消息的发送者确定的。消息的发送者只需要指导另外的实例可以执行一种特定操作即可,这一特性对于扩充系统的开发是特别有效的。按这种方法可开发易于维护、可塑性好的系统。例如,如果希望加一个对象到类中,这种维护只涉及新对象,而不涉及给它发送消息的对象。 在Java语言中,多态性体现在两方面: 由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(运行时多态)。 1. 编译时多态 在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。 2. 运行时多态 由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象代替。一个对象可以通过引用子类的实例调用子类的方法。 3.5.4其他修饰符的用法 1. final关键字 final关键字可以修饰类、类的成员变量和成员方法,但对其作用各不相同。 1) final修饰成员变量 可以用final修饰变量,目的是为了防止变量的内容被修改,经final修饰后,变量就成为了常量,其形式为: final类型 变量名; 用final修饰成员变量时,在定义变量的同时就要给出初始值。 2) final修饰成员方法 当一个方法被final修饰后,则该方法不能被子类重写,其形式为: final返回类型 方法名(参数) {  } 3) final修饰类 一个类用final修饰后,则该类不能被继承,即不能成为超类,其形式为: final class类名 {  } 2. static关键字 在Java语言中,成员变量和成员方法可以进一步分为两种: 类成员和实例成员。用关键字static修饰的变量或方法称为类变量和类方法; 没有用关键字static修饰的变量或方法称为实例变量或实例方法。 用static关键字声明类变量和类方法的格式如下。 static 类型 变量名; static 返回类型 方法名(参数) {  } 如果在声明时用static关键字修饰,则声明为静态变量和静态方法。调用静态方法时,不要进行实例化而直接调用。 【例314】说明静态方法的调用。 1/* 静态方法的调用*/ 2class B 3{ 4public static void p() 5{ 6System.out.println("I am B!"); 7} 8} 9classExample3_14 10{ 11public static void main(String[] args) 12{ 13B.p(); 14} 15} 16/* 如果类B中的p()没有声明为static,则必须在Example3_14中对B进行实例化,否则编译不能 17通过 18class B{ 19publicvoid p(){ 20System.out.println("I am B!"); 21} 22} 23classExample3_14{ 24public static void main(String[] args) { 25B b=new B(); 26b.p(); 27} 28} 29*/ 【程序说明】 (1) 在程序的第2~8行定义了B类,其方法p()是用static修饰的静态方法,所以类Example3_14在第13行可以直接调用p方法,而没有对B进行实例化。 (2) 在程序的第19~28行,说明了由于类B中的p方法没有声明为static,所以它是实例方法,因此在Example3_14调用时,必须先对B进行实例化(第25行和第26行)。 3. 类成员与实例成员的区别 类成员与实例成员的区别如下。 1) 实例变量和类变量 每个对象的实例变量都分配内存,通过该对象来访问这些实例变量,不同的实例变量是不同的。 类变量仅在生成第一个对象时分配内存,所有实例对象共享同一个类变量,每个实例对象对类变量的改变都会影响其他的实例对象。类变量可通过类名直接访问,无须先生成一个实例对象,也可以通过实例对象访问类变量。 2) 实例方法和类方法 实例方法可以对当前对象的实例变量进行操作,也可以对类变量进行操作,实例方法由实例对象调用。 但类方法不能访问实例变量,只能访问类变量。类方法可以由类名直接调用,也可由实例对象进行调用。类方法中不能使用this或super关键字。 【例315】实例成员和类成员的区别。 1class Member 2{ 3static int classVar; 4int instanceVar; 5static void setClassVar(int i) 6{ 7classVar=i; 8//instanceVar=i;//该语句错误,因为类方法不能访问实例变量 9} 10static int getClassVar() 11{ 12return classVar; 13} 14void setInstanceVar(int i) 15{ 16classVar=i;//实例方法不但可以访问类变量,也可以访问实例变量 17instanceVar=i; 18} 19int getInstanceVar() 20{ 21return instanceVar; 22} 23} 24public class Example3_15 25{ 26public static void main(String args[]) 27{ 28Member m1=new Member(); 29Member m2=new Member(); 30m1.setClassVar(1); 31m2.setClassVar(2); 32System.out.println("m1.classVar="+m1.getClassVar() 33+"m2.ClassVar="+m2.getClassVar()); 34m1.setInstanceVar(11); 35m2.setInstanceVar(22); 36System.out.println("m1.InstanceVar="+m1.getInstanceVar() 37+"m2.InstanceVar="+m2.getInstanceVar()); 38} 39} 4. abstract关键字(抽象类) Java语言中,用关键字abstract修饰一个类时,这个类叫作抽象类。 抽象类用来描述对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但本质上相同的具体概念的抽象。例如,如果进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念。从外形上看它们是不同的,但从概念方面来看,它们都属于形状这样一个概念。形状这个概念在问题领域是不存在的,是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能实例化的。 抽象类的式如下。 abstract class类名//定义抽象类 { 成员变量; 方法(); //定义普通方法 abstract方法(); //定义抽象方法 } 抽象类是专门设计用来让子类继承的类。用abstract关键字修饰一个方法时,这个方法叫作抽象方法。抽象类具有以下特点。 (1) 抽象类中可以包含普通方法,也可以包含抽象方法; 可以只有普通方法,没有抽象方法。 (2) 抽象类中的抽象方法是只有方法声明,没有代码实现的空方法。 (3) 抽象类不能被实例化。 (4) 若某个类包含了抽象方法,则该类必须被定义为抽象类。 (5) 由于抽象方法是没有完成代码实现的空方法,因此抽象类的子类必须重写父类定义的每一个抽象方法。 【例316】抽象类应用示例。 1/* 抽象类 */ 2abstractclass生物 3{ 4publicabstract String 特征(); 5} 6 7class植物extends生物//植物是生物的子类 8{ 9String leaf; 10植物(String _leaf) 11{this.leaf=_leaf;} 12public String 特征() 13{return leaf;} 14} 15 16class动物extends生物//动物是生物的子类 17{ 18String mouth; 19动物(String _mouth) 20{this.mouth=_mouth;} 21public String 特征() 22{return mouth;} 23} 24 25publicclassExample3_16 26{ 27publicstaticvoidmain(String args[]) 28{ 29植物 A = new 植物("叶"); 30System.out.println("植物的特征: "+A.特征()); 31动物 B = new 动物("嘴巴"); 32System.out.println("动物的特征: "+B.特征()); 33} 34} 程序运行结果为: 植物的特征: 叶 动物的特征: 嘴巴 3.6接口 3.6.1接口的定义 接口是抽象类的一种,只包含常量和方法的定义,而没有变量和具体方法的实现,且其方法都是抽象方法。它的用处体现在以下方面。 (1) 通过接口实现不相关类的相同行为,而无须考虑这些类之间的关系。 (2) 通过接口指明多个类需要实现的方法。 (3) 通过接口了解对象的交互界面,而无须了解对象所对应的类。 1. 接口定义的一般形式 接口的定义包括接口声明和接口体。 接口定义的一般形式如下。 [public] interface接口名[extends父接口名] { //接口体 } extends子句与类声明的extends子句基本相同; 不同的是一个接口可有多个父接口,用逗号隔开,而一个类只能有一个父类。 2. 接口的实现 在类的声明中用implements子句表示一个类使用某个接口,在类体中可以使用接口中定义的常量,而且必须实现接口中定义的所有方法。一个类可以实现多个接口,在implements子句中用逗号分开。 【例317】设原来拟编写一个超类Prnting,其中定义了一个prnt方法供子类继承,现将其改成接口,由子类实现该接口。 1/* 接口的使用 2//设拟编写一个超类Prnting,其中定义了一个prnt方法 3classPrnting 4{ 5voidprnt() 6{ 7System.out.println("蔬菜和水果都重要"); 8 } 9}*/ 10//下面要将其改成接口 11//将一个类改成接口,只要把class换成interface,再把其所有方法的内容都抽掉 12interface Prnting 13{ 14voidprnt(); 15} 16public class Example3_17 implements Prnting//实现接口,重写prnt方法 17{ 18public void prnt()//注意,public不能缺少 19{ 20System.out.println("蔬菜重要。"); 21} 22} 3.6.2接口的应用 接口的定义及语法规则很简单,但真正要理解接口不那么容易。从例316可以看到,可以用一个类完成同样的事情。那么,为什么要用接口呢? 例如,项目开发部需要帮助一个用户单位编写管理程序,项目主管根据用户需求,把应用程序需要实现的各个功能都做成接口形式,然后分配给项目组其他成员编写具体的功能。 【例318】管理程序具有查询数据、添加数据、删除数据等功能接口的应用示例。 1interface DataOption 2{ 3public void dataSelect();//查询数据 4public void dataAdd();//添加数据 5public void dataDel();//删除数据 6} 7class DataManagement implements DataOption 8{ 9public void dataSelect() 10{ 11System.out.println("查询数据"); 12} 13public void dataAdd() 14{ 15System.out.println("添加数据"); 16} 17public void dataDel() 18{ 19System.out.println("删除数据"); 20} 21} 22public class Example3_18 23{ 24public static void main(String args[]) 25{ 26DataManagement data=new DataManagement(); 27data.dataSelect(); 28data.dataAdd(); 29data.dataDel(); 30} 31} 【例319】首先,编写一个接口程序,并在其中定义一个计算面积的方法; 再设计应用程序实现这个接口,分别计算矩形面积和圆的面积。 1interface Area 2{ 3public double area(); 4} 5class A implements Area 6{ 7int x, y; 8void set_xy(int x, int y) 9{ 10this.x=x; 11this.y=y; 12} 13public double area() 14{ 15double s; 16s=x*y; 17return s; 18} 19} 20class B implements Area 21{ 22int r; 23void set_r(int r) 24{ 25this.r=r; 26} 27public double area() 28{ 29double s; 30s=3.14*r*r; 31return s; 32} 33} 34 35class ex3_19 36{ 37public static void main(String args[]) 38{ 39int x=10, y=5; 40double ss1, ss2; 41A aa=new A(); 42aa.set_xy(x,y); 43ss1=aa.area(); 44System.out.println("矩形面积="+ss1); 45int r=5; 46B bb = new B(); 47bb.set_r(r); 48ss2=bb.area(); 49System.out.println("圆面积="+ss2); 50} 51} 3.7包 在Java语言中,每个类都会生成一个字节码文件,该字节码文件名与类名相同。这样,可能会发生同名类的冲突。为了解决这个问题,Java采用包来管理类名空间。包不仅提供了一种类名管理机制,也提供了一种面向对象方法的封装机制。包将类和接口封装在一起,方便了类和接口的管理与调用。例如,Java的基础类都封装在java.lang包中,所有与网络相关的类都封装在java.net包中。程序设计人员也可以将自己编写的类和接口封装到一个包中。 3.7.1创建自己的包 1. 包的定义 为了更好地组织类,Java提供了包机制。包是类的容器,用于分隔类名空间。把一个源程序归入某个包的方法用package来实现。 package语句的一般形式为: package 包名; 包名有层次关系,包名的层次必须与Java开发系统的文件系统目录结构相同。简言之,包名就是类所在的目录路径,各层目录之间以.符号分隔。通常包名用小写字母表示,这与类名以大写字母开头的命名约定有所不同。 在源文件中,package是源程序的第一条语句。 例如,要编写一个MyTest.java源文件,并且文件存放在当前运行目录的子目录abc\test下,则: package abc.test; public class MyTest {  } 在源文件中,package是源程序的第一条语句。包名一定是当前运行目录的子目录。一个包内的Java代码可以访问该包的所有类及类中的非私有变量和方法。 2. 包的引用 要使用包中的类,必须用关键字import导入这些类所在的包。 import语句的一般形式为: import包名.类名; 当要引用包中所有的类或接口时,类名可以用通配符*代替。 图314包的文件目录结构 【例320】创建一个自己的包。 本例所创建包的文件目录结构如图314所示。 (1) 在当前运行目录下创建一个子目录结构abc\test,在子目录下存放已经编译成字节码文件的MyTest .class类,其MyTest.class类的源程序为: 1package abc.test; 2public class MyTest 3{ 4public void prn() 5{ 6System.out.println("包的功能测试"); 7} 8} (2) 在当前目录的PackageTest.java中,要使用子目录abc\test下有MyTest.class类中的prn方法,则其源程序为: 1import abc.test.MyTest; 2public class PackageTest 3{ 4public static void main(String args[]) 5{ 6MyTest mt = new Mytest(); 7mt.prn(); 8} 9} 3.7.2压缩文件jar 1. 将类压缩为jar文件 在Java提供的工具集bin目录下有一个jar.exe文件,它可以把多个类的字节码文件打包压缩成一个jar文件,将这个jar文件存放到Java运行环境的扩展框架中,即将该jar文件存放在JDK安装目录的jre\lib\ext下,这样,其他的程序就可以使用这个jar文件中的类来创建对象了。 设有两个字节码文件Test1.class和Test2.class,要将它们压缩成一个jar文件Test.jar。 (1) 编写Manifest.mf清单文件。 Manifest-Version: 1.0 Main-Class:Test1Test2 注意: MainClass与后面的类名之间要有一个空格,且最后一行要按Enter键换行。将其保存为Manifest.mf。 (2) 生成jar文件。 JarcfmTest.jarManifest.mfTest1.classTest2.class 其中,参数c表示要生成一个新的jar文件; f表示要生成jar文件的文件名; m表示清单文件的文件名。 2. 将应用程序压缩为jar文件 可以用jar.exe将应用程序生成可执行文件。在Windows环境下,双击该文件,就可以运行该应用程序。 其生成jar文件的步骤与前面生成类的jar文件相同。要压缩多个类时,在清单文件中只写出主类的类名,设有主类A.class,则: Manifest-Version: 1.0 Main-Class: A 生成jar文件时,也可以使用通配*.class。 jarcfmTest.jarManifest.mf*.class 需要注意的是,如果计算机上安装了WinRAR解压软件,则无法通过双击该文件的办法执行程序,可以编写一个批处理文件: javaw-jar Test.jar 将其保存为test.bat,双击这个批处理文件就可以运行应用程序。 实验3 【实验目的】 (1) 掌握类的声明,对象的创建以及方法的定义和调用。 (2) 掌握打包机制。 (3) 掌握类的继承。 (4) 掌握类接口的使用。 【实验内容】 (1) 运行下列程序,并写出其输出结果。 //Father.java: package tom.jiafei; public classFather {intheight; protected int money; publicint weight; public Father(int m) {money=m; } protected int getMoney() {return money; } void setMoney(int newMoney) {money=newMoney; } } //Jerry.java: import tom.jiafei.Father; public class Jerry extends Father//Jerry和Father在不同的包中 {public Jerry() {super(20); } public static void main(String args[]) {Jerryjerry=new Jerry(); jerry.height=12;//非法的 jerry.weight=200; jerry.money=800; int m=jerry.getMoney(); jerry.setMoney(300);//非法的 System.out.println("m="+m); } } (2) 运行下列程序,并写出其输出结果。 interfaceShowMessage {void 显示商标(String s); } class TV implements ShowMessage {public void 显示商标(String s) {System.out.println(s); } } class PC implements ShowMessage {public void 显示商标(String s) {System.out.println(s); } } public class Ex3_2 {public static void main(String args[]) {ShowMessage sm;//声明接口变量 sm=new TV();//接口变量中存放对象的引用 sm.显示商标("长城牌电视机");//接口回调 sm=new PC();//接口变量中存放对象的引用 sm.显示商标("联想奔月5008PC机");//接口回调 } } (3) 编写一个应用程序,求50以内的素数,并将其打包; 另写一个应用程序调用该包,在以下3种情况下,实现类之间的调用。 ① 在同一目录下。 ② 在不同目录下。 ③ 在不同盘符下。 习题3 1. 什么是Java程序使用的类?什么是类库? 2. 如何定义方法?在面向对象程序设计中方法有什么作用? 3. 简述构造方法的功能和特点。下面的程序片断是某学生为student类编写的构造方法,请指出其中的错误。 void Student(int no, String name) { studentNo = no; studentName = name; return no; } 4. 定义一个表示学生的类student,包括的成员变量有学号、姓名、性别、年龄,包括的成员方法有获得学号、姓名、性别、年龄和修改年龄。书写Java程序创建student类的对象及测试其方法的功能。 5. 扩充、修改程序。为第(4)题的student类定义构造方法初始化所有的成员,增加一个方法public String printInfo(),该方法将student类对象的所有成员信息组合形成一个字符串,并在主类中创建学生对象及各方法的功能。 6. 什么是修饰符?修饰符的种类有哪些?它们各有什么作用? 7. 什么是抽象类,为什么要引入抽象类的概念? 8. 什么是抽象方法?如何定义、使用抽象方法? 9. 包的作用是什么?如何在程序中引入已定义的类?使用已定义的用户类、系统类有哪些主要方式? 10. 什么是继承?如何定义继承关系? 11. 什么是多态?如何实现多态? 12. 解释this和super的意义和作用。 13. 什么是接口?为什么要定义接口?接口和类有什么异同? 14. 将一个抽象类改写成接口,并实现这个接口。 15. 编写一个程序实现包的功能。 16. 填空。 ① 如果类A继承了类B,则类A被称为类,类B被称为类。 ② 继承使成为可能,它节省了开发时间。 ③ 如果一个类包含一个或多个abstract方法,它就是一个类。 ④ 一个子类一般比其超类封装的功能要。 ⑤ 标记成类的成员不能由该类的方法访问。 ⑥ Java用关键字指明继承关系。 ⑦ this代表了的引用。 ⑧ super表示的是当前对象的对象。 ⑨ 抽象类的修饰符是。 ⑩ 接口中定义的数据成员是。 接口中没有什么方法,所有的成员方法都是方法。 17. 编写一个接口程序,并在其中定义一个计算体积的方法; 再设计应用程序实现这个接口,分别计算矩形柱面体积和圆形柱面体积。 18. 编写一个Plus类,计算2+4+6+8+…+100的和,并将其打包,另写一个应用程序调用该包,在以下两种情况下,实现类之间的调用。 ① Plus类与应用程序在同一目录下。 ② Plus类在应用程序所在目录下的子目录com/examp/plus/ 下。 19. 编写一个应用程序,用jar.exe将类压缩为jar可执行文件。