Java程序设计精讲五 第六章 继承与多态1
1 子类
在Java中,也可以用子类和父类来刻画事物,大的更一般的类可以看作是父类,而包含在其中的特殊的类是子类。 子类与父类的关系是︰子类对象“is a”(或“is kind of”)父类对象,也就是说,子类中的任何一个成员也是父类中的一个成员。还体现着继承的特点,可以支持软件的可复用性。
1.1. is a关系
1.2. extends关键字
Java提供了派生机制,允许程序员用以前已定义的类来定义一个新类。新类称作子类,原来的类称作父类,也称为基类或超类。两个类中共同的内容放到父类中,特殊的内容放到子类中。在定义类时可以表明一个类是不是另一个类的子类。 在Java中,用关键字extends
表示派生,格式如下∶
1
2
3
修饰符 class子类名 extends 父类名{
类体
}
如果一个类的定义中没有出现extends关键字,则表明这个类派生于object类。
1.3. Object类
1.3.1. 说明
Object类是Java程序中所有类的直接或间接父类,处在类层次的最高点。所有其他的类都是从Object类派生而来的。Object类包含了所有Java类的公共属性,其构造方法是Object ()。 类中主要的方法︰
- public final Class getClass()∶获取当前对象所属的类信息,返回Class对象。
- public String toString()∶按字符串对象返回当前对象本身的有关信息。
- public boolean equals(Object obj):比较两个对象是否是同一个对象,是则返回true。
1.3.2. 对象相等的判别
使用
==
运算符使用
equals()
方法。
这两种方式判定的都是两个对象是否是同一个对象(称为同一)。如果两个对象具有相同的类型及相同的属性值,则称为相等。同一的对象一定相等,但相等的对象不一定同一。
1.3.3. 重写Object类的equals方法,判定两个字符串的内容是否相同
1
2
3
4
5
6
7
8
public boolean equals(Object x) {
if (this.getClass() != x.getClass()) //具有相同的类类型
return false;
BankAccount b = (BankAccount) x;
return ((this.getOwnerName().equals(b.getOwnerName()))
&& (this.getAccountNumber() == b.getAccountNumber())
&& (this.getBalance() == b.getBalance()));//判定各个成员变量的值是否相等
}
1.4. 单重继承
1.4.1. 概念
多重继承∶是指从多个类共同派生一个子类,即一个类可以有多个父类。如果子类的多个父类中有同名的方法和属性,那么容易造成子类实例的混乱。故在Java中抛弃了多重继承,只允许单重继承。
单重继承︰在Java中,如果一个类有父类,则其父类只能有一个,也就是只允许从一个类中扩展类。这条限制称为单重继承。
- 多重继承的能力通过接口来实现
。
子类不能继承构造方法。让一个类得到构造方法的方式
自己编写构造方法;
在用户没有编写构造方法的时候,由系统为类提供唯——个默认的构造方法。
- 子类的对象可以直接使用其父类中定义的公有(及保护)属性和方法,就如同在自己的类中定义了一样。 子类不能直接访问其父类中定义的私有属性及方法,但可以使用父类中定义的公有(及保护)方法访问私有数据成员。
1.5. 对象转型
1.5.1. 概念
对象转型( Casting):Java允许使用对象的父类类型的一个变量指向该对象,比如对于前面定义的Employee类和Manager类,可以将子类的对象赋给父类的变量∶
1
Employee e = new Manager( ); //子类Manager的实例赋给父类变量e
:对象引用的赋值兼容原则允许把子类的实例赋给父类的引用。但反过来是错误的,不能把父类的实例赋给子类的引用。
1.5.2. instanceof
通过instanceof运算符判断一个引用到底指向哪个实例,即判定其真正类型。
1
2
3
4
5
6
7
8
9
public void method(Employee e) {
if (e instanceof Manager) {//Manager对象的处理
/*对Manager对象进行处理*/
} else if (e instanceof Contractor) { //Contractor对象的处理
/*对Contractor对象进行处理*/
} else {//Employee对象的处理
/*对Employee对象进行处理*/
}
}
示例:
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
Employee betty = new Manager();//使用父类引用指向子类实例
betty.name = "Betty";
System.out.printIn(betty.getName());
betty.employeeNumber = 543469;
// betty.department = "Test";//错误
if (betty instanceof Manager) {//将Employee类型的betty转型为Manager类型
Manager m = (Manager) betty;
m.department = "Test";//允许
System.out.printIn("This is the manager of" + m.department);//访问Manager类中的属性
}
}
进行对象引用转型的规则:
- 沿类层次向"上"转型总是合法的,例如,把Manager引用转型为Employee引用。此种方式下不需要转型运算符,只用简单的赋值语句就可以完成。
- 对于向"下“转型,只能是祖先类转型到后代类,其他类之间是不允许的。例如,把Manager引用转型为Contractor引用是非法的,因为Contractor不是Manager。这两个类之间没有继承关系。要替换的类(赋值号右侧)必须是当前引用类型(赋值号左侧)的父类,且要使用显式转换。
2 方法的覆盖与多态
2.1. 方法的覆盖及其原则
方法覆盖(Override),也称为方法重写或隐藏︰父类中原有的方法可能不能满足新的要求,因此需要修改父类中已有的方法。
2.1.1. 方法覆盖
当子类重写父类方法时,子类与父类使用的是相同的方法名及参数列表(具有相同的方法签名)。覆盖的同名方法中,子类方法不能比父类方法的访问权限更严格。
和重载的区别:如果方法名相同,而参数列表不同,则是对方法的重载。重载的方法属于同一个类,覆盖的方法分属于父类、子类中。
2.2.2. super关键字
如果子类已经重写了父类中的方法,但在子类中还想使用父类中被隐藏的方法,可以使用super关键字。 使用super时,需注意:
- 使用super. method()调用父类中的方法method(),将执行父类方法中的所有操作,其中可能会包括一些原本不希望进行的操作。所以调用时要谨慎。
- 由继承性的机制可以知道,super.method()语句所调用的这个方法不一定是在父类中加以描述的,它也可能是父类从它的祖先类中继承来的。因此,有可能需要按继承层次关系依次向上查询才能够找得到。
2.2.3. 应用覆盖时的重要原则
- 覆盖方法的允许访问范围不能小于原方法。
- 覆盖方法所抛出的异常不能比原方法更多。
2.2. 调用父类的构造方法
- super关键字
也可以用在构造方法中,其功能为调用父类的构造方法。
- 如果在子类构造方法的定义中没有明确调用父类的构造方法,则系统在执行子类的构造方法时会自动调用父类的默认构造方法(即无参数的构造方法)。
- 如果在子类构造方法的定义中调用了父类的构造方法,则调用语句必须出现在子类构造方法的第一行。
- 一般来讲,调用super()时参数的个数没有限制,只要其参数列表和父类中的某个构造方法的参数列表相符即可。在通常情况下,没有参数的默认构造方法常被用来初始化父类对象。当然,也可以根据具体情况选择父类其他的构造方法。不管怎样,如果要显式调用父类的构造方法的话,super()调用必须放在子类构造方法的开头位置。
1
2
3
4
5
class SubClass extends SuperClass {
public SubClass() {
super();
}
}
2.3. 多态
2.3.1. 多态
重载一个方法名可以看作是多态的,父子类之间直接或间接重写的方法名要由对象在运行时确定将调用哪个方法,这也是多态。 Java规定,这种情况下要执行的是与对象真正类型(运行时类型)相关的方法,而不是与引用类型(编译时类型)相关的方法。
2.3.2. 静态类型和动态类型
- 静态类型∶是出现在声明中的类型。也称为引用类型,是在代码编译时确定下来的。
- 动态类型:运行过程中某一时刻变量指向的对象的类型,这是它此刻的真正类型。变量的动态类型会随运行进程而改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PolymorphismDemo {
public static void main(String[] args) {
SuperClass superClass = new SuperClass(); //静态与动态类型一致
SubClass subClass = new SubClass(); //静态与动态类型一致
SuperClass superClass1 = new SubClass(); //静态与动态类型不一致
superClass.method(); //SuperClass!
subClass.method(); //SubClass!
superClass1.method(); //SubClass!
}
}
class SuperClass {
public void method() {
System.out.println("SuperClass!");
}
}
class SubClass extends SuperClass {
@Override
public void method() {
System.out.println("SubClass!");
}
}
2.3.3. 绑定
- 动态绑定︰或称后绑定,调用稍后可能被覆盖的方法的这种处理方式。动态绑定一定要到运行时才能确定要执行的方法代码。
- 静态绑定:或称前绑定,在编译过程中能确定调用方法的处理方式。
3 终极类与抽象类
- 关键字final:表示终极,既可以修饰一个类,也可以修饰类中的成员变量或成员方法。顾名思义,用这个关键字修饰的类或类的成员都是不能改变的。如果一个类被定义为final,则它不能有子类;如果一个方法被定义为filial ,则不能被覆盖;如果一个变量被定义为final,则它的值不能被改变。
- 关键字abstract:表示抽象,它可以用于类或方法。使用abstract修饰的方法的方法体为空,修饰的类必须被子类继承。
3.1. 终极类
3.1.1. 概念
被标记为final的类将不能被继承,这样的类称为终极类或终态类。
3.1.2. 格式
1
2
3
final class 终极类名{
类体
}
3.2. 终极方法
3.2.1. 概念
被标记为final的方法将不能被覆盖。
3.2.2. 格式
1
2
3
final 返回值类型 终极方法名([参数列表]){
方法体
}
3.3. 终极变量
3.3.1. 概念
一个变量被标记为final,称为终极变量或终态变量。实际上它会成为一个常量。 如果将一个引用类型的变量标记为final,那么这个变量将不能再指向其他对象,但它所指对象中的属性值还是可以改变的。
3.3.2. 格式
1
final 终极变量类型 终极变量名=xxx;
3.4. 抽象类
3.4.1. 概念
- 抽象类:定义了方法但没有定义具体实现的类。通过关键字abstract。
- 抽象方法︰每一个未被定义具体实现的方法也应标记为abstract,这样的方法称为抽象方法。
3.4.2. 格式
1
2
3
4
public abstract class 抽象类名{
public abstract 返回值类型 抽象方法名([参数列表]);
类体;
}
3.4.3. 注意
- 在程序中不能用抽象类作为模板来创建对象,必须生成抽象类的一个非抽象的子类后才能创建实例。
- 抽象类可以包含常规类能够包含的任何成员方法,因为子类可能需要继承这些方法。也可以包含构造方法。
- 抽象类中通常会包含抽象方法,这种方法只有方法的声明,而没有方法的实现。这些方法将在抽象类的子类中被实现。除了抽象方法,抽象类中也可以包含非抽象方法,反之,不能在非抽象的类中定义抽象方法。也就是说,只有抽象类才能具有抽象方法。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class Employee {
final int basic = 2000;
abstract void salary();
}
class Manager extends Employee {
@Override
void salary() {
System.out.println("薪资等于" + basic * 5);
}
}
class Worker extends Employee {
@Override
void salary() {
System.out.println("薪资等于" + basic * 2);
}
}
4 接口
接口:一个“纯”的抽象类。它允许创建者规定一个类的基本形式,包括方法名、参数列表以及返回值类型,但不规定方法体。因此在接口中所有的方法都是抽象方法,都没有方法体。
Java允许一个类实现多个接口,从而实现了多重继承的能力。
4.1. 接口的定义
4.1.1. 格式
1
2
3
[接口修饰符] interface 接口名 [extends 父接口列表]{
...//方法原型或静态常量
}
4.1.2. 注意:
- 接口与一般类一样,本身也具有数据成员变量与方法,但数据成员变量一定要赋初值,且此值不能再更改,而方法必须是"抽象方法”。
- 在接口中定义的成员变量都默认为终极静态变量,即系统会将其自动添加final和static这两个关键字,并且对该变量必须设置初值。
4.2. 接口的实现
要实现接口,可以在类的声明中用关键字implements
来表示。
语句的格式︰
1
2
3
public class 类名 implements 接口名[,接口名[,接口名]]{
/*抽象方法及终极静态变量的定义*/
}
注意:
- 接口中的所有抽象方法必须在类或子类中实现。
- Java程序中,可以在implements后面声明多个接口名,也就是一个类可以实现多个接口。
- 在实际应用中,并非接口里的所有方法都需要用到。这时有一个简单的方法,即用一对大括号来表示一个方法的空方法体。
- 实现一个接口的类也必须实现此接口的父接口。
- Java允许省略定义数据成员的final关键字、方法的public及abstract关键字不能直接由接口来创建对象,而必须通过由实现接口的类来创建。