实验2 面向对象b继承多态c抽象类接口.docx
《实验2 面向对象b继承多态c抽象类接口.docx》由会员分享,可在线阅读,更多相关《实验2 面向对象b继承多态c抽象类接口.docx(21页珍藏版)》请在冰豆网上搜索。
实验2面向对象b继承多态c抽象类接口
实验2面向对象(继承、多态、抽象类、接口)
注意:
有些程序由于Word的关系,复制后,tab缩位可能会变成其它符号。
需要你去调整一下,删除缩位,重新Tab
一、实验目的1
二、实验要求1
三、实验内容2
1.类的继承与覆盖练习2
1.0父类的哪些成员可以被继承?
2
1.1父类Student(学生)与子类HiStudent(大学生)2
1.2实例方法为什么需要覆盖4
1.3验证成员方法的覆盖方式5
1.4this、super和super()的使用6
1.5变量、静态方法的隐藏。
9
1.6隐藏与覆盖的综合性实例10
2.类的多态性练习11
2.1普通方法的重载(实验3,本实验略)11
2.2构造方法的重载(实验3,本实验略)11
2.3运行时多态与动态绑定11
3.抽象类13
4.接口14
一、实验目的
通过编程和上机实验理解Java语言的面向对象特性,了解类的继承性和多态性的作用,掌握它们的实现方法,了解数据域和静态方法的隐藏,了解抽象类和接口的作用。
二、实验要求
1、编写体现类的继承性(成员变量、成员方法、成员变量隐藏)的程序;
2、编写体现类的多态性的程序;
3、编写体现抽象类和接口功能的程序。
三、实验内容
1.类的继承与覆盖练习
例如,圆是一种形状。
圆类Circle是形状类Shape的子类。
父类也叫基类、超类,子类也叫次类、扩展类、派生类。
子类可从父类中产生,保留父类的成员变量和方法,并可根据需要对它们加以修改。
新类还可添加新的变量和方法。
这种现象就称为类的继承。
当建立一个父类时,不必写出全部成员变量和成员方法。
只要简单地声明这个类是从一个已定义的类继承下来的,就可以引用被继承类的全部成员。
Java提供了一个庞大的类库让开发人员继承和使用。
设计这些类是出于公用的目的,因此,很少有某个类恰恰满足你的需要。
你必须设计自己的能处理实际问题的类,如果你设计的这个类仅仅实现了继承,和父类一样,那样没什么用。
所以,通常要对父类进行扩展,即添加新的属性和方法。
这使得子类要比父类大,但更具特殊性,代表着一组更具体的对象。
继承的意义就在于此。
1.0父类的哪些成员可以被继承?
可以访问的成员才能被继承。
具体分2种情况:
*当父类和子类在同一个包时,子类可以继承父类中的public/protected/无修饰级别的变量和方法;
*当父类和子类不在同一个包时,且父类是public的,那么子类可以继承父类的public/protected级别的变量和方法。
如果父类前没有public,那么子类无法访问父类,更谈不上继承的问题。
1.1父类Student(学生)与子类HiStudent(大学生)
(1)Student.java程序源代码如下。
publicclassStudent//学生类
{
protectedStringxm;//姓名
protectedintxh;//学号
voidsetData(Stringm,inth)//设置姓名和学号
{
xm=m;
xh=h;
}
publicvoidprint()//输出姓名和学号
{
System.out.println(xm+","+xh);
}
}
(2)HiStudent.java的描述和代码如下
程序功能:
通过Student类产生子类HiStudent,其不仅具有父类的成员变量xm(姓名)、xh(学号),还定义了新成员变量xy(学院)、xi(系)。
在程序中调用了父类的print方法,同时可以看出子类也具有该方法。
程序源代码如下
publicclassHiStudentextendsStudent{//大学生类继承自学生类
protectedStringxy;//所在学院或大学名称
protectedStringxi;//所在系的名称
publicstaticvoidmain(Stringargs[]){
Students1=newStudent();
s1.setdata("李四",12321);
s1.print();
HiStudents2=newHiStudent();
s2.setdata("张三",12345);//调用父类的成员方法
s2.xy="XX大学";//访问本类的成员变量
s2.xi="计算机系";//访问本类的成员变量
s2.print();//调用父类的方法,就象调用它自己的一样
System.out.print(s2.xm+","+s2.xy+","+s2.xi);
}
}
(3)编译并运行,结果如下图所示。
1.2实例方法为什么需要覆盖
上例中,s2.print();语句调用父类的方法,只能输出父类的数据(姓名和学号)。
如果想用函数输出姓名、学号、学院、系,需要在子类中另外添加一个函数。
如果这个函数也叫print(),(嗯,这样好记),并且如果形参、返回类型都一样,并且其可见性修饰符和父类print()方法的一样甚至更开放,并且它们都不是静态的,并且它所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类,或者什么也不抛出,那么,我们可以说子类的print()方法覆盖(override)了父类的print()方法。
子类中添加的print()可以这样写:
publicvoidprint()//输出姓名、学号、学院、系
{
System.out.println(xm+","+xh+","+xy+","+xi);
}
可见,子类的覆盖方法重写(修改)了父类的被覆盖方法。
实现了不同的功能。
当然,父类的被覆盖方法仍然存在,在子类中可以用super.print()来引用。
嗯,如果参数不一样,那也叫重载。
如果返回类型不一样,或者一个静态一个非静态,或者子类方法的开放性比父类被覆盖方法的更低,都会编译错。
如果两个方法其它一样,又都是静态的,这没问题,叫做隐藏,而非覆盖。
另外,父类中用final修饰的方法不能被覆盖。
再重复一下方法覆盖的概念:
如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。
覆盖的说明:
∙子类的方法名称返回类型及参数签名必须与父类的一致
∙子类方法不能缩小父类方法的访问权限
∙子类方法不能抛出比父类方法更多的异常
∙方法覆盖只存在于子类和父类之间,同一个类中只能重载
∙父类的静态方法不能被子类覆盖为非静态方法
∙子类可以定义于父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法(满足覆盖约束),
∙Java虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。
∙父类的非静态方法不能被子类覆盖为静态方法
∙父类的私有方法不能被子类覆盖
∙父类的抽象方法可以被子类通过两种途径覆盖(即实现和覆盖)
∙父类的非抽象方法可以被覆盖为抽象方法
1.3验证成员方法的覆盖方式
方法覆盖为子类提供了修改父类实例方法的能力。
例如,Object类的toString方法返回的字符串是“类名@随机码”这样的。
子类可以修改层层继承下来的Object根类的toString方法,让它输出一些更有用的信息。
下面的程序显示了在子类Circle中添加toString方法,用来返回圆半径和圆面积信息。
(1)编写Circle类,覆盖Object类的toString方法,Circle类和Test类源代码如下。
classCircle{
privateintradius;
Circle(intr){
setRadius(r);
}
publicvoidsetRadius(intr){
radius=r;
}
publicintgetRadius(){
returnradius;
}
publicdoublearea(){
returnMath.PI*radius*radius;
}
publicStringtoString(){//覆盖继承自Object的toString()方法
return"圆半径:
"+getRadius()+"圆面积:
"+area();
}
}
publicclassTest{
publicstaticvoidmain(Stringargs[]){
Circlec=newCircle(10);
System.out.println(c.toString());
}
}
1.4this、super和super()的使用
(1)程序功能:
说明this.、this()、super.和super()的用法。
程序首先定义Point(点)类,然后创建点的子类Line(线)。
Line继承了Point的点,它自己又定义了一个点。
最后通过Test类输出线段的长度。
Line对象程序中通过super(a,b)调用父类Point的构造方法为父类的x和y赋值。
在子类Line的setLine方法中,因为参数名和成员变量名相同,为给成员变量赋值,使用this引用,告诉编译器是为当前类的成员变量赋值。
在length和toString方法中使用父类成员变量时,使用super引用,告诉编译器使用的是父类的成员变量。
(2)程序源代码如下。
classPoint{
protectedintx,y;
Point(inta,intb){
setPoint(a,b);
}
publicvoidsetPoint(inta,intb){
x=a;
y=b;
}
}
classLineextendsPoint{
protectedintx,y;
Line(inta,intb){
super(a,b);
setLine(a,b);
}
publicvoidsetLine(intx,inty){
this.x=x+x;
this.y=y+y;
}
publicdoublelength(){
intx1=super.x,y1=super.y,x2=this.x,y2=this.y;//this.可省略
returnMath.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
}
publicStringtoString(){
return"直线端点:
["+super.x+","+super.y+"]["+x+","+y+"]直线长度:
"+this.length();
}
}
publicclassTest{
publicstaticvoidmain(Stringargs[]){
Lineline=newLine(50,50);
System.out.println(line.toString());
}
}
说明:
这个例子只为验证super(),super.,this.等的用法。
当然,线并不是一种点,所以用Line继承Point不合适。
用聚合的形式更合适些。
例如可以如下修改:
classPoint{
protectedintx,y;
Point(intx,inty){
setPoint(x,y);
}
publicvoidsetPoint(intx,inty){
this.x=x;
this.y=y;
}
}
classLine{
Pointa,b;
publicdoublelength(){
returnMath.sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
publicLine(){}
publicLine(Pointa,Pointb){
this.a=a;
this.b=b;
}
publicLine(intx1,inty1,intx2,inty2){
a=newPoint(x1,y1);
b=newPoint(x2,y2);
}
}
publicclassTest{
publicstaticvoidmain(Stringargs[]){
Lineline=newLine(1,1,2,0);
System.out.println("直线端点:
["+line.a.x+","+line.a.y+"]-["+line.b.x+","+line.b.y
+"]直线长度:
"+line.length());
}
}
1.5变量、静态方法的隐藏。
父类和子类定义了同名的变量,或者相同的静态方法。
(1)父类和子类有相同的变量。
父类变量被隐藏。
将B类中的x所在行屏蔽起来或者不屏蔽,比较输出结果有何变化。
classA{
intx=1;
}
classBextendsA{
//intx=2;
voidprintX(){//输出父类对象的x值
System.out.println(super.x);//如果B类没有声明x,那么super.可省略。
但如果B类也声明了变量x,那么父类的x被隐藏,要访问父类对象的成员,super.不能省略
}
}
publicclassTest{
publicstaticvoidmain(String[]s){
Bp=newB();
System.out.println(p.x);
p.printX();
//System.out.println(p.super.x);//语法错误。
要用super.x引用B类的父类对象的成员,可以在B类中进行
}
}
(2)父类和子类有相同的静态方法。
父类静态方法被隐藏。
下面B类中的print()方法,屏蔽或不屏蔽,输出结果有何不同?
classA{
staticvoidprint(){
System.out.println("A");
}
}
classBextendsA{
/*
staticvoidprint(){
System.out.println("B");
}
*/
}
publicclassTest{
publicstaticvoidmain(String[]s){
Bp=newB();
p.print();
}
}
【说明】上面A、B两类中的print()方法都是static的,称为静态方法隐藏。
如果都是非静态的,则称为覆盖。
如果其中一个是静态的,另一个非静态,则编译错,提示“无法覆盖”。
1.6隐藏与覆盖的综合性实例
注意看蓝色或红色的注释。
其中,红色的注释是容易出错的地方。
classA{
intx=1;//被隐藏
voidprint(){//被覆盖
System.out.println("这里是父类方法,x="+x);//父类A的方法中访问的变量必然是A类或A的父类的,不可能访问B类的。
m();//父类A的方法中调用的实例方法m()是子类B的,由于发生了覆盖
}
voidm(){//被覆盖
System.out.println("这里是父类的实例方法m()");
}
staticvoidm2(){//被隐藏
System.out.println("这里是父类的静态方法m2()");
}
}
classBextendsA{
intx=2;
voidprint(){
System.out.println("这里是子类方法,x="+x);//子类方法访问的变量是子类对象的(当然条件是子类中声明了这个变量)
System.out.println("这里是子类方法,super.x="+super.x);//super.x是父类对象的
super.print();//调用父类的print()方法
m();//调用本对象的m()方法
}
voidm(){
System.out.println("这里是子类的实例方法m()");
}
staticvoidm2(){
System.out.println("这里是子类的静态方法m2()");
}
}
publicclassTest{
publicstaticvoidmain(String[]s){
Ap=newB();
System.out.println(p.x);//通过引用变量p来访问变量或静态方法,要看p的声明类型。
所以x是A类的。
p.m2();//同上。
静态方法m2()是A类的。
p.print();//通过引用变量p来访问实例方法,要看p指向的对象的实际类型。
由于覆盖,调用的print()方法是子类的。
}
}
2.类的多态性练习
c++允许多重继承(多个直接父类),功能强大,但复杂的继承关系也给c++开发者带来了很大麻烦。
为了规避风险,java只允许单重继承,即,只能有一个直接父类,所以继承关系简单明了。
但在功能上又给开发者提出了难题。
因此,Java用多态性、抽象类、接口的概念来弥补此项不足。
多态性:
在程序中同一符号或名字在不同情况下具有不同解释
Ø编译时多态性:
指在程序编译阶段即可确定下来的多态性。
重载发生时,编译器根据方法签名来确定未来会调用哪个方法。
Ø运行时多态性:
指必须等到程序动态运行时才可确定的多态性,JVM根据引用变量指向的实际对象来绑定方法。
2.1普通方法的重载
(参考实验3,本实验略)
2.2构造方法的重载
(参考实验3,本实验略)
2.3运行时多态与动态绑定
一组方法,在多个类中都有定义。
由于继承与覆盖,到底调用哪个方法,需要根据不同的对象来进行动态绑定。
因此结果呈现多态性。
如果A是父类,B是子类,那么Ap=newB();是合法的。
p的类型被声明成A类型,但p实际上指向一个B类对象。
(1)如果A、B两类中都有实例方法m(),那么p.m()调用的是B中的m()。
(2)如果A中有而B中没有,那么p.m()调用的是B类对象从A继承而来的m()方法。
这是动态绑定。
(3)如果A中没有m()方法,则无论B中有没有,都会编译错。
因为编译并非执行,编译器发现p是A类型的,而A类型以及A的父类都没有m()方法,也就是说,A类没有这个方法,也没有通过继承获得这个方法。
另外,关于静态方法:
(1)如果m()方法在A、B两个类中,一个静态一个非静态,则编译错。
(无法覆盖)
(2)如果m()方法在A中是静态的,在B中没有,或者也是静态的,那么p.m()调用的是A类的静态方法。
编译的时候就把p.m()换成A.m()。
读1.6隐藏与覆盖的综合性实例。
(3)如果A中没有静态方法m(),那么编译器要看A的父类有没有。
静态方法虽然无法覆盖,但也可继承。
如果其父类也没有,则会编译错。
下面的程序,Test类中函数showInformation的形参是Person类型的引用变量x。
实参如果是其子类对象,那么就是让x指向子类对象,这和Ap=newB()的原理是一样的。
//代码Test.java
classPerson{
protectedStringname;//姓名
Person(){}
Person(Stringname){
this.name=name;
}
Stringinformation(){
return"类Person:
"+name;
}
}
classStudentextendsPerson{//学生类
protectedintid;//学号
Student(){}
Student(Stringname,intid){
super(name);
this.id=id;
}
/*
Stringinformation(){
return"类Student:
"+name+id;
}
*/
}
classHiStudentextendsStudent{//大学生类继承自学生类
protectedStringschool;//大学名称
protectedStringdepartment;//系的名称
HiStudent(){}
HiStudent(Stringname,intid,Stringschool,Stringdepartment){
super(name,id);
this.school=school;
this.department=department;
}
Stringinformation(){
return"类HiStudent:
"+name+id+school+department;
}
}
publicclassTest{
publicstaticvoidmain(String[]as){
showInformation(newPerson("张三"));
showInformation(newStudent("张三",201122));//Student类没有information方法,因此showInformation将调用Student对象继承来的information方法。
这是动态绑定。
showInformation(newHiStudent("张三",201122,"西南大学","计科系"));
}
staticvoidshowInformation(Personx){
System.out.println(x.information());
}
}
问题:
1、如果在Student类中,将对information()方法的屏蔽取消,结果有何变化?
2、Test类showInformation方法的形参类型是Person。
将其改为Object,编译能通过吗?
3.抽象类
将父类设计得非常抽象,让它包含所有子类的共同属性、方法,以至于它没有具体的实例。
如果子类不是抽象类,就必须实现(覆盖)抽象父类中的所有抽象方法。
这其实也就规定了子类必须实现的的共同行为。
abstractclassGeometricShape{//几何形状类
publicabstractdoublegetArea();//不知道具体形状,无法计算,只能定义