文章目录
9、类的继承与多态的内容小结
学好继承和多态是面向对象开发语言中非常重要的一个环节。如果在程序中使用继承和多态得当,整个程序的架构将变得非常有弹性,同时可以减少代码的冗余性。
继承机制的使用可以复用一些定义好的类,减少重复代码的编写。多态机制可以动态调整对象的调用,降低对象之间的依存关系。
9.1 概念
9.1.1 继承的概念
首先我们先了解一下类的封装:
封装将类的某些信息隐藏在类内部,不允许外部程序直接访问,只能通过该类提供的方法来实现对隐藏信息的操作和访问。
例如:一台计算机内部极其复杂,有主板、CPU、硬盘和内存, 而一般用户不需要了解它的内部细节,不需要知道主板的型号、CPU 主频、硬盘和内存的大小,于是计算机制造商将用机箱把计算机封装起来,对外提供了一些接口,如鼠标、键盘和显示器等,这样当用户使用计算机就非常方便。
封装的特点:
- 只能通过规定的方法访问数据。
- 隐藏类的实例细节,方便修改和实现。
继承
概念:继承是面向对象的三大特征之一。继承和现实生活中的“继承”的相似之处是保留一些父辈的特性,从而减少代码冗余,提高程序运行效率。Java 中的继承就是在已经存在类的基础上进行扩展,从而产生新的类。已经存在的类称为父类、基类或超类,而新产生的类称为子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。
格式:
修饰符 class class_name extends extend_class {
// 类的主体
}
Java 的继承通过 extends 关键字来实现,extends 的英文意思是扩展,而不是继承。extends 很好的体现了子类和父类的关系,即子类是对父类的扩展,子类是一种特殊的父类。从这个角度看,使用继承来描述子类和父类的关系是错误的,用扩展更恰当。
与C++的区别
Java 与 C++ 定义继承类的方式十分相似。Java 用关键字 extends 代替了 C++ 中的冒号(:)。
在 Java 中,所有的继承都是公有继承, 而没有 C++ 中的私有继承和保护继承。
类的继承不改变类成员的访问权限,也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性,并且子类不能获得父类的构造方法。
单继承
Java 语言摒弃了 C++ 中难以理解的多继承特征,即 Java 不支持多继承,只允许一个类直接继承另一个类,即子类只能有一个直接父类,extends 关键字后面只能有一个类名。例如,如下代码会导致编译错误:
class Student extends Person,Person1,Person2{…}
class Student extends Person,extends Person1,extends Person2{…}
很多地方在介绍 Java 的单继承时,可能会说 Java 类只能有一个父类,严格来讲,这种说法是错误的,应该是一个类只能有一个直接父类,但是它可以有多个间接的父类。
例如,Student 类继承 Person 类,Person 类继承 Person1 类,Person1 类继承 Person2 类,那么 Person1 和 Person2 类是 Student 类的间接父类。下图展示了单继承的关系。
从图中可以看出,三角形、四边形和五边形的直接父类是多边形类,它们的间接父类是图形类。图形类、多边形类和三角形、四边形、五边形类形成了一个继承的分支。在这个分支上,位于下层的子类会继承上层所有直接或间接父类的属性和方法。如果两个类不在同一个继承树分支上,就不会存在继承关系,例如多边形类和直线。
如果定义一个 Java 类时并未显式指定这个类的直接父类,则这个类默认继承 java.lang.Object 类。因此,java.lang.Object 类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的 Java 对象都可调用 java.lang.Object 类所定义的实例方法。
使用继承的注意点:
- 子类一般比父类包含更多的属性和方法。
- 父类中的 private 成员在子类中是不可见的,因此在子类中不能直接使用它们。
- 父类和其子类间必须存在“是一个”即“is-a”的关系,否则不能用继承。但也并不是所有符合“is-a”关系的都应该用继承。例如,正方形是一个矩形,但不能让正方形类来继承矩形类,因为正方形不能从矩形扩展得到任何东西。正确的继承关系是正方形类继承图形类。
- Java 只允许单一继承(即一个子类只能有一个直接父类,但是可以通过多态与接口实现类似多继承),C++ 可以多重继承(即一个子类有多个直接父类)。
继承的优缺点
在面向对象语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:
- 实现代码共享,减少创建类的工作量,使子类可以拥有父类的方法和属性。
- 提高代码维护性和可重用性。
- 提高代码的可扩展性,更好的实现父类的方法。
自然界的所有事物都是优点和缺点并存的,继承的缺点如下:
- 继承是侵入性的。只要继承,就必须拥有父类的属性和方法。
- 降低代码灵活性。子类拥有父类的属性和方法后多了些约束。
- 增强代码耦合性(开发项目的原则为高内聚低耦合)。当父类的常量、变量和方法被修改时,需要考虑子类的修改,有可能会导致大段的代码需要重构。
9.1.2 多态的概念
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载
,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
Demo
1)创建一个Animal类作为父类
package Polymorphism;
public class Animal {
public String name;
public int age;
//构造方法
Animal(String name, int age){
this.age = age;
this.name = name;
}
//用于重写,多态的方法
public void sayHello(){
System.out.println("Animal say its age is "+this.age+" name is "+this.name);
}
}
2)两个子类Dog,Cat
package Polymorphism;
public class Cat extends Animal {
private String hobby;
Cat(String name, int age, String hobby){
super(name,age);
this.hobby = hobby;
}
public void sayHello(){
System.out.println("Cat say:我的爱好是"+this.hobby);
}
}
package Polymorphism;
public class Dog extends Animal {
private String hobby;
Dog(String name, int age, String hobby){
super(name,age);
this.hobby = hobby;
}
public void sayHello(){
System.out.println("Dog say:我的爱好是"+this.hobby);
}
}
3)测试类Test,体现多态
package Polymorphism;
/**
* 需求:测试多态的条件:
* 1.满足继承关系
* 2.使用向上转型
* 3.方法重写
*/
public class test_1 {
public static void main(String []args){
//创建父类Animal
Animal an = new Animal("祖宗",223);
//Animal
an.sayHello();
//cat
an = new Cat("Tom",6,"catch mouse");//向上转向,必须有继承关系
an.sayHello();//方法重写
//dog
an = new Dog("Jimmy",8,"catch cat");
an.sayHello();
}
}
运行结果:
9.2 一些关键字
9.2.1 InstanceOf(双目运算符)
严格来说 instanceof 是 Java 中的一个双目运算符,由于它是由字母组成的,所以也是 Java 的保留关键字。在 Java 中可以使用 instanceof 关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例
,语法格式如下所示。
boolean result = obj instanceof Class
其中,obj 是一个对象,Class 表示一个类或接口。obj 是 class 类(或接口)的实例或者子类实例时,结果 result 返回 true,否则返回 false。
Demo or Test
package Polymorphism;
import java.util.ArrayList;
import java.util.List;
/**
* 目的:
* 练习使用Instanceof来判断类,接口,对象的实例
* 运算符简介:
* instanceof 是 Java 中的一个双目运算符,由于它是由字母组成的,所以也是 Java 的保留关键字。
* 在 Java 中可以使用 instanceof 关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例
*/
public class InstanceOf_Test {
public static void main(String[]args){
//创建一个对象
Object oj = new Object();
//判断类型
System.out.println( oj instanceof Object);
//判断继承的类,这个时候的object是object定义的,没有使用向上转型
System.out.println(oj instanceof InstanceOf_Test);//false
//判断接口
ArrayList arrayList = new ArrayList();
System.out.println(arrayList instanceof List);
//反过来
List array = new ArrayList();//这里使用了向上转型,true
System.out.println( array instanceof ArrayList );
//与null比
// Integer i = 1;
// System.out.println(i instanceof null);无法编译,null与任何比较都是false
//必须为引用类型, 编译器会检查 obj 能否转换成右边的 class 类型,如果不能转换则直接报错,如果不能确定类型,则通过编译。
//int l = 9;
//System.out.println(l instanceof Integer);
InstanceOf_Test in = new InstanceOf_Test();
System.out.print(in.toString());//打印出类的信息
//System.out.print(in instanceof String);//但是无法转化为String
}
}
运行结果:
9.2.2 super and this
1.super
由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用 super 关键字。super 可以用来访问父类的构造方法、普通方法和属性。
super 关键字的功能:
在子类的构造方法中显式的调用父类构造方法
访问父类的成员方法和变量。
public class Person {
public Person(String name) {//定义了有参构造函数就不会有默认缺省构造函数
}
}
public class Student extends Person {
}
会发现 Student 类出现编译错误,提示必须显式定义构造方法,错误信息如下:
Implicit super constructor Person() is undefined for default constructor. Must define an explicit constructor
在本例中 JVM 默认给 Student 类加了一个无参构造方法,而在这个方法中默认调用了 super(),但是 Person 类中并不存在该构造方法,所以会编译错误。
Demo
声明父类 Person,类中定义两个构造方法。示例代码如下:
public class Person {
public Person(String name, int age) {
}
public Person(String name, int age, String sex) {
}
}
子类 Student 继承了 Person 类,使用 super 语句来定义 Student 类的构造方法。示例代码如下:
public class Student extends Person {
public Student(String name, int age, String birth) {
super(name, age); // 调用父类中含有2个参数的构造方法
}
public Student(String name, int age, String sex, String birth) {
super(name, age, sex); // 调用父类中含有3个参数的构造方法
}
}
super和this的区别
this 指的是当前对象的引用,super 是当前对象的父对象的引用。
下面先简单介绍一下 super 和 this 关键字的用法。
super 关键字的用法:
- super.父类属性名:调用父类中的属性
- super.父类方法名:调用父类中的方法
- super():调用父类的无参构造方法
- super(参数):调用父类的有参构造方法
如果构造方法的第一行代码不是 this() 和 super(),则系统会默认添加 super()。
this 关键字的用法:
- this.属性名:表示当前对象的属性
- this.方法名(参数):表示调用当前对象的方法
- 当局部变量和成员变量发生冲突时,使用this.进行区分。
关于 Java super 和 this 关键字的异同,可简单总结为以下几条。
- 子类和父类中变量或方法名称相同时,用 super 关键字来访问。可以理解为 super 是指向自己父类对象的一个指针。在子类中调用父类的构造方法。this 是自身的一个对象,代表对象本身,可以理解为 this 是指向对象本身的一个指针。在同一个类中调用其它方法。
- this 和 super 不能同时出现在一个构造方法里面,因为 this 必然会调用其它的构造方法,其它的构造方法中肯定会有 super 语句的存在,所以在同一个构造方法里面有相同的语句,就失去了语句的意义,编译器也不会通过。
this( ) 和 super( ) 都指的是对象,所以,均不可以在 static 环境中使用,包括 static 变量、static 方法和 static 语句块。
- 从本质上讲,
this 是一个指向对象本身的指针, 然而 super 是一个 Java 关键字。
9.3 类型转化(存在继承关系的类的转换)
将一个类型强制转换成另一个类型的过程被称为类型转换。本节所说的对象类型转换,是指存在继承关系的对象,不是任意类型的对象。当对不存在继承关系的对象进行强制类型转换时,会抛出 Java 强制类型转换(java.lang.ClassCastException)异常。
Java 中引用类型之间的类型转换(前提是两个类是父子关系)主要有两种,分别是向上转型(upcasting)和向下转型(downcasting)。
9.3.1 向上转型(用于多态体现)
向上转型就是把子类对象直接赋给父类引用,不用强制转换。使用向上转型可以调用父类类型中的所有成员,不能调用子类类型中特有成员,最终运行效果看子类的具体实现。
9.3.2 向下转型
与向上转型相反,子类对象指向父类引用为向下转型,语法格式如下:
sonClass obj = (sonClass) fatherClass;
其中,fatherClass 是父类名称,obj 是创建的对象,sonClass 是子类名称。
向下转型可以调用子类类型中所有的成员,不过需要注意的是如果父类引用对象指向的是子类对象,那么在向下转型的过程中是安全的,也就是编译是不会出错误。但是如果父类引用对象是父类本身,那么在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现我们开始提到的 Java 强制类型转换异常
,一般使用 instanceof 运算符来避免出此类错误。
例如,Animal 类表示动物类,该类对应的子类有 Dog 类,使用对象类型表示如下:
Animal animal = new Dog(); // 向上转型,把Dog类型转换为Animal类型
Dog dog = (Dog) animal; // 向下转型,把Animal类型转换为Dog类型,这里的animal本身指向的是Dog类
Animal animal = new Animal();
Dog dog = (Animal) animal;//不安全,运行可能出错
//
Animal animal = new Cat();//Animal animal = new Dog();
if (animal instanceof Cat) {//加一个判断
Cat cat = (Cat) animal; // 向下转型
...
}
9.3.3 强制转型
向下转型就是强制转型,向上转型不必要使用强制转型。
不具备继承关系的类不能够使用强制转型。
Dog dog = new Dog();
Cat cat = (Cat)dog; // 编译出错,不允许把Dog对象类型转换为Cat对象类型