5 继承Java.docx
《5 继承Java.docx》由会员分享,可在线阅读,更多相关《5 继承Java.docx(28页珍藏版)》请在冰豆网上搜索。
5继承Java
第5章继承
目标
当完成本章后,你应该能够:
•了解继承
•在Java中实现继承
•理解访问权限控制
•了解和使用super关键字
•覆盖父类的方法
•了解和使用final关键字
继承
•继承的背景
•继承的语法
•继承的结果
•继承与组合
继承的背景
•根据封装的原则,可以把现实世界中的所有事物定义成相应的类
会得到数量众多的类
这些类之间有什么联系呢?
•马克思主义哲学原理:
联系是无处不在的
因此,没有联系是不可能的
类之间的联系取决于现实世界中事物之间的联系
事物之间的联系
•事物之间的联系有很多种
一种常见的联系是
事物A和B,
A所有的特点B都拥有
除此之外,B还有一些A不具备的特点
换言之,所有的B都是一个A,反之不一定
•例如,
平行四边形与菱形
长方形与正方形
员工与经理
isa
•这种关系,所有的B都是一个A,可称之为isa
一般和特殊的关系
父类:
一般
子类:
特殊
•如何表示事物间这种常见的联系呢?
•Java中采用继承来实现
•首先看不采用继承的情况
定义员工类Employee
定义经理类Manager
TestEmployeeManager.java
不采用继承
•员工类Employee的属性和方法,经理类Manager都有
•不采用继承
员工类Employee已经定义的成员,在经理类中需要重新定义,因为这两个类没有任何联系
•后果
代码冗长
如果员工类Employee的属性或方法有变化,两个类都需要进行修改
可维护性差
结论
•采用继承来表达员工类Employee和经理类Manager之间的联系
继承的语法
•使用Java的关键字extends
表示继承,扩展
•语法:
classSonClassextendsBaseClass{
//SonClass的成员
}
•extends前为子类,后为父类(基类/超类)
表示前者从后者继承
采用继承
•经理类不需要定义员工类已经定义了的属性和方法,采用继承机制自动就拥有父类的属性和方法
•修改TestEmployeeManager.java
成为TestExtends.java
继承的结果
•采用继承后
建立了员工类Employee和经理类Manager之间这种特殊的联系
•优点:
代码更简洁
员工类Employee中已经定义的属性和方法,经理类Manager会自动拥有
一旦需要进行修改,修改员工类Employee的成员,经理类Manager可直接使用修改后的成员
而对经理类Manager的修改则不会对员工类Employee产生任何影响
可维护性较高
继承的结果
•但是,继承从一定程度上打破了封装的结果—类
子类在一定程序上依赖于父类的实现
如果父类进行了修改,有可能导致原来已经定义好的子类不能正常工作
父类的修改除了影响自身之外,还可能会影响到它所有直接或间接的子类
直接/间接父类
•直接父类
classManagerextendsEmployee
则Employee是Manager的直接父类
Manager是Employee的直接子类
•Employee的直接父类是谁呢?
Object
•因此
Object是Manager的间接父类
Manager是Object的间接子类
继承关系图
•UML语言
统一建模语言
UnifiedModelingLanguage
图形式的语言
用于面向对象的分析与设计
OOA
OOD
Java则用于OOP
直接父类的个数
•单继承:
只允许有一个直接父类
•多重继承:
允许有多个直接父类
•Java语言只支持单继承
C++则支持多继承,更复杂,更难学习
•Java中所有的类构成了一棵树
称之为继承结构,hierarchy
树的根节点是Object
所有成员都自动拥有吗?
•子类能把父类的所有成员都继承过来吗?
•答案是否定的
•以下成员不能被继承:
private成员
静态成员
构造器
以上成员均包括成员变量和成员方法
继承与组合
•继承:
父类和子类是一般与特殊的关系
•除此之外,现实世界中还有一种联系也很常见,即整体和局部的关系,例如:
电动剃须刀是一个包含电源子系统的组合系统
电动剃须刀类Shaver
电源类Power
后者又有两个子类:
电池类Battery,交流电源类AcPower
Shave-Power组合示意图
组合的实现
•在定义类时,把局部类的一个对象作为整体类的一个成员变量即可
例如,TestComposition.java中
类Shaver的定义中包含:
privatePowerpower;
publicvoidShaver(Powerpower){
this.power=power;
}
publicvoidsetPower(Powerpower){
this.power=power;
}
继承与组合的选择
•两个类之间的关系
取决于现实世界中二者是一般和特殊的关系,还是整体和局部的关系
•当然,两个类之间的关系
除了继承与组合之外
还可以有其它类型的关系
例如,如果类A使用类B提供的服务
则称为类A依赖类B
依赖,dependency
super
•super概述
•通过super访问父类成员变量
•通过super调用父类成员方法
•通过super调用父类构造器
•构造子类对象
•super与this
super概述
•super:
Java语言的关键字
用来在子类的成员方法中访问父类的成员
可以访问的成员包括:
成员变量
普通的成员方法
构造器
在通过super访问上述三种成员时
其语法不相同,要求也不一样
通过super访问父类成员变量
•子类的成员方法中可以通过super来访问父类的成员变量
语法:
super.成员变量名称
•限制:
只能在子类的非静态成员方法中使用super
在子类的构造器中亦可
访问权限控制
•尽管是子类和父类的继承关系
子类依然不能访问父类的private成员
通过super来访问亦然
•修改TestExtends.java
把父类的成员变量id设为private权限
重新编译程序
通过super调用父类成员方法
•在子类的非静态成员方法中通过下述形式可以调用父类的成员方法:
super.成员方法名称(参数列表)
例如,子类Manager没有定义print()方法
采用的是从父类Employee继承的方式
但是这个继承过来的print()方法的功能与Manager所要求的还有一定的距离
没有打印出经理所承担的责任
解决方法
•子类Manager再定义一个print()方法
在该方法中,使用super.print()来调用父类的print()方法,之后自己来打印出相应的职责responsibility
•修改TestExtends.java
publicvoidprint(){
super.print();
System.out.println("\t职责是"+this.responsibility);
}
如果不使用super
•把super.print()改为print()试一试
看看会发生什么情况
•如果删去super
那么就意味着调用this.print()
这种方法自己直接调用自己的方式
称之为
递归调用之直接递归调用
由于程序没有控制如何退出,因此造成方法调用的堆栈层次太深从而产生堆栈溢出
java.lang.StackOverflowError
另一种变化
•把子类Manager中的两条语句互换位置
变成了
publicvoidprint(){
System.out.println("\t职责是"+this.responsibility);
super.print();
}
•重新编译程序
结果
•程序通过了编译,也可以正常运行
职责的输出在员工的工号、姓名、部门之前
•虽然通过了编译,也可以运行
但是,在大多数情况下
子类的方法调用父类成员方法的语句
一般都做为子类方法的第一条语句出现
通过super调用父类构造器
•在子类的构造器中,可以通过super来调用父类的构造器
•语法:
super(参数列表);
例如
可以把子类Manager构造器的前三行
修改为通过super来调用父类的构造器
通过super调用父类构造器
•子类Manager的构造器变成了:
publicManager(Stringid,Stringname,Stringdepartment,Stringresponsibility){
super(id,name,department);
this.responsibility=responsibility;
}
把id、name和department的初始化工作
交给了父类的构造器来完成
调用两次?
•在super(id,name,department)之前再加上一个对父类构造器的调用super()
重新编译程序
产生了编译错误
src\TestExtends.java:
29:
对super的调用必须是构造器中的第一个语句
super(id,name,department);
^
1错误
互换位置?
•子类Manager的构造器变成了:
publicManager(Stringid,Stringname,Stringdepartment,Stringresponsibility){
this.responsibility=responsibility;
super(id,name,department);
}
•重新编译程序
得到的编译错误与前面相同
说明
•在子类的构造器中通过super可以调用父类的构造器
•如果有该调用
那么必须是子类构造器的第一条语句
因此最多只能调用一次
•不能在子类的非构造器中通过super来调用父类的构造器
构造子类对象
•由于子类的对象不仅具有本类定义的成员变量和成员方法,还有其父类定义的成员变量和成员方法,因此构造子类的对象,比构造一个简单的类的对象要复杂得多
例如,创建一个经理类Manager的对象
该对象不仅有成员变量responsibility
还有从父类继承过来的成员变量
id、name和department
以及一个成员方法print()
构造子类对象
•虽然要完成的工作比较多
但是程序员创建该类对象的方法依然与以前完全相同:
Managerm;
m=newManager(“1001”,“john”,“技术部”,“负责技术部事宜”);
•还是通过new来调用相应的构造器
来完成对象的构造工作
调用父类构造器
•为了完成子类对象的构造,子类的构造器一定会调用父类的某个构造器
•这个调用可以由程序员通过super来完成
•那如果程序员没有使用super来调用父类的构造器呢?
调用父类构造器
•如果程序员没有明确写出调用父类构造器的语句,那么编译器会在子类构造器的第一条语句之前默认调用父类无参数的构造器
•这要求父类必须有一个无参数的构造器,如果没有,则产生编译错误,不能通过编译
修改程序
•修改程序
删去子类Manager的构造器中通过super来调用父类构造器的语句,使用赋值语句来完成
然后注释父类Employee的无参数构造器
•重新编译程序
编译错误
src\TestExtends.java:
29:
找不到符号
符号:
构造器Employee()
位置:
类Employee
publicManager(Stringid,Stringname,Stringdepartment,Stringresponsibility){
^
1错误
•错误指向子类Manager构造器的开始大括号
说明
•由于该构造器并未明确调用父类的构造器,因此编译器会自动在此构造器的开始处运行以下语句:
super();
•而父类Employee并没有一个无参数的构造器,因此产生了上述编译错误
措施
•父类:
保证有一个无参数的构造器
另外,根据成员变量的具体情况
来编写数个有参数的构造器
•子类:
在构造器中明确调用父类的构造器
super与this
•都是Java语言的关键字
•都只能在类的非静态成员方法中使用
•this:
访问本类的成员
•super:
访问父类的成员
上述成员均包括:
成员变量
普通的成员方法
构造器
方法覆盖
•方法的继承
•方法的覆盖
方法的继承
•对于子类来说,通过继承可以自动获得父类已经定义好的成员,包括成员变量和成员方法
例如
子类Manager无需自己定义就拥有了print()方法
这是继承的优点
继承的方法
•从父类继承的方法是父类定义的
其方法体是按照父类的具体情况(例如成员变量情况)来编写的
对于子类则不一定完全合适
例如
从父类Employee继承过来的print()方法没有输出成员变量responsibility的值
因为类Employee没有这个成员变量
只能被动继承吗?
•继承过来的方法对于子类而言是只读的吗?
不能进行修改吗?
•答案是否定的
对于从父类继承过来的方法
如果不能完全满足子类的需要
子类可以重新定义该方法
称为
方法的覆盖,重写,override
方法的覆盖
•把父类的方法重新定义一遍
•语法:
方法签名相同,只是方法体不同
•方法签名
方法的头部
包括访问权限、返回类型、方法的名称以及方法的参数列表
重新定义print()方法
•在子类Employee中重新定义print()方法
publicvoidprint(){
System.out.println(“该经理的工号是"+super.id);
System.out.println("\t姓名是"+super.name);
System.out.println("\t部门是"+super.department);
System.out.println("\t职责是"+this.responsibility);
}
•重新编译程序
拿来主义
•子类在覆盖父类的方法时,如果有一部分工作父类被覆盖的方法已经完成了,子类的方法是在父类方法的基础上增加一些功能,那么子类的方法可以通过super来调用父类被覆盖的方法
•优点
提高代码的可重用性
减少代码行数
使用super调用的print()方法
•使用super来调用父类的方法
子类Manager的print()方法变成了
publicvoidprint(){
super.print();
System.out.println("\t职责是"+this.responsibility);
}
小bug
•小bug:
原来输出是;该经理的工号是
现在输出是:
该员工的工号是
•原因:
成员方法print()功能较多
不仅打印员工的详细信息
而且还打印员工的类型
•解决方案:
用多个方法来实现打印功能,例如
printType()用来打印员工的类型
printInfo()用来打印员工的详细信息
解决方法:
父类
•父类Employee增加两个方法
publicvoidprintType();
publicvoidprintInfo();
然后把print()方法修改成:
publicvoidprint(){
this.printType();
this.printInfo();
}
解决方法:
子类
•子类
继承父类的print()方法
覆盖父类新增加的两个方法
publicvoidprintType();
publicvoidprintInfo();
•另存为TestExtendsPrint.java
重新编译,运行
解决方法:
小结
•重新进行了成员方法的设计与实现
父类:
增加了两个方法printType()及printInfo()
在print()方法中调用上述两个方法
子类:
继承了父类的print()方法
覆盖了父类新增加的两个方法
客户:
即包含main()的类
未做任何修改
父类、子类关于打印的实现对于使用者是透明的
程序的可维护性较高
方法设计
•一个方法完成一个功能
但是功能也有大小之分
功能之间也有包含的关系
粒度
•根据实际情况进行功能的划分、确定
一个方法完成一个指定的不可再分的功能
不可再分:
原子性,atom
方法的覆盖
•覆盖父类的方法时
子类的方法与父类的方法必须具有
相同的返回类型
相同的方法名称
相同的参数列表
方法的覆盖
•访问权限呢?
相同肯定是可以的
但不是必须得具有相同的访问权限
子类的方法不能比父类的方法访问更严格
•严格?
宽松?
访问权限控制
禁止方法被覆盖?
•一个类作为父类可以被继承
某一个方法已经实现了预期功能
并且不想让子类继承时覆盖这个方法
怎么办呢?
•不想让一个变量初始化后再被赋值,即常量
•不想让一个类可以被继承,即禁止类被继承
•解决方案,尽在Java关键字:
final
final修饰符
•final概述
•final变量
•final方法
•final类
final概述
•final:
Java语言的关键字
最终的,不能改变的
可以修饰:
变量
成员方法
类
final变量
•变量用final修饰则成为常量
特点:
一旦初始化后则不能被再次赋值
其值保持不变
•final可以修饰的变量包括:
静态成员变量
非静态成员变量,即实例变量
局部变量
方法的形式参数
final静态成员变量
•无需创建该类即可使用的常量
例如,圆周率在类Math中已经进行了定义
publicstaticfinaldoublePI=3.14159265358979323846;
•根据命名规范,常量的名称全部大写
如果有多个单词,之间以下划线_隔开
例如,类Integer中的静态成员变量定义
publicstaticfinalintMIN_VALUE=0x80000000;
publicstaticfinalintMAX_VALUE=0x7fffffff;
其中0x开头代表16进制
查看JavaAPI源代码
•把JDK安装目录下的src.zip解压缩
产生src目录
该目录中即为JavaAPI类的源代码
因为类的数量很多
因此是按子目录分开存放的
类Math、Integer均在子目录java/lang下
•参观高手编写的程序
提高编程能力的途径之一,临摹
使用final静态成员变量
•通过类名加点号.再加成员变量名称即可访问
例如,定义类圆Circle
privatedoubleradius;
publicCircle();
publicCircle(doubleradius);
publicdoublegetRadius();
publicvoidsetRadius(doubleradius);
publicdoublegetPerimeter();
publicdoublegetArea();
示例:
TestCircle.java
修饰符
•类Math中成员PI的修饰符有
public:
访问权限控制
表示任何地方都可以访问该变量
static:
静态的
表示无需创建Math类的对象即可访问
final:
最终的
表示这是一个常量,不能再被赋值
double:
双精度浮点类型
说明该常量的数据类型是double类型
final实例变量
•例一
Java语言中有8种基本数据类型
每一种都有对应的类
该类封装了一个对应的基本数据类型做为其成员变量
例如,类Integer封装了一个int类型的值
privatefinalintvalue;
这是一个私有的final实例变量
封装类
基本数据类型
封装类
boolean
Boolean
char
Character
byte
Byte
short
Short
int
Integer
long
Long
float
Float
double
Double
final实例变量
•例一(续)
该实例常量的初始化工作由构造器完成
publicInteger(intvalue){
this.value=value;
}
该常量一旦初始化后,其值不能改变
因此,所有的封装类都是不可变类
不可变,immutable
final实例变量
•实际上,所有的封装类都有一个名称为value的privatefinal实例变量,所不同的是不同的封装类中该变量的类型不同
例如,类Boolean中该实例常量的声明为
privatefinalbooleanvalue;
也由构造器进行初始化
publicBoolean(booleanvalue){
this.value=value;
}
final实例变