2.继承
继承
(1)继承的概念
在实际生活中,继承的例子随处可见。
图3.16.3所示的就是一个简单的继承关系,鸽子和老鹰属于飞禽,狮子和老鼠属于走兽,飞禽和走兽又都是属于动物,这样就构成一个继承树。动物都具有觅食、睡眠、行动的能力,飞禽和走兽都具有动物的特征。除了具有觅食、睡眠、行动的能力外,飞禽还会飞,走兽还会奔跑。下层的子类会继承上层的直接父类和间接父类的特征和行为。
图3.16.3 生活中的继承
继承符合的关系是is-a的关系(例如,飞禽是一种动物,狮子是一种走兽)。父类更通用,子类更具体。父类具有更一般的特征和行为,而子类除了具有父类的特征和行为,还具有一些自己的特殊特征和行为。
(2)继承的定义
【代码16.5】定义Mouse(鼠)
鼠:属性(id、姓名),方法(吃、睡、自我介绍、打洞)。
【代码16.6】定义Bird(鸟)类
鸟:属性(id、姓名),方法(吃、睡、自我介绍、飞)
我们看到,鼠和鸟因为都是动物,所以都具有动物的一般特性,所以代码16.5和16.4中有若干的代码是重复的。编程的时候,重复的代码是要避免的,否则,代码臃肿、结构不清晰、维护困难(因为相同的代码多次出现,所以在需要修改代码的时候,就需要修改多处,这样容易造成不一致)。
这时候,我们把两个类中共通的内容抽取成一个父类Animal(动物),子类中不再需要重复写父类中的内容,通过继承父类的特征,子类只需要写和父类不同的内容。
【代码16.7】用继承重新编写代码16.5和代码16.6
可以看到父类Animal中的eat()方法、sleep()方法、introduce()方法,都自动继承在子类中,子类中只需要写与父类相比扩展了的内容。
子类自动继承父类的属性和方法,从而避免了重复代码,有效提高了代码的复用程度。
继承的定义语法如下:
在Java中,extends后面只能有一个类,也就是直接父类只能有一个,这叫作单继承。父类中除了构造方法外,其他内容都继承到子类中。但是在子类中,继承自父类的成员是否可以直接访问还要看访问权限。
(3)子类构造方法
子类构造方法
如代码16.7的第23、32行,在创建子类对象的时候,构造方法参数id和name要传入当前对象的id和name属性域,但是,id和name在父类中是private的,在子类中不能访问,所以子类构造方法的第一句调用父类构造方法super(id,name),将参数传入。
子类构造方法的第一句话一定是调用父类构造方法。子类构造方法的第一句如果没有写调用父类构造方法的话,系统会自动添加无参super(),在这种情况下,父类中必须要有无参构造方法,不然子类构造方法中的super()就会编译出错。所以,在一般情况下,定义一个类时,都会提供无参构造方法,本类中可能会用,子类中也可能会用。
(4)方法重写(覆盖)
重写和super
重写的特殊情况
protected
子类会继承父类的内容,例如,子类Mouse继承了来自父类Animal的成员变量id、name,继承了来自父类Animal的成员方法eat()、sleep()、introduce()。子类可以添加属于自己的内容,例如,子类Mouse添加了属于自己的成员方法dig()。另外,子类可以改写从父类继承来的成员方法,参见以下这个例子。
Ostrich(鸵鸟)是一种鸟,是Bird的子类,但是鸵鸟飞的方式和其他鸟飞的方式不同。鸵鸟从父类鸟继承的fly()方法需要重写。
【代码16.8】重写案例(Bird类如代码16.7的第30行至第37行)
代码16.8中第5行的子类Ostrich除了继承父类的成员外,还添加了新的成员方法jump()。Ostrich飞的方式和父类Bird飞的方式不同,代码16.8的第8行在Ostrich类中写了一个新的fly()方法,这个方法的头部和父类中fly()方法的头部完全相同,但是方法体的内容不同,所以子类中的fly()方法会覆盖掉继承自父类的fly()方法。
当子类Ostrich对象调用fly()方法时,会调用Ostrich中重写的fly()方法,见代码16.8的第18行。
子类重写父类的方法也叫作覆盖。方法重写(覆盖)的规定:方法名和参数列表必须相同,子类中方法的返回值类型小于或等于父类方法的返回值类型,子类中抛出的异常类型小于或等于父类中抛出的异常类型,子类方法的访问权限需要大于或等于父类方法的访问权限,所谓“两同两小一大”。
(5)super的用法
super指代当前类的父类对象。
①类的构造方法的第一句总是无参的super()或者有参的super(参数列表),用来调用父类的构造方法,否则,系统会自动添加一个无参的super()。
②当子类覆盖了父类的方法时,如果要在子类中调用父类中原有的方法,则可用super调用,如代码16.9的第10行。
③当子类中定义了和父类同名的成员变量,并且有权限访问父类的成员变量时,在子类中用super。成员变量引用父类成员变量,如代码16.10中的第18和和第20行。
【代码16.9】super用法例程1(Bird类同前,见代码16.7的第30行到第37行)
【代码16.10】super用法例程2
运行结果:
调用父类的被重写的go()方法:
in Father!
父类中重名的成员变量f=1
son的f=3
在代码16.10中,Son类的对象中有两个f,一个f是继承自父类Father的,另一个f是Son中的,两个并存。并且因为访问权限是默认类型,所以Son类中可以访问两个f。而对于另一个并存的成员变量a,因为访问权限是private,所以在子类中不能访问父类中的a,只能访问子类自己的a。
Object类-1
本例只是用来说明语法,在实际的类设计中,要根据具体的设计需求给出恰当的定义。
(6)Object类
一个类在定义的时候,若没有使用extends关键字明确标识父类,那么它的父类默认就是java.lang.Object,所有的类都直接或者间接地继承自Object类,Object类是所有类的“祖先类”。由此,所有Java对象都可以调用Object类的方法。
Object类-2
对于任意一个没有显式父类的类,它的父类就是Object类,这个类继承了Object类的方法,如图3.16.4所示。Test类没有定义除了main()外的其他方法,当前可调用的方法均来自父类Object。
图3.16.4 任意一个Test类从Object类继承的方法
下面介绍两个最常用的来自Object类的方法。
①Object类中的toString()方法
public String toString()在需要将一个对象转化为字符串的时候调用。
【代码16.11】toString()方法的使用
运行结果:
test2.Person@15db9742
可以按照自己的需求重写toString()方法,如代码16.12。
【代码16.12】toString()方法的重写
运行结果:
name:lily,age:20
②Object类中的equals()方法
【代码16.13】equals()方法的使用
运行结果:
两人不相同!
如果两个Person对象的id、name、age都相同,那我们就认定两个对象是相同的,这就需要重写equals()方法,见代码16.14。
【代码16.14】equals()方法的重写
运行结果:
两人相同!
Object类的其他方法,大家可以在后续用到的时候再扩展。