第5章类与对象 视频讲解 主要内容  面向对象的特性;  类;  构造方法与对象的创建;  参数传值;  对象的组合;  实例成员与类成员;  方法重载与多态;  this关键字;  包;  import语句;  访问权限。 面向对象语言有三个重要特性: 封装、继承和多态。学习面向对象编程要掌握怎样通过抽象得到类,继而学习怎样编写类的子类来体现继承和多态。本章主要讲述类和对象,即学习面向对象的第一个特性——封装,第6章学习面向对象的另外两个特性——继承和多态。 视频讲解 5.1面向对象的特性 随着计算机硬件设备功能的进一步提高,面向对象的编程成为可能。面向对象的编程更加符合人的思维模式,编写的程序更加健壮和强大,更重要的是,面向对象编程鼓励创造性的程序设计。面向对象编程是一种先进的编程思想,更加容易解决复杂的问题。面向对象编程主要具有下面三个特性。 1. 封装性 面向对象编程的核心思想之一是将数据和对数据的操作封装在一起。通过抽象,即从具体的实例中抽取共同的性质,形成一般的概念,例如类的概念。 在实际生活中,我们每时每刻都在与“对象”打交道,例如我们用的钢笔,骑的自行车,乘的公共汽车等。而我们经常见到的卡车、公共汽车、轿车等都会涉及以下几个重要的物理量: 可乘载的人数、运行速度、发动机的功率、耗油量、自重、轮子数目等; 另外,还有几个重要的功能: 加速功能、减速功能、刹车、转弯功能等。可以把这些功能称作它们具有的方法,而物理量是它们的状态描述,仅仅用物理量或功能不能很好地描述它们。在现实生活中,对这些共有的属性和功能给出一个概念——机动车类。也就是说,人们经常谈到的机动车类就是从具体的实例中抽取共同的属性和功能形成的一个概念,那么一个具体的轿车就是机动车类的一个实例,即对象。一个对象将自己的数据和对这些数据的操作合理有效地封装在一起,例如,每辆轿车调用“加大油门”改变的都是自己的运行速度。 2. 继承性 继承体现了一种先进的编程模式。子类可以继承父类的属性和功能,即继承了父类具有的数据和数据上的操作,同时又可以增添子类独有的数据和数据上的操作。例如,“人类”自然继承了“哺乳类”的属性和功能,同时又增添了人类独有的属性和功能。 3. 多态性 多态是面向对象编程的又一重要特征。有两种意义的多态。一种多态是操作名称的多态,即有多个操作具有相同的名字,但这些操作所接收的消息类型必须不同。例如,让一个人执行“求面积”操作时,他可能会问你求什么面积。所谓操作名称的多态,是指可以向操作传递不同消息,以便让对象根据相应的消息来产生一定的行为。另一种多态是和继承有关的多态,是指同一个操作被不同类型对象调用时可能产生不同的行为。例如,狗和猫都具有哺乳类的功能——“喊叫”,当狗操作“喊叫”时产生的声音是“汪汪…… ”,而当猫操作“喊叫”时产生的声音是“喵喵……”。 视频讲解 5.2类 类是组成Java程序的基本要素,一个Java应用程序就是由若干个类构成的(见第2章)。类是Java语言中最重要的“数据类型”,类声明的变量被称作对象,即类是用来创建对象的“模板”。 类的定义包括两部分: 类声明和类体。基本格式为: class类名{ 类体的内容 } class是关键字,用来定义类。“class 类名”是类的声明部分,类名必须是合法的Java标识符。两个大括号以及之间的内容是类体。 5.2.1类声明 以下是两个类声明的例子。 class People { … } class植物{ … } “class People”和“class植物”叫作类声明; “People”和“植物”分别是类名。类的名字要符合标识符规定,即名字可以由字母、下画线、数字或美元符号组成,并且第一个字符不能是数字(这是语法要求的)。给类命名时,遵守下列编程风格(这不是语法要求的,但应当遵守)。 (1) 如果类名使用拉丁字母,那么名字的首字母使用大写字母,如Hello、Time等。 (2) 类名最好容易识别、见名知义。当类名由几个单词复合而成时,每个单词的首字母大写,如ChinaMade、AmericanVehicle、HelloChina等。 5.2.2类体 定义类的目的是根据抽象描述一类事物共有的属性和功能,即给出用于创建具体实例(对象)的一种数据类型,描述过程由类体实现。类声明之后的一对大括号“{”、“}”以及它们之间的内容称作类体,大括号之间的内容称作类体的内容。 抽象的关键是抓住事物的两个方面: 属性和功能,即数据以及在数据上进行的操作。因此,类体的内容由两部分构成。  变量的声明: 用来描述数据(体现对象的属性)。  方法: 方法可以对类中声明的变量进行操作,即给出算法(体现对象具有的功能)。 下面是一个类名为Ladder的类(用来描述梯形),类体内容的变量定义部分定义了4个float类型变量: above、bottom、height和area,方法定义部分定义了两个方法: computerArea和setHeight。 class Ladder { float above;//梯形的上底(变量声明) float bottom;//梯形的下底(变量声明) float height;//梯形的高(变量声明) float area;//梯形的面积(变量声明) float computerArea() {//计算面积(方法) area=(above+bottom)*height/2.0f; return area; } void setHeight(float h) {//修改高(方法) height=h; } } 5.2.3成员变量 类体分为两部分: 一部分是变量的声明; 另一部分是方法的定义。变量声明部分声明的变量被称作域变量或成员变量。 1. 成员变量的类型 成员变量的类型可以是Java中的任何一种数据类型,包括基本类型: 整型、浮点型、字符型; 引用类型: 数组、对象和接口(对象和接口见后续内容)。例如: class Factory { float a[]; Workman zhang; } class Workman { double x; } Factory类的成员变量a是类型为float的数组,zhang是Workman类声明的变量,即对象。 2. 成员变量的有效范围 成员变量在整个类的所有方法中都有效,其有效性与它在类体中书写的先后位置无关。例如,前述的Ladder类也可以等价地写成: class Ladder { float above;//梯形的上底(变量声明) float area ;//梯形的面积(变量声明) float computerArea() {//计算面积(方法) area=(above+bottom)*height/2.0f; return area; } float bottom ;//梯形的下底(变量声明) void setHeight(float h) {//修改高(定义) height=h; } float height;//梯形的高(变量声明) } 不提倡把成员变量的定义分散地写在方法之间或类体的最后,人们习惯先介绍属性再介绍功能。 3. 编程风格 (1) 一行只声明一个变量。我们已经知道,尽管可以使用一种数据的类型、用逗号分隔来声明若干个变量,例如: float above,bottom; 但是在编码时却不提倡这样做(本书中某些代码可能没有严格遵守这个风格,是为了减少代码行数,降低书的成本),其原因是不利于给代码增添注释内容。提倡的风格是: float above; //梯形上底 float bottom; //梯形下底 (2) 变量的名字除了符合标识符规定外,名字的首单词的首字母使用小写; 如果变量的名字由多个单词组成,从第2个单词开始的其他单词的首字母使用大写。 (3) 变量的名字应见名知义,避免使用诸如m1、n1等作为变量的名字,尤其是名字中不要将小写的英文字母l和数字1相邻接,人们很难区分“l1”和“ll”。 5.2.4方法 我们已经知道一个类的类体由两部分组成: 变量的声明和方法的定义。方法的定义包括两部分: 方法声明和方法体。一般格式为: 方法声明部分{ 方法体的内容 } 1. 方法声明 最基本的方法声明包括方法名和方法的返回类型,例如: double getSpeed() { return speed; } 根据程序的需要,方法返回的数据的类型可以是Java中的任何一种数据类型; 当一个方法不需要返回数据时,返回类型必须是void。很多的方法声明中都给出方法的参数,参数是用逗号隔开的一些变量声明。方法的参数可以是任意的Java数据类型。 方法的名字必须符合标识符规定,给方法起名字的习惯和给变量起名字的习惯类似。例如,如果名字使用拉丁字母,首字母小写; 如果名字由多个单词组成,从第2个单词开始的其他单词的首字母大写。 2. 方法体 方法声明之后的一对大括号“{” 、“}”以及之间的内容称作方法的方法体。方法体的内容包括局部变量的声明和Java语句,即在方法体内可以对成员变量和该方法体中声明的局部变量进行操作。在方法体中声明的变量和方法的参数被称作局部变量,例如: int getSum(int n) {// 参数变量n是局部变量 int sum=0;// 声明局部变量sum for(int i=1;i<=n;i++) {// for循环语句 sum=sum+i; } return sum;// return 语句 } 和类的成员变量不同的是,局部变量只在声明它的方法内有效,而且与其声明的位置有关。方法的参数在整个方法内有效,方法内的局部变量从声明它的位置之后开始有效。如果局部变量的声明是在一条复合语句中,那么该局部变量的有效范围是该复合语句,即仅在该复合语句中有效; 如果局部变量的声明是在一个循环语句中,那么该局部变量的有效范围是该循环语句,即仅在该循环语句中有效。例如: public class A { int m=10,sum=0;//成员变量,在整个类中有效 void f() { if(m>9) { int z=10;//z仅仅在该复合语句中有效 z=2*m+z; } for(int i=0;i0){ radius=r; } } double getRadius(){ return radius; } double getArea(){ area=3.14*radius*radius; return area; } } Example5_4.java public class Example5_4 { public static void main(String args[]) { Circle circle=new Circle(); double w=121.709; circle.setRadius(w); System.out.println("圆的半径: "+circle.getRadius()); System.out.println("圆的面积: "+circle.getArea()); System.out.println("更改向方法参数r传递值的w的值为100"); w=100; System.out.println("w="+w); System.out.println("圆的半径: "+circle.getRadius()); } } 5.4.3引用类型参数的传值 Java的引用型数据包括前面学习的数组、刚刚学习的对象以及后面将要学习的接口。当参数是引用类型时,“传值”传递的是变量中存放的“引用”,而不是变量引用的实体。 需要注意的是,对于两个同类型的引用型变量,如果具有同样的引用,就会用同样的实体。因此,如果改变参数变量引用的实体,就会导致原变量的实体发生同样的变化; 但是,改变参数中存放的“引用”不会影响向其传值的变量中存放的“引用”,反之亦然,如图5.9所示。 例5.5中涉及引用类型参数,请注意程序的运行效果。例5.5中除了使用例5.4中的Circle类外,还需要一个Circular类(刻画圆锥)和一个主类。程序在主类的main方法中首先使用Circle类创建一个“圆”对象circle,然后使用Circular类创建一个圆锥对象circular,在创建圆锥对象circular时,需要将先前Circle类的实例circle,即“圆”对象的引用,传递给圆锥对象的成员变量bottom。程序运行效果如图5.10所示。 图5.9引用类型参数的传值 图5.10向参数传递对象的引用 【例5.5】 Circular.java public class Circular { Circle bottom; double height; Circular(Circle c,double h) {//构造方法,将Circle类的实例的引用传递给bottom bottom = c; height = h; } double getVolume() { return bottom.getArea()*height/3.0; } double getBottomRadius() { return bottom.getRadius(); } public void setBottomRadius(double r){ bottom.setRadius(r); } } Example5_5.java public class Example5_5 { public static void main(String args[]) { Circle circle = new Circle(10);//【代码1】 System.out.println("main方法中circle的引用:"+circle); System.out.println("main方法中circle的半径"+circle.getRadius()); Circular circular = new Circular(circle,20);//【代码2】 System.out.println("circular圆锥的bottom的引用:"+circular.bottom); System.out.println("圆锥的bottom的半径:"+circular.getBottomRadius()); System.out.println("圆锥的体积:"+circular.getVolume()); double r = 8888; System.out.println("圆锥更改底圆bottom的半径:"+r); circular.setBottomRadius(r);//【代码3】 System.out.println("圆锥的bottom的半径:"+circular.getBottomRadius()); System.out.println("圆锥的体积:"+circular.getVolume()); System.out.println("main方法中circle的半径:"+circle.getRadius()); System.out.println("main方法中circle的引用将发生变化"); circle = new Circle(1000); //重新创建circle 【代码4】 System.out.println("现在main方法中circle的引用:"+circle); System.out.println("main方法中circle的半径:"+circle.getRadius()); System.out.println("但是不影响circular圆锥的bottom的引用"); System.out.println("circular圆锥的bottom的引用:"+circular.bottom); System.out.println("圆锥的bottom的半径:"+circular.getBottomRadius()); } } 我们对例5.5中的Example5_5.java中的重要的、需要理解的代码给出了代码1~代码4注释,以下结合对象的内存模型,对这些重要的代码给予讲解。 1) 执行代码1后内存中的对象模型 执行代码1: Circle circle = new Circle(10); 后,内存中诞生了一个circle对象,内存中对象的模型如图5.11所示。 2) 执行代码2后内存中的对象模型 执行代码2: Circular circular = new Circular(circle,20); 后,内存中又诞生了一个circular对象。执行代码2将circle对象的引用以“传值”方式传递给circular对象的bottom,因此,circular对象的bottom和circle对象就有同样的实体(radius)。内存中对象的模型如图5.12所示。 图5.11执行代码1后内存中的对象模型 图5.12执行代码2后内存中的对象模型 3) 执行代码3后内存中的对象模型 对于两个同类型的引用型变量,如果具有同样的引用,就会用同样的实体。因此,如果改变参数变量所引用的实体,就会导致原变量的实体发生同样的变化。 执行代码3: circular.setBottomRadius(r); 就使得circular的bottom和circle的实体(radius)发生了同样的变化,如图5.13所示。 4) 执行代码4后内存中的对象模型 执行代码4: circle = new Circle(1000); 使得circle的引用发生变化,重新创建了circle对象,即circle对象将获得新的实体(circle对象的radius的值是1000),但circle先前的实体不被释放,因为这些实体还是circular的bottom的实体。最初circle对象的引用是以传值方式传递给circular对象的bottom的,所以,circle的引用发生变化并不影响circular的bottom的引用(bottom对象的radius的值仍然是8888)。对象的模型如图5.14所示。 图5.13执行代码3后内存中的对象模型 图5.14执行代码4后内存中的对象模型 视频讲解 5.5对象的组合 我们已经知道,一个类的成员变量可以是Java允许的任何数据类型。因此,一个类可以把对象作为自己的成员变量,如果用这样的类创建对象,那么该对象中就会有其他对象。也就是说,该对象将其他对象作为自己的组成部分,这就是人们常说的HasA。例如,前面的例5.5中的圆锥对象就将一个圆对象作为自己的成员,即圆锥有一个圆底。 5.5.1由矩形和圆组合而成的图形 一个矩形和一个圆可以组合成各种形状的几何图形,如图5.15所示。 图5.15对象geometry是圆和矩形的组合 在例5.6中,一共编写了4个类,分成4个源文件: Rectangle.java、Circle.java、Geometry.java和Example5_6.java,需要将这4个源文件分别编辑,并保存在相同的目录(例如C:\ch5)中。  Rectangle.java中的Rectangle类有double型的成员变量x、y、width、height,分别用来表示矩形左上角的位置坐标以及矩形的宽和高。该类提供了修改x、y、width、height以及返回x、y、width、height的方法。  Circle.java中的Circle类有double型的成员变量x、y、radius,分别用来表示对象的圆心坐标和圆的半径。该类提供了修改x、y、radius以及返回x、y、radius的方法。  Geometry.java中的Geometry类有Rectangle类型和Circle类型的成员变量,名字分别为rect和circle。也就是说,Geometry类创建的对象(几何图形)是由一个Rectangle对象和一个Circle对象组合而成。该类提供了修改rect、circle位置和大小的方法,提供了显示rect和circle位置关系的方法。  Example5_6.java含有主类,主类在main方法中用Geometry类创建对象geometry,该对象调用相应的方法设置其中的圆的位置和半径、矩形的位置以及宽和高。 例5.6的运行效果如图5.16所示。 图5.16圆和矩形组合的对象 【例5.6】 Rectangle.java public class Rectangle { double x,y,width,height; public void setX(double a) { x=a; } public double getX() { return x; } public void setY(double b) { y=b; } public double getY(){ return y; } public void setWidth(double w) { if(w > 0) width=w; } public double getWidth(){ return width; } public void setHeight(double h) { if(h > 0) height=h; } public double getHeight() { return height; } } Circle.java public class Circle { double x,y,radius; public void setX(double a) { x=a; } public double getX() { return x; } public void setY(double b){ y=b; } public double getY() { return y; } public void setRadius(double r){ if(r >0 ) radius=r; } public double getRadius(){ return radius; } } Geometry.java public class Geometry { Rectangle rect; Circle circle; Geometry(Rectangle rect,Circle circle){ this.rect=rect; this.circle=circle; } public void setCirclePosition(double x,double y){ circle.setX(x); circle.setY(y); } public void setCircleRadius(double radius){ circle.setRadius(radius); } public void setRectanglePosition(double x,double y){ rect.setX(x); rect.setY(y); } public void setRectangleWidthAndHeight(double w,double h){ rect.setWidth(w); rect.setHeight(h); } public void showState(){ double circleX=circle.getX(); double rectX=rect.getX(); if(rectX-rect.getWidth()>=circleX+circle.getRadius()) System.out.println("矩形在圆的右侧"); if(rectX+rect.getWidth()<=circleX-circle.getRadius()) System.out.println("矩形在圆的左侧"); } } Example5_6.java public class Example5_6 { public static void main(String args[]){ Rectangle rect=new Rectangle(); Circle circle=new Circle(); Geometry geometry; geometry=new Geometry(rect,circle); geometry.setRectanglePosition(30,40); geometry.setRectangleWidthAndHeight(120,80); geometry.setCirclePosition(260,30); geometry.setCircleRadius(60); System.out.print("几何图形中圆和矩形的位置关系是: "); geometry.showState();//显示圆和矩形的位置关系 System.out.println("几何图形重新调整了圆和矩形的位置。"); geometry.setRectanglePosition(220,160); geometry.setCirclePosition(40,30); System.out.print("调整后,几何图形中圆和矩形的位置关系是: "); geometry.showState();//显示圆和矩形的位置关系 } } 5.5.2关联关系和依赖关系的UML图 1. 关联关系 如果A类中成员变量是用B类声明的对象,那么A和B的关系是关联关系,称A关联于B或A组合了B。如果A关联于B,那么通过使用一条实线连A和B的UML图,实线的起始端是A的UML图,终点端是B的UML图,但终点端使用一条指向B的UML图的方向箭头表示实线的结束。 图5.17是例5.5中Circular类关联于Circle类的UML图。 2. 依赖关系 如果A类中某个方法的参数是用B类声明的对象,或者某个方法返回的数据类型是B类对象,那么A和B的关系是依赖关系,称A依赖于B。如果A依赖于B,那么通过使用一条虚线连A和B的UML图,虚线的起始端是A的UML图,终点端是B的UML图,但终点端使用一条指向B的UML图的方向箭头表示虚线的结束。 图5.18是ClassRoom依赖于Student的UML图。 图5.17关联关系的UML图 图5.18依赖关系的UML图 注: 在Java中,习惯上将A关联于B也称作A依赖于B,当需要强调A是通过方法参数依赖于B时,就在UML图中使用虚线连接A和B的UML图。 视频讲解 5.6实例成员与类成员 5.6.1实例变量和类变量的声明 在讲述类的时候我们讲过,类体中包括成员变量的声明和方法的定义,而成员变量又可细分为实例变量和类变量。在声明成员变量时,用关键字static给予修饰的称作类变量(类变量也称为static变量、静态变量); 否则称作实例变量。例如: class Dog { float x;//实例变量 static int y;//类变量 } Dog类中,x是实例变量,而y是类变量。需要注意的是,static需放在变量的类型的前面。 5.6.2实例变量和类变量的区别 1. 不同对象的实例变量互不相同 我们已经知道,一个类通过使用new运算符可以创建多个不同的对象,这些对象将被分配不同的(成员)变量,说得准确些就是: 分配给不同的对象的实例变量占有不同的内存空间,改变其中一个对象的实例变量不会影响其他对象的实例变量。 2. 所有对象共享类变量 如果类中有类变量,当使用new运算符创建多个不同的对象时,分配给这些对象的这个类变量占有相同的一处内存,改变其中一个对象的这个类变量会影响其他对象的这个类变量。也就是说对象共享类变量。 3. 通过类名直接访问类变量 我们知道,当Java程序执行时,类的字节码文件被加载到内存,如果该类没有创建对象,类中的实例变量不会被分配内存。但是,类中的类变量在该类被加载到内存时,就被分配了相应的内存空间。如果该类创建对象,那么不同对象的实例变量互不相同,即分配不同的内存空间; 而类变量不再重新分配内存,所有的对象共享类变量,即所有的对象的类变量是相同的一处内存空间,类变量的内存空间直到程序退出运行,才释放占有的内存。 类变量是与类相关联的数据变量,也就是说,类变量是和该类创建的所有对象相关联的变量,改变其中一个对象的类变量就同时改变了其他对象的这个类变量。因此,类变量不仅可以通过某个对象访问,也可以直接通过类名访问。实例变量仅仅是和相应的对象关联的变量,也就是说,不同对象的实例变量互不相同,即分配不同的内存空间,改变其中一个对象的实例变量不会影响其他对象的实例变量。实例变量可以通过对象访问,不可以使用类名访问。 图5.19梯形共享一个下底 在例5.7中,Ladder.java中的Ladder类创建的梯形对象共享一个下底。程序运行效果如图5.19所示。 【例5.7】 Ladder.java public class Ladder { double 上底,高;//实例变量 static double 下底;//类变量 void 设置上底(double a) { 上底 = a; } void 设置下底(double b) { 下底 = b; } double 获取上底() { return 上底; } double 获取下底() { return 下底; } } Example5_7.java public class Example5_7 { public static void main(String args[]) { Ladder.下底=100;//Ladder的字节码文件被加载到内存,通过类名操作类变量 Ladder ladderOne=new Ladder(); Ladder ladderTwo=new Ladder(); ladderOne.设置上底(28); ladderTwo.设置上底(66); System.out.println("ladderOne的上底:"+ladderOne.获取上底()); System.out.println("ladderOne的下底:"+ladderOne.获取下底()); System.out.println("ladderTwo的上底:"+ladderTwo.获取上底()); System.out.println("ladderTwo的下底:"+ladderTwo.获取下底()); } } 例5.7从Example5_7.java中的主类的main方法开始运行,当执行 Ladder下底=100; 时,Java虚拟机首先将Ladder的字节码加载到内存,同时为类变量“下底”分配了内存空间,并赋值100,如图5.20所示。 100下底 图5.20为下底分配内存 当执行 Ladder ladderOne = new Ladder(); Ladder ladderTwo = new Ladder(); 时,实例变量“上底”和“高”都两次被分配内存空间,分别被对象ladderOne和ladderTwo引用,而类变量“下底”不再分配内存,直接被对象ladderOne和ladderTwo引用、共享,如图5.21所示。 图5.21对象共享类变量 注: 类变量似乎破坏了封装性,其实不然,当对象调用实例方法时,该方法中出现的类变量也是该对象的变量,只不过这个变量和所有的其他对象共享而已。 5.6.3实例方法和类方法的定义 类中的方法也可分为实例方法和类方法。方法声明时,方法类型前面不加关键字static修饰的是实例方法,加static关键字修饰的是类方法(静态方法)。例如: class A { int a; float max(float x,float y) {//实例方法 … } static float jerry() {//类方法 … } static void speak(String s) { //类方法 … } } A类中的jerry方法和speak方法是类方法,max方法是实例方法。需要注意的是static需放在方法的类型的前面。 5.6.4实例方法和类方法的区别 1. 对象调用实例方法 当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址,只有该类创建对象后,类中的实例方法才分配入口地址,从而实例方法可以被类创建的任何对象调用执行。需要注意的是,当创建第一个对象时,类中的实例方法就分配了入口地址,当再创建对象时,不再分配入口地址。也就是说,方法的入口地址被所有的对象共享,当所有的对象都不存在时,方法的入口地址才被取消。 实例方法中不仅可以操作实例变量,也可以操作类变量。当对象调用实例方法时,该方法中出现的实例变量就是分配给该对象的实例变量,该方法中出现的类变量也是分配给该对象的变量,只不过这个变量和所有的其他对象共享而已。 2. 类名调用类方法 对于类中的类方法,在该类被加载到内存时,就分配了相应的入口地址。从而类方法不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。类方法的入口地址直到程序退出才被取消。需要注意的是,实例方法不能通过类名调用,只能由对象来调用。 和实例方法不同的是,类方法不可以操作实例变量,这是因为在类创建对象之前,实例成员变量还没有分配内存。 如果一个方法不需要操作实例成员变量就可以实现某种功能,可以考虑将这样的方法声明为类方法。这样做的好处是避免创建对象浪费内存。 在例5.8中,Sum类中的getContinueSum方法是类方法。 【例5.8】 Example5_8.java class Sum { int x,y,z; static int getContinueSum(int start,int end) { int sum=0; for(int i=start;i<=end;i++) { sum=sum+i; } return sum; } } public class Example5_8 { public static void main(String args[]) { int result=Sum.getContinueSum(0,100); System.out.println(result); } } 视频讲解 5.7方法重载与多态 Java中存在两种多态: 重载(Overload)和重写(Override)。重写是与继承有关的多态,将在第6章讨论。 方法重载是两种多态的一种。例如,让一个人执行“求面积”操作时,他可能会问你求什么面积。所谓功能多态性,是指可以向功能传递不同的消息,以便让对象根据相应的消息来产生相应的行为。对象的功能通过类中的方法来体现,那么功能的多态性就是方法的重载。方法重载的意思是一个类中可以有多个方法具有相同的名字,但这些方法的参数必须不同,即或者是参数的个数不同,或者是参数的类型不同。下面的A类中的add方法是重载方法。 class A { float add(int a,int b) { return a+b; } float add(long a,int b) { return a+b; } double add(double a,int b) { return a+b; } } 注: 方法的返回类型和参数的名字不参与比较,也就是说如果两个方法的名字相同,即使类型不同,也必须保证参数不同。 在例5.9中,People类中的computerArea方法是重载方法。另外,例5.9除了People、Tixing和主类外,还用到了例5.4中的Circle类。程序运行效果如图5.22所示。 图5.22方法重载 【例5.9】 Tixing.java public class Tixing { double above,bottom,height; Tixing(double a,double b,double h) { above = a; bottom = b; height = h; } double getArea() { return (above+bottom)*height/2; } } People.java public class People { double computerArea(Circle c) { double area=c.getArea(); return area; } double computerArea(Tixing t) { double area=t.getArea(); return area; } } Example5_9.java public class Example5_9 { public static void main(String args[]) { Circle circle = new Circle(); circle.setRadius(196.87); Tixing ladder = new Tixing(3,21,9); People zhang = new People(); System.out.println("zhang计算圆的面积: "); double result=zhang.computerArea(circle); System.out.println(result); System.out.println("zhang计算梯形的面积: "); result=zhang.computerArea(ladder); System.out.println(result); } } 视频讲解 5.8this关键字 this是Java的一个关键字,表示某个对象。this可以出现在实例方法和构造方法中,但不可以出现在类方法中。 5.8.1在构造方法中使用this this关键字出现在类的构造方法中时,代表使用该构造方法创建的对象。 在例5.10中,People类的构造方法中使用了this。 【例5.10】 People.java public class People{ int leg,hand; String name; People(String s){ name=s; this.init();//可以省略this,即将“this.init();”写成“init();” } void init(){ leg=2; hand=2; System.out.println(name+"有"+hand+"只手"+leg+"条腿"); } public static void main(String args[]){ People bushi=new People("布什"); //创建bushi时,构造方法中的this就是对象bushi } } 5.8.2在实例方法中使用this 实例方法只能通过对象来调用,不能通过类名来调用。当this关键字出现在实例方法中时,代表正在调用该方法的当前对象。 实例方法可以操作类的成员变量,当实例成员变量在实例方法中出现时,默认的格式是: this.成员变量; 当static成员变量在实例方法中出现时,默认的格式是: 类名.成员变量; 例如: class A { int x; static int y; void f() { this.x=100; A.y=200; } } 上述A类中的实例方法f中出现了this,this就代表使用f的当前对象。所以,“this.x”就表示当前对象的变量x,当对象调用方法f时,将100赋给该对象的变量x。因此,当一个对象调用方法时,方法中的实例成员变量就是指分配给该对象的实例成员变量,而static变量和其他对象共享。因此,通常情况下,可以省略实例成员变量名字前面的“this.”以及static变量前面的“类名.”。 例如: class A { int x; static int y; void f() { x=100; y=200; } } 但是,当实例成员变量的名字和局部变量的名字相同时,成员变量前面的“this.”或“类名.”就不可以省略。 我们知道类的实例方法可以调用类的其他方法,对于实例方法调用的默认格式是: this.方法; 对于类方法调用的默认格式是: 类名.方法; 例如: class B { void f() { this.g(); B.h(); } void g() { System.out.println("ok"); } static void h() { System.out.println("hello"); } } 在上述B类中的方法f中出现了this,this代表调用方法f的当前对象,所以,方法f的方法体中的this.g()就是当前对象调用方法g,也就是说,某个对象调用方法f的过程中,又调用了方法g。由于这种逻辑关系非常明确,一个实例方法调用另一个方法时可以省略方法名字前面的“this.”或“类名.”。 例如: class B { void f() { .g();//省略this h();//省略类名 } void g() { System.out.println("ok"); } static void h() { System.out.println("hello"); } } 注: this不能出现在类方法中,这是因为类方法可以通过类名直接调用,这时,可能还没有任何对象诞生。 视频讲解 5.9包 包是Java语言中有效地管理类的一个机制。不同的Java源文件中可能出现名字相同的类,如果想区分这些类,就需要使用包名。 5.9.1包语句 通过关键字package声明包语句。package语句作为Java源文件的第一条语句,指明该源文件定义的类所在的包,即为该源文件中声明的类指定包名。package语句的一般格式为: package包名; 如果源程序中省略了package语句,源文件中所定义命名的类被隐含地认为是无名包的一部分,只要这些类的字节码被存放在相同的目录中,那么它们就属于同一个包,但没有包名。 包名可以是一个合法的标识符,也可以是若干个标识符加“.”分隔而成,例如: package sunrise; package sun.com.cn; 5.9.2有包名的类的存储目录 如果一个类有包名,那么就不能在任意位置存放它,否则虚拟机将无法加载这样的类。 程序如果使用了包语句,例如: package tom.jiafei; 那么存储文件的目录结构中必须包含如下的结构: …\tom\jiafei 例如: C:\1000\tom\jiafei 并且要将源文件编译得到的类的字节码文件保存在目录C:\1000\tom\jiafei中(源文件可以任意存放)。 当然,可以将源文件保存在C:\1000\tom\jiafei中,然后进入tom\jiafei的上一层目录1000中编译源文件: C:\1000 > javac tom\jiafei\源文件 那么得到的字节码文件默认地保存在当前目录C:\1000\tom\jiafei中。 5.9.3运行有包名的主类 如果主类的包名是tom.jiafei,那么主类的字节码一定存放在…\tom\jiafei目录中,必须到tom\jiafei的上一层目录(即tom的父目录)中去运行主类。假设tom\jiafei的上一层目录是1000,那么,必须用如下格式来运行: C:\1000> java tom.jiafei.主类名 即运行时,必须写主类的全名。因为使用了包名,主类全名是“包名.主类名”(就好比大连的全名是“中国.辽宁.大连”)。 例5.11中的Student.java和Example5_11.java使用包语句。 【例5.11】 Student.java package tom.jiafei; public class Student{ int number; Student(int n){ number=n; } void speak(){ System.out.println("Student类的包名是tom.jiafei,我的学号: "+number); } } Example5_11.java package tom.jiafei; public class Example5_11 { public static void main(String args[]){ Student stu=new Student(10201); stu.speak(); System.out.println("主类的包名也是tom.jiafei"); } } 由于Example5_11.java用到了同一包中的Student类,所以在编译Example5_11.java时,需在包的上一层目录使用javac来编译。以下说明怎样编译和运行例5.11。 1. 编译 将上述两个源文件保存到C:\1000\tom\jiafei中,然后进入tom\jiafei的上一层目录1000中编译两个源文件: C:\1000> javac tom\jiafei\Student.java C:\1000 >javac tom\jiafei\Example5_11.java 编译通过后,C:\1000\tom\jiafei目录中就会有相应的字节码文件Student.class和Example5_11.class。 也可以进入C:\1000\tom\jiafei目录中,使用通配符*编译全部的源文件: C:\1000\tom\jiafei>javac *.java 2. 运行 运行程序时必须到tom\jiafei的上一层目录1000中运行,例如: C:\1000\java tom.jiafei.Example5_11 图5.23运行有包名的主类 例5.11的编译、运行效果如图5.23所示。 注意: Java语言不允许用户程序使用java作为包名的第一部分,例如java.bird是非法的包名(发生运行异常)。 视频讲解 5.10import语句 一个类可能需要另一个类声明的对象作为自己的成员或方法中的局部变量,如果这两个类在同一个包中,当然没有问题,例如,前面的许多例子中涉及的类都是无名包,只要存放在相同的目录中,它们就是在同一个包中; 对于包名相同的类,如例5.11,它们必须按照包名的结构存放在相应的目录中。但是,如果一个类想要使用的那个类和它不在一个包中,它怎样才能使用这样的类呢?这正是import语句要帮助完成的使命。以下详细讲解import语句。 5.10.1引入类库中的类 用户编写的类和类库中的类不在一个包中。如果用户需要类库中的类,就必须使用import语句。 在编写源文件时,除了自己编写类外,经常需要使用Java提供的许多类,这些类可能在不同的包中。在学习Java语言时,使用已经存在的类,避免一切从头做起,这是面向对象编程的一个重要方面。 为了能使用Java提供的类,可以使用import语句引入包中的类。在一个Java源程序中可以有多个import语句,它们必须写在package语句(假如有package语句的话)和源文件中类的定义之间。Java为我们提供了大约130个包(在后续的章节将需要一些重要包中的类),例如: java.lang: 包含所有的基本语言类(见第9、12章) javax.swing: 包含抽象窗口工具集中的图形、文本、窗口GUI类(见第11章) java.io: 包含所有的输入输出类(见第10章) java.util: 包含实用类(见第9章) java.sql: 包含操作数据库的类(见第14章) java.net: 包含所有实现网络功能的类(见第13章) 如果要引入一个包中的全部类,可以用通配符号星号(*)来代替,例如: import java.util.*; 表示引入java.util包中所有的类,而 import java.util.Date; 只是引入java.util包中的Date类。 例如,如果用户编写一个程序,并想使用java.util中的Date类创建对象来显示本地的时间,那么就可以使用import语句引入java.util中的Date类。在例5.12中,Example5_12.java使用了import语句,运行效果如图5.24所示。 图5.24引入类库中的类 【例5.12】 Example5_12.java import java.util.Date; public class Example5_12 { public static void main(String args[]) { Date date=new Date(); System.out.println("本地机器的时间:"); System.out.println(date); } } 注: ① java.lang包是Java语言的核心类库,它包含了运行Java程序必不可少的系统类,系统自动为程序引入java.lang包中的类(例如System类、Math类等),因此不需要再使用import语句引入该包中的类。 ② 如果使用import语句引入了整个包中的类,那么可能会增加编译时间,但绝对不会影响程序运行的性能,因为当程序执行时,只是将你真正使用的类的字节码文件加载到内存。 5.10.2引入自定义包中的类 1. 有包名的源文件 包名路径左对齐。所谓包名路径左对齐,就是让源文件中的包名所对应的路径和它要用import语句引入的非类库中的类的包名所对应的路径的父目录相同。假如用户的源文件的包名是hello.nihao,该源文件想引入的非类库中的包名是sohu.com的类,那么只需将两个包名所对应的路径左对齐,即让两个包名所对应的路径的父目录相同。例如,将用户的源文件和它准备用import语句引入的包名是sohu.com的类分别保存在: C:\ch5\hello\nihao 和 C:\ch5\sohu\com 中,即hello\nihao和sohu\com的父目录相同,都是C:\ch5。 2. 无包名的源文件 包名路径和源文件左对齐。假如用户的源文件没有包名,该源文件想引入的非类库中的包名是sohu.com的类,那么只需让源文件中import语句要引入的非类库中的类的包名路径的父目录和用户的源文件所在的目录相同,即包名路径和源文件左对齐。例如,将用户的源文件和它准备用import语句引入的包名是sohu.com的类分别保存在: C:\ch5\ 和 C:\ch5\sohu\com 中,即sohu\com的父目录和用户的源文件所在目录都是C:\ch5。 编写一个有价值的类是令人高兴的事情,可以将这样的类打包(自定义包),形成有价值的“软件产品”,供其他软件开发者使用。 在例5.13中,Triangle.java含有一个Triangle类,该类可以创建“三角形”对象,一个需要三角形的用户可以使用import语句引入Triangle类。将例5.13中的Triangle.java源文件保存到C:\ch5\tom\jiafei中(将tom\jiafei目录放在文件夹ch5中)。 如下编译Triangle类: C:\ch5> javac tom\jiafei\Triangle.java 【例5.13】 Triangle.java package tom.jiafei; public class Triangle { double sideA,sideB,sideC; boolean isTriangle; public Triangle(double a,double b,double c) { sideA=a; sideB=b; sideC=c; if(a+b>c&&a+c>b&&c+b>a) { isTriangle=true; } else { isTriangle=false; } } public void 计算面积() { if(isTriangle) { double p=(sideA+sideB+sideC)/2.0; double area=Math.sqrt(p*(p-sideA)*(p-sideB)*(p-sideC)) ; System.out.println("是一个三角形,面积是:"+area); } else { System.out.println("不是一个三角形,不能计算面积"); } } public void 修改三边(double a,double b,double c) { sideA=a; sideB=b; sideC=c; if(a+b>c&&a+c>b&&c+b>a) { isTriangle=true; } else { isTriangle=false; } } } 在例5.14中,Example5_14.java中的主类(无包名)使用import语句引入tom.jiafei包中的Triangle类,以便创建三角形,并计算三角形的面积。 将Example5_14.java保存在C:\ch5目录中(因为ch5下有tom\jiafei子目录)。程序运行效果如图5.25所示。 图5.25引入自定义包中的类 【例5.14】 Example5_14.java import tom.jiafei.Triangle; public class Example5_14 { public static void main(String args[]) { Triangle tri=new Triangle(6,7,10); tri.计算面积(); tri.修改三边(3,4,5); tri.计算面积(); } } 视频讲解 5.11访问权限 我们已经知道,当用一个类创建了一个对象之后,该对象可以通过“.”运算符操作自己的变量、使用类中的方法,但对象操作自己的变量和使用类中的方法是有一定限制的。 5.11.1何谓访问权限 所谓访问权限,是指对象是否可以通过“.”运算符操作自己的变量或通过“.”运算符使用类中的方法。访问限制修饰符有private、protected和public,都是Java的关键字,用来修饰成员变量或方法。以下来说明这些修饰符的具体作用。 需要特别注意的是,在编写类的时候,类中的实例方法总是可以操作该类中的实例变量和类变量; 类方法总是可以操作该类中的类变量,与访问限制符没有关系。 5.11.2私有变量和私有方法 用关键字private修饰的成员变量和方法称为私有变量和私有方法。例如: class Tom { private float weight;//weight是private的float型变量 private float f(float a,float b) {//方法 f是private方法 return a+b; } } 当在另外一个类中用类Tom创建了一个对象后,该对象不能访问自己的私有变量和私有方法。例如: class Jerry { void g() { Tom cat=new Tom(); cat.weight=23f;//非法 float sum=cat.f(3,4);//非法 } } 如果Tom类中的某个成员是私有类变量(静态成员变量),那么在另外一个类中也不能通过类名Tom来操作这个私有类变量。如果Tom类中的某个方法是私有的类方法,那么在另外一个类中也不能通过类名Tom来调用这个私有的类方法。 当用某个类在另外一个类中创建对象后,如果不希望该对象直接访问自己的变量,即通过“.”运算符来操作自己的成员变量,就应当将该成员变量访问权限设置为private。面向对象编程提倡对象应当调用方法来改变自己的属性,类应当提供操作数据的方法,这些方法可以经过精心的设计,使得对数据的操作更加合理,如例5.15所示。 【例5.15】 Student.java public class Student { private int age; public void setAge(int age) { if(age>=7&&age<=28) { this.age=age; } } public int getAge() { return age; } } Example5_15.java public class Example5_15 { public static void main(String args[]) { Student zhang=new Student(); Student geng=new Student(); zhang.setAge(23); System.out.println("zhang的年龄: "+zhang.getAge()); geng.setAge(25); //zhang.age=23;或geng.age=25;都是非法的,因为zhang和geng已经不在Student类中 System.out.println("geng的年龄: "+geng.getAge()); } } 5.11.3共有变量和共有方法 用public修饰的成员变量和方法被称为共有变量和共有方法。例如: class Tom { public float weight;//weight是public的float型变量 public float f(float a,float b) {//方法 f是public方法 return a+b; } } 当在任何一个类中用类Tom创建了一个对象后,该对象能访问自己的public变量和类中的public方法。例如: class Jerry { void g() { Tom cat=new Tom(); cat.weight=23f;//合法 float sum=cat.f(3,4);//合法 } } 如果Tom类中的某个成员是public类变量,那么在另外一个类中也可以通过类名Tom来操作Tom类中的这个成员变量。如果Tom类中的某个方法是public类方法,那么在另外一个类中也可以通过类名Tom来调用Tom类中的这个public类方法。 5.11.4友好变量和友好方法 不用private、public、protected修饰符的成员变量和方法被称为友好变量和友好方法。例如: class Tom { float weight;//weight是友好的float型变量 float f(float a,float b) {//方法 f是友好方法 return a+b; } } 当在另外一个类中用类Tom创建了一个对象后,如果这个类与Tom类在同一个包中,那么该对象能访问自己的友好变量和友好方法。在任何一个与Tom在同一包的类中,也可以通过Tom类的类名访问Tom类的友好成员变量和友好方法。 假如Jerry与Tom是同一个包中的类,那么下述Jerry类中的cat.weight、cat.f(3,4)都是合法的: class Jerry { void g() { Tom cat=new Tom(); cat.weight=23f;//合法 float sum=cat.f(3,4);//合法 } } 在源文件中编写命名的类总是在同一包中的。如果源文件使用import语句引入了另外一个包中的类,并用该类创建了一个对象,那么该类的这个对象将不能访问自己的友好变量和友好方法。 5.11.5受保护的成员变量和方法 用protected修饰的成员变量和方法被称为受保护的成员变量和受保护的方法。例如: class Tom { protected float weight;//weight是protected的float型变量 protected float f(float a,float b) {//方法 f是protected方法 return a+b; } } 当在另外一个类中用类Tom创建了一个对象后,如果这个类与Tom类在同一个包中,那么该对象能访问自己的protected变量和protected方法。在任何一个与Tom类在同一个包中的类中,也可以通过Tom类的类名访问Tom类的protected类变量和protected类方法。 假如Jerry与Tom是同一个包中的类,那么下述Jerry类中的cat.weight、cat.f(3,4)都是合法的: class Jerry { void g() { Tom cat=new Tom(); cat.weight=23f;//合法 float sum=cat.f(3,4);//合法 } } 注: 在后面讲述子类时,将讲述“受保护(protected)”和“友好”之间的区别。 5.11.6public类与友好类 类声明时,如果在关键字class前面加上public关键字,就称这样的类是一个public类。例如: public class A { … } 可以在任何另外一个类中,使用public类创建对象。如果一个类不加public修饰,例如: class A {… } 这样的类被称作友好类,那么另外一个类中使用友好类创建对象时,要保证它们是在同一包中。 注: (1) 不能用protected和private修饰类。 (2) 访问限制修饰符按访问权限从高到低的排列顺序是public、protected、友好的、private。 视频讲解 5.12基本类型的类包装 Java的基本数据类型包括byte、int、short、long、float、double、char。Java同时也提供了基本数据类型相关的类,实现了对基本数据类型的封装。这些类在java.lang包中,分别是Byte、Integer、Short、Long、Float、Double和Character类。 5.12.1Double类和Float类 Double类和Float类实现了对double和float基本型数据的类包装。 可以使用Double类的构造方法: Double(double num) 创建一个Double类型的对象; 使用Float类的构造方法: Float(float num) 创建一个Float类型的对象。Double对象调用doubleValue()方法可以返回该对象含有的double型数据; Float对象调用floatValue()方法可以返回该对象含有的float型数据。 5.12.2Byte类、Short类 、Integer类、Long类 下述构造方法分别创建Byte、Integer、Short和Long类型的对象: Byte(byte num) Short(short num) Integer(int num) Long(long num) Byte、Short、Integer和Long对象分别调用byteValue ()、shortValue()、intValue()和longValue ()方法返回该对象含有的基本型数据。 5.12.3Character类 Character类实现了对char基本型数据的类包装。 可以使用Character类的构造方法: Character(char c) 创建一个Character类型的对象。Character对象调用charValue()方法可以返回该对象含有的char型数据。 视频讲解 5.13可变参数 可变参数(variable argument)是JDK 1.5新增的功能。可变参数是指在声明方法时不给出参数列表中从某项直至最后一项参数的名字和个数,但这些参数的类型必须相同。可变参数使用“…”表示若干个参数,这些参数的类型必须相同,最后一个参数必须是参数列表中的最后一个参数。例如: public void f(int … x) 那么,方法f的参数列表中,从第1个至最后一个参数都是int型,但连续出现的int型参数的个数不确定。称x是方法f的参数列表中的可变参数的“参数代表”。 再如: public void g(double a,int … x) 那么,方法g的参数列表中,第1个参数是double型,第2个至最后一个参数是int型,但连续出现的int型参数的个数不确定。称x是方法g的参数列表中的可变参数的“参数代表”。 参数代表可以通过下标运算来表示参数列表中的具体参数,即x[0]、x[1]、……、x[m]分别表示x代表的第1个至第m个参数。例如,对于上述方法g,x[0]、x[1]就是方法g的整个参数列表中的第2个参数和第3个参数。对于一个参数代表,如x,x.length等于x代表的参数的个数。参数代表非常类似自然语言中的“等”,英语中的and so on。 对于类型相同的参数,如果参数的个数需要灵活的变化,那么使用参数代表可以使方法的调用更加灵活。例如,如果需要经常计算若干个整数的和,如: 1203+78+556,1+2+3+4+5,31+202+1101+1309+257+88 由于整数的个数经常需要变化,又无规律可循,那么就可以使用下列带可变参数的方法计算它们的和: int getSum (int … x); 那么, getSum (203,178,56,2098); 就可以返回203、178、56、2098的和。 在例5.16中,有两个Java源文件,分别是Computer.java和Example5_16.java,其中,Computer类中的getSum()方法使用了参数代表,可以计算若干个整数的和。 【例5.16】 Computer.java public class Computer { public int getSum(int... x) {//x是可变参数的参数代表 int sum=0; for(int i=0;i=1){ channel=m; } } int getChannel(){ return channel; } void showProgram(){ switch(channel) { case 1 : System.out.println("综合频道"); break; case 2 : System.out.println("经济频道"); break; case 3 : System.out.println("文艺频道"); break; case 4 : System.out.println("国际频道"); break; case 5 : System.out.println("体育频道"); break; default : System.out.println("不能收看"+channel+"频道"); } } } Family.java public class Family { TV homeTV; void buyTV(TV tv) { 【代码1】 //将参数tv赋值给homeTV } void remoteControl(int m) { homeTV.setChannel(m); } void seeTV() { homeTV.showProgram(); //homeTV调用showProgram()方法 } } MainClass.java public class MainClass { public static void main(String args[]) { TV haierTV = new TV(); 【代码2】 //haierTV调用setChannel(int m)方法,并向参数m传递5 System.out.println("haierTV的频道是"+haierTV.getChannel()); Family zhangSanFamily = new Family(); 【代码3】//zhangSanFamily调用void buyTV(TV tv)方法,并将haierTV传递给参数TV System.out.println("zhangSanFamily开始看电视节目"); zhangSanFamily.seeTV(); int m=2; System.out.println("zhangSanFamily将电视更换到"+m+"频道"); zhangSanFamily.remoteControl(m); System.out.println("haierTV的频道是"+haierTV.getChannel()); System.out.println("zhangSanFamily再看电视节目"); zhangSanFamily.seeTV(); } } 4. 实验指导 1) 黑盒复用 通过组合对象来复用方法也称“黑盒复用”,因为当前对象只能委托所包含的对象调用其方法,这样一来,当前对象对所包含的对象的方法的细节是一无所知的。 2) 参考答案 【代码1】: homeTV=tv; 【代码2】: haierTV.setChannel(5); 【代码3】: zhangSanFamily.buyTV(haierTV); 5. 实验后的练习 (1) 省略【代码2】程序能否通过编译?若能通过编译,程序输出的结果是怎样的? (2) 在主类的main方法的最后增添下列代码,并解释运行效果。 Family lisiFamily = new Family(); lisiFamily.buyTV(haierTV); lisiFamily.seeTV(); 5.15.3森林 1. 实验目的 类变量是与类相关联的数据变量,而实例变量是仅仅和对象相关联的数据变量。不同的对象的实例变量将被分配不同的内存空间,如果类中有类变量,那么所有对象的这个类变量都分配给相同的一处内存,改变其中一个对象的这个类变量会影响其他对象的这个类变量。也就是说,对象共享类变量。类中的方法可以操作成员变量,当对象调用方法时,方法中出现的成员变量就是指分配给该对象的变量,方法中出现的类变量也是该对象的变量,只不过这个变量和所有的其他对象共享而已。实例方法可操作实例成员变量和静态成员变量; 静态方法只能操作静态成员变量。 本实验的目的是让学生掌握类变量与实例变量,以及类方法与实例方法的区别。 2. 实验要求 编写程序模拟两个村庄共同拥有一片森林。编写一个Village类,该类有一个静态的int型成员变量treeAmount用于模拟森林中树木的数量。在主类MainClass的main方法中创建两个村庄,一个村庄改变了treeAmount的值,另一个村庄查看treeAmount的值。程序运行参考效果如图5.26所示。 图5.26村庄共享森林 3. 程序模板 请按照模板要求,将【代码】替换为Java程序代码。 Village.java class Village { static int treeAmount;//模拟森林中树木的数量 int peopleNumber; //村庄的人数 String name; //村庄的名字 Village(String s) { name = s; } void treePlanting(int n){ treeAmount = treeAmount+n; System.out.println(name+"植树"+n+"棵"); } void fellTree(int n){ if(treeAmount-n>=0){ treeAmount = treeAmount-n; System.out.println(name+"伐树"+n+"棵"); } else { System.out.println("无树木可伐"); } } static int lookTreeAmount() { return treeAmount; } void addPeopleNumber(int n) { peopleNumber = peopleNumber+n; System.out.println(name+"增加了"+n+"人"); } } MainClass.java public class MainClass { public static void main(String args[]) { Village zhaoZhuang,maJiaHeZi; zhaoZhuang = new Village("赵庄"); maJiaHeZi = new Village("马家河子"); zhaoZhuang.peopleNumber=100; maJiaHeZi.peopleNumber=150; 【代码1】 //用类名Village访问treeAmount,并赋值200 int leftTree =Village.treeAmount; System.out.println("森林中有 "+leftTree+" 棵树"); 【代码2】//zhaoZhuang调用treePlanting(int n),并向参数传值50 leftTree =【代码3】//maJiaHeZi调用lookTreeAmount()方法得到树木的数量 System.out.println("森林中有 "+leftTree+" 棵树"); 【代码4】maJiaHeZi调用fellTree(int n),并向参数传值70 leftTree = Village.lookTreeAmount(); System.out.println("森林中有 "+leftTree+" 棵树"); System.out.println("赵庄的人口:"+zhaoZhuang.peopleNumber); zhaoZhuang.addPeopleNumber(12); System.out.println("赵庄的人口:"+zhaoZhuang.peopleNumber); System.out.println("马家河子的人口:"+maJiaHeZi.peopleNumber); maJiaHeZi.addPeopleNumber(10); System.out.println("马家河子的人口:"+maJiaHeZi.peopleNumber); } } 4. 实验指导 1) 关于共享变量 对象共享类变量,在【代码1】之前已经有了zhaoZhuang对象,这个时候,【代码1】用“Village.treeAmount=200;”或“zhaoZhuang.treeAmount=200;”替换都是正确的。 2) 参考答案 【代码1】: Village.treeAmount = 200; 【代码2】: zhaoZhuang.treePlanting (50); 【代码3】: maJiaHeZi.lookTreeAmount(); 【代码4】: zhaoZhuang.fellTree (70); 5. 实验后的练习 【代码2】是否可以写成“Village.treePlanting(50);”? 5.16课外读物 课外读物均来自编者的教学辅助微信公众号javaviolin,扫描二维码即可学习观看。 (1) 守株待兔。 (2) 调虎离山。 (3) 请女朋友吃海鲜。 (4) 击鼓传花。 课外读物(1) 课外读物(2) 课外读物(3) 课外读物(4) 习题 1. 类中的实例变量在什么时候会被分配内存空间? 2. 什么叫方法的重载?构造方法可以重载吗? 3. 类中的实例方法可以操作类变量(static变量)吗?类方法(static方法)可以操作实例变量吗? 4. 类中的实例方法可以用类名直接调用吗? 5. 简述类变量和实例变量的区别。 6. 下列类声明错误的是。 A. class A B. public class A C. protected class A D. private class A 7. 下列A类的类体中【代码1】~【代码5】哪些是错误的? class Tom { private int x=120; protected int y=20; int z=11; private void f() { x=200; System.out.println(x); } void g() { x=200; System.out.println(x); } } public class A { public static void main(String args[]) { Tom tom=new Tom(); tom.x=22;//【代码1】 tom.y=33;//【代码2】 tom.z=55;//【代码3】 tom.f();//【代码4】 tom.g();//【代码5】 } } 8. 请说出A类中System.out.println的输出结果。 class B {int x=100,y=200; public void setX(int x) {x=x; } public void setY(int y) { this.y=y; } public int getXYSum() { return x+y; } } public class A {public static void main(String args[]) {B b=new B(); b.setX(-100); b.setY(-200); System.out.println("sum="+b.getXYSum()); } } 9. 请说出A类中System.out.println的输出结果。 class B { int n; static int sum=0; void setN(int n) { this.n=n; } int getSum() { for(int i=1;i<=n;i++) sum=sum+i; return sum; } } public class A { public static void main(String args[]) { B b1=new B(),b2=new B(); b1.setN(3); b2.setN(5); int s1=b1.getSum(); int s2=b2.getSum(); System.out.println(s1+s2); } } 10. 请说出E类中System.out.println的输出结果。 class A { double f(int x,double y) { return x+y; } int f(int x,int y) { return x*y; } } public class E { public static void main(String args[]) { A a=new A(); System.out.println(a.f(10,10)); System.out.println(a.f(10,10.0)); } }