类的继承.docx
- 文档编号:9945776
- 上传时间:2023-05-22
- 格式:DOCX
- 页数:16
- 大小:22.10KB
类的继承.docx
《类的继承.docx》由会员分享,可在线阅读,更多相关《类的继承.docx(16页珍藏版)》请在冰点文库上搜索。
类的继承
第6章继承
前二章主要介绍了单个类和对象的构造以及类的封装,这一章讨论面向对象三个最主要的特性封装、继承和多态的第二个特性----继承。
继承是进化论在软件工程中的实现,是对我们周围事物发展演化的模仿,一个类可以继承它的父类,并且比它的父类更强大。
“张三像他爸”,因为张三这个对象继承了他父亲的很多静态特性或动态行为方式,自然界中这种现象数不胜数。
当我们把对象张三抽象成“儿子类”,张三的父亲抽象成“父亲类”,也很容易发现:
儿子继承了父亲的行为特征,而这种继承即是多级的(曾祖—>祖父父亲儿子),又是多重的(儿子继承父亲+母亲)。
面向对象的继承不仅是对是对父类行为方式的模仿,而且包含发展进化思想。
发展进化是指子类除了继承父类所有的属性和方法外,还可以添加自已的属性方法,甚至可以修改(覆盖)从父类继承来的属性和方法。
面向对象的程序设计,子类永远比父类更强大,这一点与现实世界有所不同。
1子类继承父类
继承是在一个已有的类(父类)的基础上,创建新类(子类)的机制。
语法:
class子类名extends父类名
{//子类新增的属性和方法}
●extends是JAVA关键字,表示“继承、扩展”。
●子类可以继承父类构造方法以外的所有属性和方法。
●JAVA只允许单继承,即一个子类只能继承一个父类。
而C++允许多继承。
●继承可以是多级的,即子类继承父类,父类继承祖父类,祖父类继承曾祖类。
。
。
但继承层次过多会造成严重的效率问题。
例1:
publicclassPeople//声明一个父类
{Stringname;//父类有2个属性
intage;
voideat(){};//父类有2个方法
voidsleep(){};
}
classStudentsextendsPeople//子类Students继承了父类
{Stringschool;//子类新增了1个属性
voidstudy(){};//子类新增了1个方法
}
问题:
子类有几个属性,几个方法?
答:
3个属性(2个是继承父类的,1个是新增的)
3个方法(2个是继承父类的,1个是新增的
语义:
继承使软件模块在原有的基础上,实现“可持续性发展”。
首先,子类必需是父类的一类(子类isakindof父类),这是至关重要的设计概念(语义级的),否则会造成继承的滥用和误用,带来设计的混乱。
比如上例中,Students可以继承People,因为StudentsisakindofPeople,反过来则是语义错误。
JAVA编译器只检查语法,不检查语义。
其次,子类继承了父类后,“自动”拥有了父类的属性和方法,并且增加了子类特有的属性和方法,也就是说子类的功能比父类更多,反过来则不成立。
看下面的代码:
例2:
publicclassPeople//声明一个父类
{Stringname;//父类有2个属性
intage;
voideat(){};//父类有2个方法
voidsleep(){};
}
classStudentsextendsPeople//子类Students继承了父类
{Stringschool;//子类新增了1个属性
voidstudy(){};//子类新增了1个方法
}
classTest{
publicstaticvoidmain(Stringargs[])
{Peoplep=newPeople();//父类对象
Studentss=newStudents();//子类对象
s.name=“张三”;//子类继承了这个属性
s.eat();//子类继承了这个方法
s.school=“威海分校”;//子类特有的属性
s.study();//子类特有的方法
p.school=“”;//错,父类没有此属性
p.study();//错,父类没有此方法
p.name=“张三”;//与s.name=“张三”是一回事吗?
}}
p.name=“张三”与s.name=“张三”不是一回事,不要被子类“免费”继承父类的所有属性和方法迷惑了,当对象实例化后,子类对象是子类对象,父类对象是父类对象,它们各自拥有不同的内存空间,如下图所示(这只是一个概念图):
父类对象p的空间:
p.name=“张三”
p.age=0
p.eat()
p.sleep()
子类对象s的空间:
s.name=“张三”
s.age=0
s.school=“威海分校”
s.eat()
s.sleep()
s.study()
而且,上例的子类对象s实例化时,也不需要先实例化父类对象p,直接实例化对象s即可。
2子类继承了父类的那些属性和方法?
前面一小节初步讨论了继承,侧重于对继承概念的掌握,曾提到“子类可以继承父类构造方法以外的所有属性和方法”,在父类属性和方法使用了private/public/protected/缺省修饰的情况下,情况有所变化:
首先,无论如何,父类构造方法是不能被子类继承的。
第二,如果子类和父类在同一个包中,子类可以继承父类private以外的成员变量和方法。
第三,如果子类和父类不在同一个包中,子类只能继承父类public/protected成员变量和方法。
由第二、三点可以看出,无论何种情况,子类都不能继承private的成员变量和方法,下面演示一下:
例3:
publicclassPeople//声明一个父类
{privateintage;//私有属性不能被继承
privateintsetAge(intage)//私有方法不能被继承
{this.age=age;}
//省略类的其它代码
}
classStudentsextendsPeople//子类Students继承了父类
{publicstaticvoidmain(Stringargs[])
{Studentss=newStudents();//在子类本类创建对象
s.age=21;//非法,说明子类没有继承这个属性
s.setAge(21);//非法,说明子类没有继承这个属性
}
上面两个非法操作说明子类确实没有继承父类的private成员,否则子类在本类创建对象应该可以访问它们。
这从语法和语义上是有道理的,因为父类要有“隐私权”。
但是从子类对象的内部构造过程上,这些private成员又存在于子类对象的空间上,只是子类对象不能直接访问它们。
如下面的概念图所示:
子类对象s的内存空间概念图
s.age存在,但不能直接访问
s.setAge()存在,但不能直接访问
s的其它成员,包括从父类继承的非private成员,可以直接访问
既然子类不能继承父类的private成员,为什么子类对象空间中又有它们?
因为这些成员有被子类对象间接访问到的可能:
例4:
publicclassPeople//声明一个父类
{privateintage;//私有属性不能被继承
privateintsetAge(intage)//私有方法不能被继承
{returnthis.age=age;}
publicintgetAge()//公有方法能被继承
{returnage;}
}
classStudentsextendsPeople//子类Students继承了父类
{publicstaticvoidmain(Stringargs[])
{Studentss=newStudents();//在子类本类创建对象
s.age=21;s.setAge(21);//非法
s.getAge();//合法,间接访问了age属性
}
问题:
s.getAge()访问的age属性是父类的吗?
答:
不是,它是创建在子类空间的。
子类对象在实例化时,由于继承了父类成员,必需先调用父类的构造方法,在子类空间中初始化继承来的父类成员,然后再调用子类的构造方法,在子类空间中初始化子类成员。
看下一小节。
【重要说明:
】本小节讲解了子类对父类private成员的继承问题(依次可以类推到对父类protected和缺省成员的继承问题),对这个问题有两种看法:
一种如本课件所述,子类没有继承父类private成员,但子类对象实例化后,其对象空间包含父类的private成员;另一种如教材所述,子类继承了父类的private成员,但不能直接访问。
这两种看法都有道理,其最终效果是一样的:
子类对象空间包含继承自父类的private成员,但不能直接访问。
3子类对象的构造过程
子类对象的构造过程是一个比较纯粹的技术性问题,不涉及面向对象编程的概念和思想,深入讨论对初学者相当困难,但这又是个较重要的知识点。
下面是一个浅显的讨论:
在创建子类的对象时,Java虚拟机首先执行父类的构造方法,然后再执行子类的构造方法。
在多级继承的情况下,将从继承链最上层的类开始,依次向下执行各个类的构造方法,这可以保证子类对象从所有直接或间接父类中继承的实例变量都在子类空间中被正确地初始化。
构造方法按继承层次从上而下依次执行,最后在子类空间中形成一个完整的对象。
例5:
A<--B-C-D继承链的构造
publicclassA{
A(){System.out.println("A的构造方法被调用");}
}
classBextendsA{
B(){System.out.println("B的构造方法被调用");}
}
classCextendsB{
C(){System.out.println("C的构造方法被调用");}
}
classDextendsC{
D(){System.out.println("D的构造方法被调用");}
publicstaticvoidmain(String[]args)
{Dd=newD();
}}
运行结果是:
A的构造方法被调用
B的构造方法被调用
C的构造方法被调用
D的构造方法被调用
这个例子演示了,当D实例化时,其继承链上的所有类要按从高到低的顺序依次调用构造方法,以初始化D类继承过来的属性和方法。
但是,虽然D类继承链上的所有构造方法都被调用了,但只生成了一个D类对象d:
Dd=newD();
并未生成A,B,C类的对象,切记!
4Object类
如果一个类在声明时没有extends某个类,那它默认继承Object类,Object类是JAVA所有类的根类,在后续课程介绍JAVAAPI时再具体介绍这个类。
5继承的作用
前面讨论了类的继承关系,继承在实际编程中有什么用呢?
继承是软件系统升级维护的重要技术,它有利于实现软件系统渐进的可持续性发展,而不是突然的革命性变化,这对提高软件的质量、降低开发成本、延长产品生命周期有重要价值。
在实际工作中,JAVA等面向对象的程序员,很少从0开始编写程序,他们写的大多数程序都是在已有可复用模块(以往项目中开发的模块、开发环境中自带的类库、外购或免费下载的其它库)的基础上,加上一些扩充性的代码,形成了新的系统。
继承是他们用到的主要技术。
但是继承也是有局限性的,仅列举3点:
第一,从设计概念上说,继承的前提是父类不再修改,这要求父类必需特别稳定,这个要求是非常强的。
如果父类要经常修改,容易造成继承链的崩溃,产生不稳定的软件系统。
解决办法是使用抽象类做基类,下一章将讲解抽象类。
第二,有些问题不能用继承解决,继承要求“子类isakindof父类”,这个设计概念是必须要满足的。
解决办法是使用接口,或使用类的组成关系。
下一章将讲解接口
第三,子类对象实例化时要调用父类构造方法,如果继承层次过多或对象过大,会造成效率问题。
解决办法是减少类的继承层次。
6子类覆盖(override)父类的属性和方法
考虑这样一种情况,假如有一个类F:
classF{
intx;//属性
voidfun(){};//方法
}
有三个类A,B,C分别继承了F,A和B认为,F的属性和方法对自已适用,而C类认为不适用,需要修改intx的类型和fun()内的代码,怎样解决这个矛盾?
修改父类F是不合适的,面向对象设计有一个重要的设计原则:
开闭原则----对修改关闭,对扩展开放,也就是说对一个运行稳定的旧系统(F类),尽量不要修改其源代码,而应该在新系统(C类)中,对其进行扩展(继承)性的修改。
一个好的方案是:
子类(C类)用相同的属性名重新定义从父类(类)继承来的属性,用相同的方法名(及相同的方法参数)重新定义从父类继承来的方法,重新定义的属性和方法只对子类对象有效,而对父类对象无效,这样就满足了“开闭原则”。
//例6子类覆盖父类的同名属性和方法
publicclassF{
intx=10;
voidfun()
{System.out.println("F类的fun()被调用,"+
"F类的X="+x);}
}
classAextendsF{}
classBextendsF{}
classCextendsF
{Stringx="abc";//重写父类的属性
voidfun(){System.out.println("C类的fun()被调用;"+"C类的X="+x);}//重写父类的方法
}
classEx{
publicstaticvoidmain(String[]args)
{Ff=newF();f.fun();
Aa=newA();a.fun();
Bb=newB();b.fun();
Cc=newC();c.fun();
}
}
运行结果是:
F类的fun()被调用,F类的X=10
F类的fun()被调用,F类的X=10
F类的fun()被调用,F类的X=10
C类的fun()被调用;C类的X=abc
这个例子各对象的内存概念图如下:
f对象:
x=10;
fun();
a对象
x=10;//继承自F
fun();//继承自F
b对象
x=10;//继承自F
fun();//继承自F
c对象
x=10//继承自F
fun()//继承自F
x=”abc”//重写
fun()//重写
注意c对象内存空间中保存了两套版本的x和fun(),如果使用
对象名.方法名()或对象名.属性名,则调用的是重写版的;
如果使用
super.方法名()或super.属性名,则调用的是父类继承版的;
7关键字super
以前学过关键字this,它指代“当前对象”。
关键字super则指代“(直接)父类对象”。
【注意super是直接父类对象,而不是间接父类(祖父类)对象】
super主要有二种用法:
(1)当子类重写了父类的属性或方法,用
super.方法名()或super.属性名,调用父类继承版的属性或方法。
//例7super的第一种用法
publicclassF{
intx=10;
voidfun()
{System.out.println("F类的fun()被调用,"+"F类的X="+x);}
}
classCextendsF
{Stringx="abc";//重写父类的属性
voidfun(){System.out.println("C类的fun()被调用;"+"C类的X="+x);}//重写父类的方法
voidoutput()
{Ccc=newC();
cc.fun();
super.fun();}
publicstaticvoidmain(String[]args)
{Cc=newC();c.output();
}}
运行结果是:
C类的fun()被调用;C类的X=abc
F类的fun()被调用,F类的X=10
在这个例子中,并没有new一个父类F对象,但super.fun()调用成功,原因是子类对象CC实例化时,将从父类继承的和重写的fun()都保存在CC对象自已的空间中,super.fun()调用的是CC对象空间中的(直接)父类版。
参见【例6】后面的内存概念图。
(2)super的第二种用法
是在子类构造方法中,用super(参数。
。
。
)指代父类构造方法。
根据本章第3小节的讨论,子类对象实例化时要调用父类的构造方法,这要求在写子类构造方法时,构造方法的第1行要用
super(参数。
。
。
);指代父类构造方法,如果super()没有参数,则这一行可以省略,由系统自动调用无参的super()。
//例8super的第二种用法
publicclassF{
intx;
F()//父类构造方法1,无参
{x=10;
System.out.println("父类构造方法1被调用");}
F(intx)//父类构造方法2,重载
{this.x=x;
System.out.println("父类构造方法2被调用");}
}
classAextendsF
{A()//子类构造方法1
{super();//调用父类构造方法1,这一行可以省略
System.out.println("子类构造方法1被调用");}
A(intx)//子类构造方法1,重载
{super(20);//调用父类构造方法2,这一行不可以省略,且必须写在子类构造方法第一行
System.out.println("子类构造方法2被调用");}
}
classEx{
publicstaticvoidmain(String[]args)
{Aa1=newA();
Aa2=newA(30);
}}
运行结果是:
父类构造方法1被调用
子类构造方法1被调用
父类构造方法2被调用
子类构造方法2被调用
8多层继承时子类对父类属性或方法的覆盖
//例9多层继承时子类对父类属性和方法的覆盖
publicclassA{
intx=10;
voidfun()
{System.out.println("A类的fun()被调用);}
}
classBextendsA{
floatx=10.123f;//重写A类的属性
voidfun()//重写A类的方法
{System.out.println("B类的fun()被调用);}
}
classCextendsB{
charx='z';//重写B类的属性
voidfun()//重写B类的方法
{System.out.println("C类的fun()被调用");}
}
classDextendsC
{Stringx="abc";//重写C类的属性
voidfun()//重写C类的方法
{System.out.println("D类的fun()被调用");}
}
classEx{
publicstaticvoidmain(String[]args)
{Dd=newD();//【1】d的空间有什么?
d.fun();//【2】调用了哪个版本的fun()?
}}
【1】答:
A,B,C是D的直接或间接父类,d对象实例化后,其空间内存放了A,B,C,D所有版本的x和fun()
【2】答:
d.fun()时,按继承链顺序,从低到高寻找可执行的fun()版本:
(1)先找D类的fun(),如有则执行之,否则寻找C类
(2)如C类重写了fun(),则执行之,否则寻找B类
(3)如B类重写了fun(),则执行之,否则寻找A类
(4)如A类写了fun(),则执行之,否则fun()为非法调用。
【注意】本章第3小节介绍子类对象的构造过程讲过,子类对象实例化时,按继承链从高到低的顺序调用构造方法。
而本小节所述的子类对象对父类同名属性或方法的调用,则是按继承链从低到高的顺序调用。
两者正好相反。
本章习题
简答题
1继承的作用是什么?
2子类是否继承了父类private成员?
为什么?
3在什么情况下,子类需要覆盖父类的属性或方法?
4super有哪2种用法?
5子类对象实例化调用构造方法的顺序是什么?
子类对象调用父类同名属性或方法的顺序是什么?
编程题
1编写一个“手机类”,然后再分别写一个“功能手机类”和“智能手机类”继承“手机类”,要求尽可能多的用到本章所讲的知识点。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 继承