Java继承和多态
继承
定义
继承就是利用现有类创建新类的过程,现有的类称为 父类(基类),新类称为 子类(派生类)。比如现实中:儿子继承了父亲遗产一样。面向对象程序设计中的继承,就是代码重用。
Java中所有的类都继承自 Object 这个父类。
实现
在Java中实现继承需要使用到extends关键字;
[访问修饰符] class 派生类名 extends 基类名 {
成员列表
}
如:
class Student extends Person
{
……
}
访问修饰符 请参看之前章节的内容。
例子
例:
定义抽象类:Person。
public abstract class Person { // 抽象类
private String name; // 私有变量
public String getName() { // Getter方法
return name;
}
public void setName(String name) { //Setter方法
this.name = name;
}
public Person(String name) { // 构造函数,用于初始化name
super();
this.name = name;
}
public abstract String getDesc(); // 抽象类中的抽象方法。 只有声明,没有具体实现。
public String toString(){ // toString方法覆盖了Object类的toString方法
return name + getDesc();
}
}
public class Student extends Person { // 继承类
private String major; // 新增加的数据
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
public Student(String name,String major) { // 构造函数用于初始化
super(name); // 调用超类构造函数
this.major = major;
}
@Override
public String getDesc() { // 必须实现超类中的抽象方法
// TODO Auto-generated method stub
return " : a student, major is " + major;
}
继承的作用
- 当今软件设计的特征
软件规模越来越大
软件设计者越来越多
软件设计分工越来越细 - 引入继承,实现了代码重用;
- 引入继承,实现了递增式的程序设计。
- 继承是能自动传播代码和重用代码的有力工具;
- 继承能够在某些比较一般的类的基础上建造、建立和扩充新类;
- 能减少代码和数据的重复冗余度,并通过增强一致性来减少模块间的接口和界面,从而增强了程序的可维护性;
- 能清晰地体现出类与类之间的层次结构关系。
注意事项
- 继承是单方向的,即派生类可以继承和访问基类中的成员,但基类则无法访问派生类中的成员; 父类不能访问子类方法
- 在Java中只允许 单一继承 方式,即一个派生类只能继承于一个基类,而不能象C++中派生类继承于多个基类的多重继承方式。
继承中的构造方法
- 父类中的构造方法可以被重载,但不能被子类继承,即便它是public的;
- 父类的构造方法负责初始化属于它的成员变量,而子类的构造方法则只需考虑属于自己的成员变量,不必去关注父类的情况。
- 当实例化子类的对象时,必须先执行父类的构造方法,然后再执行子类的构造方法;
- 如果父类还有更上级的父类,就会先调用最高父类的构造方法,再逐个依次地将所有继承关系的父类构造方法全部执行;
- 如果父类的构造方法执行失败,那么子类的对象也将无法实例化。
如
class ParentClass { //定义父类
public ParentClass() { //构造方法
System.out.println("这是父类的构造方法。");
}
}
class ChildClass extends ParentClass { //子类继承于父类
public ChildClass() { //构造方法
System.out.println("这是子类的构造方法。");
}
}
public class ConstructorTest { //该类用于容纳main方法
public static void main(String[] args) {
ChildClass cc = new ChildClass(); //实例化子类对象
}
}
super
如果子类需要调用父类的方法或者属性怎么办?
- 在子类的构造方法中,super关键字可以显式地调用父类的构造方法,用于将参数传递给它;需要注意的是: 该语句必须是子类构造方法的第一条语句
如:
class Point //定义"点"类
{
protected float mX, mY; //x轴坐标和y轴坐标
public Point(float x, float y) //构造方法
{
mX = x;
mY = y;
}
……
}
class Circle extends Point //定义"圆"类继承于"点"类
{
protected float mRadius; //半径
public Circle(float x, float y, float r) //构造方法
{
super(x, y); //显式调用父类构造方法,必须是第一条语句
mRadius = r;
}
……
}
- 如果父类和子类中有同名成员,在子类中默认访问是属于自己的那一个成员;super关键字可以明确地指定要访问父类中的成员;但前提条件是:父类中的该成员不是private的
多态
方法覆盖
定义
- 在类的继承体系结构中,如果子类中出现了与父类中有同原型的方法,那么认为子类中的方法覆盖了父类中的方法(也称为方法重写);
- 通过子类的实例调用被覆盖的方法时,将总是调用子类中的方法,而父类中的方法将被隐藏。
如:
/*如果不但名称相同,而且连方法原型也完全相同的话,则构成方法覆盖*/
class ParentClass { //定义父类
public void fun() {
System.out.println("这是父类中的方法。");
}
}
class ChildClass extends ParentClass {//子类继承于父类
public void fun() { //子类覆盖父类中的方法
System.out.println("这是子类中的方法。");
}
}
class OverriddenTest { //用于容纳main方法
public static void main(String[] args) {
ParentClass parObj = new ParentClass();
parObj.fun(); //父类的实例调用此方法
ChildClass chiObj = new ChildClass();
chiObj.fun(); //子类的实例调用此方法
}
}
方法覆盖的注意事项
- 子类中重写的方法,其访问权限不能比父类中被重写方法的访问权限更低
- 在子类中重写方法时要保持方法的签名与父类中方法的签名一致
引用转型
- 基类(父类)的引用可以指向派生类(子类)的对象。
如:
BaseClass obj = new DerivedClass();
- 如果存在方法覆盖,那么将会调用其派生类中的方法
如:
class Shapes { //基本形状类
public void draw() { //绘图的方法
System.out.println("绘制了一个基本形状。");
}
}
class Circle extends Shapes { //圆形类继承于基本形状类
public void draw() { //覆盖父类的绘图方法
System.out.println("绘制了一个圆形。");
}
}
class Square extends Shapes { //正方形类继承与基本形状类
public void draw() { //覆盖父类的绘图方法
System.out.println("绘制了一个正方形。");
}
}
public class polymorphismDemo {
public static void main(String[] args) {
Shapes obj = new Shapes(); //父类的引用指向父类的实例
obj.draw(); //调用绘图方法
obj = new Circle(); //父类的引用指向子类的实例
obj.draw(); //调用绘图方法
obj = new Square(); //父类的引用指向子类的实例
obj.draw(); //调用绘图方法
}
}
多态
在Java中,使用父类的引用,调用同一个方法,却可以得到不同的调用结果,这就是多态。即: 同一函数,多种形态。
实际上多态包括 动态多态 和 静态多态。
静态多态
- 静态多态也称为编译时多态,即在编译时决定调用哪个方法;
- 静态多态一般是指方法重载;
- 只要构成了方法重载,就可以认为形成了静态多态的条件;
- 静态多态与是否发生继承没有必然联系。
动态多态
动态多态也称为运行时多态,即在运行时才能确定调用哪个方法。
形成动态多态必须具体以下条件:
- 必须要有继承的情况存在;
- 在继承中必须要有方法覆盖;
- 必须由基类的引用指向派生类的实例,并且通过基类的引用调用被覆盖的方法;
由上述条件可以看出,继承是实现动态多态的首要前提。
抽象
抽象方法
基类无法(或者没有必要)提供被覆盖方法的具体实现,那么就可以将这个方法定义为 抽象方法。
[访问权限] abstract 返回值类型 方法名(参数列表){
}
如:
public abstract void draw(){
}
抽象类
如果某个类包含抽象方法,那么这个类就必须定义成 抽象类。
[访问权限] abstract class 类名{
成员列表
}
如:
public abstract class Shapes{
public abstract void draw();
}
抽象类的注意事项
- 抽象类不可以直接实例化,只可以用来继承;
- 抽象类的派生子类应该提供对其所有抽象方法的具体实现;
- 如果抽象类的派生子类没有实现其中的所有抽象方法,那么该派生子类仍然是抽象类,只能用于继承,而不能实例化;
- 抽象类中也可以包含有非抽象的方法;
- 构造方法和静态方法不可以修饰为abstract。
如:
abstract class Shapes { //基本形状类,抽象类
public abstract void draw(); //绘图方法,抽象方法
}
class Circle extends Shapes { //圆形类继承于基本形状类
public void draw() { //实现抽象父类的抽象绘图方法
System.out.println("绘制了一个圆形。");
}
}
class Square extends Shapes { //正方形类继承与基本形状类
public void draw() { //实现抽象父类的抽象绘图方法
System.out.println("绘制了一个正方形。");
}
}
public class abstractDemo { //该类用于容纳main方法
public static void main(String[] args) {
Shapes obj;
obj = new Circle(); //父类的引用指向子类的实例
obj.draw(); //调用绘图方法
obj = new Square(); //父类的引用指向子类的实例
obj.draw(); //调用绘图方法
}
}
接口
接口定义
如果某个类中所有的方法都是 抽象方法,那么可以考虑将该类定义为接口。
[访问权限] interface 接口名{
成员列表
}
如:
public interface IMyInterface{
public void doIt();
}
实现接口
接口只能用于实现,不能实例化(new)。
[访问权限] class 类名 implements 接口名 {
成员列表
}
如:
public class MyClass implements IMyInterface {
public void doIt(){
}
}
接口的注意事项
- 接口中不能定义非抽象方法,也就是说接口中不能包含有函数实体。
接口中的所有方法都默认为抽象方法,无需在每个方法前加abstract关键字。 - 接口的实现类应该提供对接口中所有抽象方法的具体实现,否则将成为抽象类。
与抽象类和它的继承类相似,也可以使用接口的引用指向其实现类的对象,从而达到动态多态的效果。 - Java只支持单继承,而不能象C++那样可以多重继承,接口正是为了弥补这一点。
- Java中还允许一个接口继承于另一个接口,即由父接口派生出子接口。
如:
public interface 子接口名 extends 父接口名 {
成员列表
}
final关键字
final修饰变量
如果将某个变量修饰为final,那么该变量就成为 常量
如:
final double PI = 3.14159;
☆ 常量在声明时必须初始化。
final修饰方法
如果将某个成员方法修饰为final,则意味着该方法 不能被子类覆盖
如:
public final void fun() {
……
}
//如果在派生类中出现同原型的方法,将会报错。
final修饰类
如果将某个类修饰为final,则说明该类 无法被继承
如:
public final class MyClass {
……
}
//任何类想继承于MyClass类都将报错。
类和类之间的关系
有——>has
汽车有轮子。一般来说这种关系对应的是 属性。是——>is
圆形是个形状。一般来说这种关系对应的是 方法。