初识Java:面向对象程序设计基础入门(2)继承性

导引

面向对象程序设计的三大特性:继承性,封装性,多态性。
阅读本文前,依然需要思考这些问题,并带着它们阅读:
什么是继承?
父类继承究竟继承什么?
子类能否脱离父类,拥有与父类无关的构造方法?
什么是接口?
接口实现了何种意义的继承?

一、继承性

继承的意义在于能够节省一些有从属关系或有直接关联的对象之间的重复代码,也增强了工程开发过程中不同工段接驳的能力。
比方说,在一个学校的数据库系统当中,学校需要管理各种的员工,例如包括:后勤员工、教师职工、干部。每个员工都有相同的信息项,比如:姓名、性别、工资,这几项信息任何员工都有;而教师有教师工号信息,这是其余两类员工没有的,也就是说这些员工既有相同的数据项,也有相异的。
我们可以先定义类Employee来代表员工,每个员工都有姓名,性别,工资,这几项内容都写入Employee类的成员域,也写入对应的成员方法;而后通过将Teacher等类的父类指定为Employee类,让这些具体的员工类直接继承Employee的各种属性。

1.父类继承superclass inheritance

在之前的一些例子当中,我们没有给类定义extends父类,而这样的声明也是正确的。这是因为,Java虚拟机会直接给没有填写父类的类补充直接父类java.lang.Object。
java.lang.Object是一切类的直接或间接父类,任何类或者以java.lang.Object为直接父类,或者不以之为直接父类但其父类的直接或间接父类为java.lang.Object,换言之即必然以java.lang.Object为间接父类。

当前类的构造方法受到其直接父类的构造方法的约束。
当前类的构造方法应当调用其直接父类的构造方法,且调用形如:

super(参数列表);

如果缺失了这一语句,Java虚拟机也会隐式地添加。而如果所调用的直接父类当中并没有当前调用的构造方法,程序将会无法执行(包括参数列表不匹配的情况)

例子:(详情可参考雍俊海《Java程序设计教程》)
假设我们设置了J_Employee类来管理员工,员工拥有工作年限成员;J_Teacher类用于管理员工的子类教职工,教职工除了拥有J_Employee的各种属性,还独有m_classHour成员和一个成员方法。
在这里插入图片描述

package class_and_superclass;

public class J_Employee {
	public int m_workYear;
	public J_Employee()
	{
		m_workYear=1;
	}
	//没有成员方法
}

另一个类J_Teacher:

package class_and_superclass;
public class J_Teacher extends J_Employee
{
	public int m_classHour;//授课的课时数,成员域
	public J_Teacher()
	{
		m_classHour=96;
	}//J_Teacher()构造方法
	public void mb_printInfo()
	{
		System.out.println("该教师的工作年限为:"+m_workYear);
		System.out.println("该教师授课的课时为"+m_classHour);
	}//mb_printInfo()成员方法
	public static void main(String[] args)
	{
		J_Teacher tom=new J_Teacher();
		tom.mb_printInfo();
	}
}

输出结果:

该教师的工作年限为:1
该教师授课的课时为96

我们看到,我没并没有为子类J_Teacher补充上父类的构造方法,这里Java虚拟机会自动为子类的构造方法的开头补充上语句:

super();

因此J_Teacher()构造方法也一样能够将所构造实例对象的成员m_workYear赋值为1.
形如:

public J_Teacher()
	{
		super();
	}

上面的例子让我们看到,子类继承了父类的成员域和构造方法。那么子类是否会继承父类的成员方法呢?
我们来用刚刚的例子做一个实验。

package class_and_superclass;
public class J_Employee {
	public int m_workYear;
	public J_Employee()
	{
		m_workYear=1;
	}
	protected void print_as_em()
	{
		System.out.println("This employee do not want to be fired.");
	}
}

我们做了一些修改,现在父类增加了一个成员方法。我们来看看能否通过子类变量调用这个父类的成员方法。

package class_and_superclass;
public class J_Teacher extends J_Employee
{
	public int m_classHour;//授课的课时数,成员域
	public J_Teacher(int i)
	{
		m_classHour=i;
	}//J_Teacher()构造方法
	public void mb_printInfo()
	{
		System.out.println("该教师的工作年限为:"+m_workYear);
		System.out.println("该教师授课的课时为"+m_classHour);
	}//mb_printInfo()成员方法
	public static void main(String[] args)
	{
		J_Teacher tom=new J_Teacher(1);
		tom.mb_printInfo();
		tom.print_as_em();
		new J_Teacher(2);
		System.gc();
	}
}

输出结果:

该教师的工作年限为:1
该教师授课的课时为1
This employee do not want to be fired.

我们发现,最终tom变量能够调用父类的方法;但是上个章节所介绍的,清理垃圾时的回溯调用并没有起作用,System.gc()语句并没有造成一个新的输出结果。
子类能够继承父类的构造方法,成员域和成员方法,并且保有自身独特性,拥有自身的,父类没有的属性。
在继承过程中,父类的构造方法不可避免地继承入子类的构造方法, 在子类实例对象的构造中造成影响。

注意到,子类和父类有各种形式的牵连,而子类的成员,方法等内含都要丰富于父类。有时可以使用类型转换把子类类型数据转化成父类类型数据。
1.隐式赋值直接转换 子类型>父类型

J_Teacher tom=new J_Teacher();
J_Employee a=tom;

将子类类型变量直接赋值给父类类型变量,a变量直接成为了所要求的,子类转变而来的父类类型变量。
2.隐式强制转换 子类型>父类型

J_Teacher tom=new J_Teacher();
System.out.println(((J_Employee)tom).m_workYear);

当然这个代码等价于直接输出tom.m_workYear的结果,强制转换是无意义的;但是必须指出:
注意:()强制类型转换的优先级并不高于成员符“.”。 也就是说:

J_Teacher tom=new J_Teacher();
System.out.println((J_Employee)tom.m_workYear);

这段代码会报错。因为tom.m_workYear的优先级更高,Java虚拟机认为要将tom.m_workYear变量转换为(J_Employee)格式,这显然是不可能的(将整型类转换为J_Employee型,而两者不存在任何父子关系)
3.显式类型转换 父类型>子类型

J_Teacher tom = new J_Teacher();
J_Employee a = tom;
J_Teacher b = (J_Teacher)a;

把一个子类变量J_Teacher tom 直接赋值给父类变量J_Employee a,得到的 变量a就成为了父类型 J_Employee 类的变量。如果第三行不这样写,而是写成:

   J_Teacher tom = new J_Teacher();
    J_Employee a = tom;
    J_Teacher b = a;
    //>>>报错

看似正确,但其实编译立刻发生错误,这是因为:
可以把子类实例对象赋值给父类实例对象得到父类实例对象(以达到格式转换之目的),但是不可以把父类实例对象赋值给子类实例对象。
这当中的内在原因是,父类含有的信息往往较少,而子类不但继承了父类的信息,还拥有自身的独异属性。所以把父类变量赋值给子类变量会造成子类当中多余的信息项空出无法填入,系统无法给这些无从得知的数据项生成数据,因为报错;而反过来把子类变量赋值给父类变量,系统只需要删去多余的信息项,保留子类和父类都共享的数据项并填入待赋值的父类实例对象当中即可。

此处第二行执行后,a变量已经变为一个子类 J_Teacher 类的变量了。如果要将这个父类变量赋值给子类,就会出错。
另外,这样的代码也是报错的:

J_Employee a = new J_Employee();
J_Teacher b = (J_Teacher)a;
//>>>报错

我们在第二种格式转换当中标注了子类型>父类型,这表明强制格式转换和赋值转换语句一样,系统没有办法生成多余的数据给信息更为丰富的子类实例对象。此处的a是一个父类变量,转换直接报错。
可以把子类实例对象强制格式转换成父类实例对象,但是不可以把父类实例对象强制转换成子类实例对象。
综上所述,父类型转换为子类型需要先新建信息更为丰富的子类型变量,而后利用中间变量b完成类型转换。如果新建语句是new J_Employee();那么就目前的知识而言,没有任何方法可以完成父类到子类的转换。

格式转换还有最后一条规则:
不可以在并无父子关系对应的类型之间转换。
instanceof 语句可以判断一个引用表达式指向的实例对象是否是某种引用类型的实例对象,返回boolean类型值。
比如:

J_Teacher a = new J_Teacher();
J_Employee b = new J_Employee();
J_Employee c = a;
if(a instanceof J_Teacher)System.out.println("a instanceof J_Teacher");
if(a instanceof J_Employee)System.out.println("a instanceof J_Employee");
if(b instanceof J_Teacher)System.out.println("b instanceof J_Teacher");
if(c instanceof J_Employee)System.out.println("c instanceof J_Employee");
if(c instanceof J_Teacher)System.out.println("c instanceof J_Teacher");

输出结果:

a instanceof J_Teacher
a instanceof J_Employee
c instanceof J_Employee
c instanceof J_Teacher

其中只有一个false值,因为b并不能认为是J_Teacher类型。
子类实例对象可以被视作父类实例对象;父类实例对象不可以被视作子类实例对象。

2.接口interface、多重继承multiple inheritance

接口的定义使得类能够从多个接口处继承多重特性。

【接口修饰词列表】interface 接口名 【extends 接口名称列表】
{
	接口体
}

与类一样,一个源文件中至多有一个public 接口或类。接口自带abstract修饰词,因而不必添加此修饰词。
extends 后的接口名称列表罗列了当前接口的一切父接口。当前接口称为这些接口的子接口。
接口没有构造方法,只有成员域和成员方法两个组成部分,它们的定义都和类相同。

详细的接口用法我们会在多态性章节介绍。

猜你喜欢

转载自blog.csdn.net/Quaint_Senator/article/details/88079501