第5章面向对象程序设计进阶 5.1实验目的 掌握static关键字,理解类层次和对象层次的区别。 掌握final关键字,理解final代表进化终止。 掌握abstract关键字,熟悉抽象类和抽象方法的设计技巧。 掌握interface关键字和接口的基本概念。 掌握package关键字和包的概念及使用技术。 5.2相关知识 在Java中,static关键字用来定义类层次的成员变量和成员方法,这种类层次的成员被类加载器装入后就可以直接使用,不需要创建对象实例,并且在内存中只有一个副本,所有对象共享使用,相对于类层次成员,对象层次的成员只有创建了对象后才能使用,并且每个对象都有自己的副本。final关键字用来修饰不可变成员,即成员的值或行为将来不可改变,具体来说,如果用final修饰类,则此类不能派生子类,相当于该类的进化终止了; 如果用final修饰方法,则此方法在其子类中不能被重写,此方法进化终止; 如果用final修饰变量,则变量的值不能再修改,变量变为常量。Java使用abstract关键字来定义抽象类和抽象方法,抽象的意思就是设计不完整,还需要后续的设计来继续完善。 Java中没有多继承,只有单一继承,为了使一个对象具有多种行为,Java提供了接口的概念,用interface关键字来定义接口,跟现实生活一样,接口代表的是规范和标准,所以只能包含公有的常量和公有抽象的方法。 前两章实验的重点内容是理解面向对象设计中的基本原理,本章实验对Java中面向对象的实现技术进行进阶学习。抽象和继承进阶中将学习抽象类,封装进阶中将介绍单态设计模式,多态进阶中将学习接口,以及对应主教材补充包和枚举等实验内容。 视频讲解 5.3实验内容 5.3.1验证实验 (1) 理解static、final关键字。static关键字修饰的成员是类层次,即通过类名来使用所有对象共享的成员。而final关键字是“最终”,不可更改的意思,代表进化终止。输入以下代码后编译运行,查看运行结果。 class Static_final { static int i=10; static final int k=20; static { i=i+5; } public static void main(String[] args) { System.out.println("i="+i); System.out.println("k="+k); k=30; //此句编译错,必须去掉 } static { i=i/2;} } 请解释为什么k=30;语句编译会出错。。 请解释为什么i的输出值不是15。。 请说明final关键字和static关键字的作用。 。 (2) 理解abstract关键字。用抽象类和抽象方法代表设计不具体、不完善的类和方法,主要用在高层抽象中,以便提供统一的接口。输入以下程序并以InheDemo.java存盘,编译并运行,同时学习并掌握继承的工作原理。 abstract class Employee{ //抽象类 String EmpName; char sex; double EmpSal; Employee(String en, char s, double es){ EmpName=en;sex=s;EmpSal=es; } public String getName(){ return EmpName; } public char getSex(){ return sex; } public abstract double getSal(); publicvoid setSal(int basSal){ EmpSal=basSal; }} /***********************************/ class Worker extends Employee{ char category; boolean dressAllowance; Worker(String en, char s, double es, char c, boolean d){ super(en,s,es); category=c;dressAllowance=d; } public char getCategory(){ return category; } public boolean getDressAll(){ return dressAllowance; } public double getSal(){ return EmpSal; }} /********************************/ class Superior extends Employee { int experience; boolean vehicle; double medicalAllowance; Superior(String en, char s, double es, int e, boolean v, double ma){ super(en,s,es); experience=e; vehicle=v; medicalAllowance=ma; } public int getExp(){ return experience; } public boolean getVehicle(){ return vehicle; } public double getMedicalAll(){ return medicalAllowance; } publicdouble getSal(){ return EmpSal*4+1000+medicalAllowance; }} /********************************/ class Officer extends Superior{ double travelAllowance; Officer(String en, char s,double es,int e, boolean v, double ma,double ta){ super(en,s,es,e,v,ma); travelAllowance=ta; } public double getTravelAll(){ return travelAllowance; } public double getSal(){ return EmpSal*2+200+travelAllowance+medicalAllowance; }} /*******************************/ class Manager extends Superior{ double clubAllowance; Manager(String en,char s,double es, int e, boolean v,double ma, double ca){ super(en,s,es,e,v,ma); clubAllowance=ca; } public double getClubAll(){ return clubAllowance; } public double getSal(){ return EmpSal*5+2000+medicalAllowance+clubAllowance; }} /**********************************/ class InheDemo{ public static void main(String args[]){ Worker w=new Worker("M.John",'M',1200.50,'B',true); System.out.println("工人信息: "); System.out.println("姓名: "+w.getName()); System.out.println("性别: "+w.getSex()); System.out.println("薪资: "+w.getSal()); System.out.println("类别: "+w.getCategory()); if(w.getDressAll()) System.out.println("提供服装津贴"); else System.out.println("未提供服装津贴"); Officer o=new Officer("S.David",'F',2500.70,15,true,345.60,200); System.out.println("\n主任信息: "); System.out.println("姓名: "+o.getName()); System.out.println("性别: "+o.getSex()); System.out.println("薪资: "+o.getSal()); System.out.println("工作经验: "+o.getExp()+"年"); if(o.getVehicle()) System.out.println("提供交通工具"); else System.out.println("未提供交通工具"); System.out.println("医疗津贴: "+o.getMedicalAll()); System.out.println("出差津贴: "+o.getTravelAll()); Manager m=new Manager("ArnoldShwaz",'M',4500.70,15,true,345.60,300); System.out.println("\n经理信息: "); System.out.println("姓名: "+m.getName()); System.out.println("性别: "+m.getSex()); System.out.println("薪资: "+m.getSal()); System.out.println("工作经验: "+m.getExp()+"年"); if(m.getVehicle()) System.out.println("提供交通工具"); else System.out.println("未提供交通工具"); System.out.println("医疗津贴: "+m.getMedicalAll()); System.out.println("会员津贴: "+m.getClubAll()); } } ① 读懂程序,完成图51所示的员工继承层次图,并填空。 ② 分析程序的执行流程并写出程序执行结果。 图51员工继承层次图 (3) 理解接口interface。Java中interface关键字用来定义接口或界面,此接口实际上是一种规范或标准。在接口中不关心细节,只关心功能和相应的数据指标要求,所以在Java的interface接口中只存在两类东西: 公有抽象的方法和公有静态的常量。 //interfacedemo.java interface Computable { int M=10; int f(int x); public abstract int g(int x,int y); } class A implements Computable { public int f(int x){ return M+2*x;} public int g(int x,int y){return M*(x+y);} } class B implements Computable { public int f(int x){return x*x*x;} public int g(int x,int y){return x*y*M;} } public class interfacedemo { public static void main(String[] args){ Computable a=new A();//可换为A a=new A(); Computable b=new B();//可换为B b=new B(); System.out.println(a.M); System.out.println(""+a.f(20)+", "+b.g(12,2)); System.out.println(b.M); System.out.println(""+b.f(20)+", "+b.g(12,2)); } } 接口(interface)可以看成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。 (4) 理解包的使用。Java中的包用来组织类和接口及资源,包的层次结构严格地对应于操作系统的目录结构,并注意以下编译和运行方式。 ① 用javac-d . -classpath . *.java//编译 ② 用java -classpath .包名.类名//执行 如本例: 编译: javac -d . Trangle.java javac flyhorse.java 运行: java -cp . flyhorse 用记事本输入以下程序并以相应的文件名存盘。 //Trangle.java packagewww.horsefly; public class Trangle{ double sideA,sideB,sideC; boolean flag; public Trangle(double a,double b,double c){ sideA=a;sideB=b;sideC=c; if(a+b>c&&a+c>b&&c+b>a){ System.out.println("我是一个三角形"); flag=true; }else{ System.out.println("我不是一个三角形"); flag=false; } } public void jsmj () { if(flag){ double p=(sideA+sideB+sideC)/2.0; double area=Math.sqrt(p*(p-sideA)*(p-sideB)*(p-sideC)) ; System.out.println("是一个三角形,能计算面积"); System.out.println("面积是:"+area); }else { System.out.println("不是一个三角形,不能计算面积"); } } public void set(double a, double b, double c) { sideA=a;sideB=b;sideC=c; if(a+b>c&&a+c>b&&c+b>a) { flag=true; }else{ flag=false; } } } //flyhorse.java import www.horsefly.Trangle; import java.util.Date; public class flyhorse { public static void main(String args[ ]){ Trangle trangle=new Trangle(12,3,104); trangle.jsmj( ); trangle.set(3,4,5); trangle.jsmj( ); Date 今天=new Date( ); System.out.println("今天是:"+今天); } } flyhorse本地编译运行结果如图52所示。 图52本地编译运行结果 本地编译后生成的目录结构如图53所示。 图53本地编译后的目录结构 请解释import www.horsefly.Trangle;语句的作用:。 程序中package www.horsefly;这条语句的含义是: 。 编译后,Trangle.class所在的路径是: 。 请回答在Java中,包名和操作系统的目录之间有什么关系: 。 (5) 带包移植。将带包的程序移植到鲲鹏云服务器上,因为操作系统的不同,可能对目录和路径的使用也不相同,所以在远程鲲鹏云服务器上把上面的实验运行一遍,看看有哪些不同。 ① 首先,登录华为云,单击“控制台”,选择原来购买区域的服务器,选择“更多”→“开机”选项,弹出如图54所示对话框,单击“是”按钮,等待服务器启动成功。 图54远程开机 ② 打开WinSCP软件,输入远程鲲鹏云服务器的IP地址和root密码,等待连接成功后,如图55所示,将flyhorse.java和Trangle.java拖(复制)到右侧鲲鹏云服务器/root/java/目录下。 图55用WinSCP上传源程序文件 ③ 双击PuTTY,输入服务器IP地址和root密码,连接到远程鲲鹏云服务器,切换到Java目录下,用ls命令查看源程序是否已经上传成功,然后用以下命令编译和运行,运行结果如图56所示,然后再用ls命令查看是否多了一个www目录。 图56远程鲲鹏云服务器编译运行结果 ④ 如果不继续在服务器上工作或做实验,一定记得关闭服务器,如图57所示,选择“更多”→“关机”选项,在弹出的对话框中选中“强制关机”复选框,再单击“是”按钮,然后就可以关闭浏览器了。 图57关闭远程鲲鹏云服务器 请读者解释javacencodingutf8d . *.java代码的含义。 虽然本地采用x86架构的Windows系统,即一般是Intel的指令集,而鲲鹏云服务器采用的是鲲鹏指令集和openEuler操作系统,但Java程序在本地和远程鲲鹏云服务器不做任何修改就可以编译和运行,运行结果也是一致的,这是为什么? 5.3.2填空实验 (1) 理解单态设计模式。单态设计模式就是指设计的类不能随意地通过new来创建对象,在程序运行期间,只存在一个对象,该对象可完成所有的相关逻辑操作,不需要另外创建对象。单态设计模式有很多写法,主教材中介绍了饿汉模式,在实验教材中另介绍一种懒汉模式,懒汉模式就是类加载完成后并不马上创建对象,只有当进程需要该对象时才创建对象。如下面的示例,请参考主教材知识完成填空。 //Singleton.java public class Singleton { private static Singleton instance = null; Singleton() {//封装构造方法 } public static Singleton getInstance(){ if (instance == null) { ; //创建Singleton对象 } return instance; } public void displayinfo(){ System.out.println("Hello, I am a Singleton Object!"); } } //TestSingleton.java public class TestSingleton { public static void main(String[] args) { //Singleton obj1=new Singleton(); //无法创建对象 Singleton obj=; //获得Singleton对象 ; //通过该对象调用displayinfo()方法 } } (2) 理解接口和动态加载执行。以下程序是一个Java多态接口动态加载示例程序。该程序是一个通用程序,用来计算每一种交通工具行驶1000千米所需的时间,已知每种交通工具的参数都是3个整数A、B、C的表达式。现有两种工具: Car 和Plane,其中Car 的速度运算公式为: A*B/C,Plane 的速度运算公式为: A+B+C。将来如果增加第3种交通工具的时候,不必修改此通用程序,只需要编写新的交通工具的程序即可。 注意: ① 充分利用接口的概念,用类对象充当参数。根据需求可以设计一个接口Common和三个类: 一个主控类ComputerTime,两个交通工具类Plane、Car,未来增加新的交通工具增加新的类即可。其运行过程是,从命令行输入类ComputerTime的4个参数,第一个是交通工具的类型,第二、第三、第四个参数分别是整数A、B、C,举例如下。 计算Plane的时间: java ComputerTime Plane 20 30 40 计算Car的时间: java ComputerTime Car 23 34 45 ② 实例化对象除了主教材中学习的new 构造方法,还有另外一种方法,通过动态加载类的方式实现: Class.forName(str).newInstance();,例如需要实例化一个Plane对象,只要调用Class.forName(类名).newInstance()即可。 请填空完成程序,并学习包的使用、接口的使用、动态加载类等知识。 //Common.java package CalTime.vehicle.all; public interface Common { double runTimer(double a, double b, double c);//定义接口方法 } //文件Plane.java package CalTime.vehicle; //导入Common接口 public class Plane implements Common { public double runTimer(double a, double b, double c) { return (a + b + c); } } //文件Car.java package CalTime.vehicle; import CalTime.vehicle.all.Common; public class Car implements Common { //仿照上面Plane类填写 } //文件ComputerTime.java import CalTime.vehicle.all.Common; import java.lang.*; public class ComputerTime { public static void main(String args[]) { System.out.println("交通工具: "+args[0]); System.out.println(" 参数A: "+args[1]); System.out.println(" 参数B: "+args[2]); System.out.println(" 参数C: "+args[3]); double A=Double.parseDouble(args[1]); double B=Double.parseDouble(args[2]); double C=Double.parseDouble(args[3]); double v,t; try { Common d=(Common) Class.forName("CalTime.vehicle."+args[0]).newInstance();//该语句的意思是动态加载 //CalTime.vehicle.Car类并创建一个实例对象,假设输入的参数是Car。用接口Common定义的 //引用d指向该对象 v=d.runTimer(A,B,C); //调用d对象实现的接口方法runTimer() t=1000/v; System.out.println("平均速度: "+v+" km/h"); System.out.println("运行时间: "+t+" 小时"); } catch(Exception e){ System.out.println("class not found"); } } } 请在当前目录编译,编译命令: javacd.Common.java Car.java Plane.java ComputerTime.java 然后运行,运行命令: java cp .ComputerTimeCar 100 5070 java cp .ComputerTimePlane 130 50 70 请将运行结果抄在下面: 5.3.3设计实验 (1) 给5.3.2节填空实验第(2)题中的通用程序ComputerTime增加一种新的交通工具Ship,假设该Ship工具的速度运算公式为: A+B/C,请设计类程序Ship.java,编译后运行下列命令: java ComputerTime Ship 40 100 25 输出结果如图58所示。 图58新交通工具Ship的运行时间 (2) 请参考抽象类Disk,设计两个类SSD和SATA分别表示一般固态硬盘和基于SATA口的固态硬盘,最后使用TestDisk类测试效果。 //Disk.java public abstract class Disk {//抽象类Disk //不管什么磁盘,都具备"价格、厂家、型号"这几个属性,子类直接继承即可 protected Float price;// 价格 protected String manufacturer;// 厂家 protected String model;// 型号 //不同的磁盘,读写方式差别很大,所以声明为abstract,强制子类必须改写 abstract void read(); abstract void write(); //如下方法是各种磁盘通用的,直接继承即可,无须改写 public String getManufacturer(){ return manufacturer; } public void setManufacturer(String manufacturer){ this.manufacturer = manufacturer; } public String getModel(){ return model; } public void setModel(String model){ this.model = model; } public Float getPrice(){ return price; } public void setPrice(Float price){ this.price = price; } } //TestDisk.java public class TestDisk { public static void main(String[] args) { Disk disk; //disk = new Disk();//编译错误: Cannot instantiate the type Disk disk = new SSD(); disk.read();// OK, 最终调用read(), 输出"SSD read, very fast" disk.write();// OK, 最终调用write(), 输出"SSD write, very fast" System.out.println(disk.getManufacturer()); // OK, 最终调用getManufacturer(), 输出"Seagate" System.out.println(disk.getModel()); // OK,最终调用getModel(),输出"ssd" System.out.println(disk.getPrice()); // OK,最终调用getPrice(),输出"500.5" disk = new SATA(); disk.read();// OK, 最终调用read(), 输出"SATA read, very slow" disk.write();// OK, 最终调用write(), 输出"SATA write, very slow" System.out.println(disk.getManufacturer()); // OK, 最终调用getManufacturer(), 输出"Maxtor" System.out.println(disk.getModel()); // OK, 最终调用getModel(),输出"sata" System.out.println(disk.getPrice()); // OK, 最终调用getPrice(),输出"100.5" } }