继承(extends)
文章目录
前言
继承是面向对象的三大特征之一。可以使子类具有超类的属性和方法,还可以在子类中重新定义,追加属性和方法。
一、继承问题的引出
下面首先看两段代码
public class Father{
public String name;
public String age;
private String job;
public void setName(String name){
this.name=name;
}
public void setAge(String age){
this.age=age;
}
public void setJob(String job){
this.job=job;
}
public String getName(){
return this.name;
}
public String getAge(){
return this.age;
}
public String getJob(){
return this.job;
}
}
public class Son{
public String name;
public String age;
private String school;
public void setName(String name){
this.name=name;
}
public void setAge(String age){
this.age=age;
}
public void setSchool(String school){
this.school=school;
}
public String getName(){
return this.name;
}
public String getAge(){
return this.age;
}
public String getSchool(){
return this.school;
}
}
通过上面的两个程序,我们可以发现代码中有明显的重复部分,但是之前学的内容无法避免这个重复,这就会导致代码量大,不便于维护,于是在Java中引入了继承机制。
二、继承的概念
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
格式:
class 子类 extends 父类{}
那么利用这一点我们可以改进上面的代码:
package text_extends;
public class Father{...}
public class son extends father{
private String school;
public void setSchool(String school){
this.school=school;
}
public String getSchool(){
return this.school;
}
}
public class FSDemo {
public static void main(String[] args) {
father f = new father();
f.setName("王富贵");
f.setAge("35");
f.setJob("程序员");
System.out.println("姓名:"+f.getName()+" 年龄:"+f.getAge()+" 工作:"+f.getJob());
son s = new son();
s.setName("王小明");
s.setAge("17");
s.setSchool("长春市第一中学");
System.out.println("姓名:"+s.getName()+" 年龄:"+s.getAge()+" 学校:"+s.getSchool());
}
}
运行结果:
姓名:王富贵 年龄:35 工作:程序员
姓名:王小明 年龄:17 学校:长春市第一中学
通过代码发现,子类(son)中没有定义name和age,但是仍然可以拥有超类(father)的方法和属性,而且可以有自己的方法和属性,我们可以发现子类更像是对超类的具体化,我们将大范围的定义放在了父类中,将小范围的定义放在了子类中,方便我们使用。
继承的好处:
- 提高了代码的复用性(多个类相同的成员可以放在一个类中)
- 提高了代码的维护性(如果方法需要修改,只需要修改一处即可)
继承的弊端:
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时,子类也变化,削弱了子类的独立性。
注意事项:
- 父类中的私有成员子类无法继承;(与访问权限有关)
- Java中只支持单继承,不可多继承;(一个子类只能有一个超类,但是一个超类可以有多个子类)
- Java中可以多层继承(A继承B,B继承C,那么A也能继承到C中的属性和方法)
三、继承中访问的特点
访问变量:
例一
public class father {
public int number=100;
}
public class son extends father {
public int number = 50;
public void show() {
int number = 30;
System.out.println(number);
}
public static void main(String[] args) {
son s = new son();
s.show();
}
}
运行结果:
30
例二
public class father {
public int number=100;
}
public class son extends father {
public int number = 50;
public void show() {
System.out.println(number);
}
public static void main(String[] args) {
son s = new son();
s.show();
}
}
运行结果:
50
例三
public class father {
public int number=100;
}
public class son extends father {
public void show() {
System.out.println(number);
}
public static void main(String[] args) {
son s = new son();
s.show();
}
}
运行结果:
100
通过三个例子的对比我们可以知道继承访问变量的特点:
- 先在方法内部寻找并访问;
- 然后在子类成员中寻找并访问;
- 最后在超类成员中寻找并访问;
- 如果在之前的范围中都没有找到该变量,则访问失败
访问成员方法:
(访问成员方法的特点和访问变量的特点类似,在此就不举例总结了)
访问成员方法的特点:
- 在子类的方法中寻找并访问;
- 在父类的方法中寻找并访问;
- 如果在之前的范围中都没有找到该变量,则访问失败
构造方法的访问:
在介绍构造方法的访问特点之前,先来学习一个关键字super。
在上面的代码中我们看到当代码的三个范围中都存在同名变量时,我们只能访问到方法中的同名变量,而通过之前学习的关键字this我们可以访问成员变量,但是我们无法访问父类中的变量,于是在Java中就提供了一个关键字super。
来看一段代码:
public class father {
public int number=100;
}
public class son extends father {
public int number = 50;
public void show() {
int number = 30;
System.out.println(number);
System.out.println(this.number);
System.out.println(super.number);
}
public static void main(String[] args) {
son s = new son();
s.show();
}
}
运行结果:
30
50
100
我们可以看到super和this的用法相似,不同的是this是对本类对象的引用,而super是父类储存空间的标识,现阶段可以理解为对父类对象的引用。
现在来介绍继承中对构造方法的访问的特点,先看代码:
public class father {
public father(){
System.out.println("father中的无参方法被调用");
}
public father(String name){
System.out.println("father中的带参方法被调用");
}
}
public class son extends father {
public son(){
System.out.println("son中的无参方法被调用");
}
public son(int age){
System.out.println("son中的带参方法被调用");
}
}
public class Demo {
public static void main(String[] args) {
son s1 = new son();
son s2 = new son(14);
}
}
运行结果为:
father中的无参方法被调用
son中的无参方法被调用
father中的无参方法被调用
son中的带参方法被调用
我们可以看到,无论是调用子类中的无参构造方法,还是调用子类中的带参构造方法,都会访问父类中的无参构造方法,为什么会调用父类的构造方法呢?为什么都会调用超类的无参构造方法呢?
我们都知道,子类会继承超类的属性和方法,可能还会用到超类中的数据,所以在子类初始化之前,超类的数据要完成初始化;因为我们在访问构造方法是要完成数据的初始化,虽然我们在访问子类的带参构造方法是传入了一个数据age,但是这个数据是完成了对子类的初始化,而超类中并没有数据传入,所以此时默认调用超类的无参构造方法。而上面我们说过super关键字,它是超类储存空间的标识,可以看做是对超类成员的引用,所以Java默认每个子类构造方法的第一条语句是:super();,我们也就不难看出没有数据传入超类,所以会默认调用无参构造方法。
通过了解我们也就明白了如何调用父类中的带参方法了,代码如下:
public class father {
public father(){
System.out.println("father中的无参方法被调用");
}
public father(String name){
System.out.println("father中的带参方法被调用");
}
}
public class son extends father {
public son(){
super();
System.out.println("son中的无参方法被调用");
}
public son(int age){
super("王小明");
System.out.println("son中的带参方法被调用");
}
}
public class Demo {
public static void main(String[] args) {
son s1 = new son();
son s2 = new son(14);
}
}
运行结果:
father中的无参方法被调用
son中的无参方法被调用
father中的带参方法被调用
son中的带参方法被调用
继承中访问构造方法的特点:
- 每个子类构造方法的第一条语句默认是super();;
- 子类中的所以构造方法都默认访问超类的无参构造方法;
四、方法重写
概念:
子类中出现和超类重名的方法。
当子类需要父类的属性和方法,但方法主体子类有自己独特的内容时,我们可以进行方法重写。
来看一段代码:
public class father {
public void work(String name){
System.out.println(name+"的今天工作是打100行代码");
}
}
public class son extends father {
public void work(String name){
System.out.println(name+"的今天工作是写100行代码和debug");
}
}
public class Demo {
public static void main(String[] args) {
son s = new son();
s.work("王小明");
}
}
运行结果:
王小明的今天工作是debug
我们可以看到在子类中对超类的方法进行了重写,改变了work方法的内容。那我们如何来调用被改写的超类中的方法呢?
还是要用到super关键字,改写后的代码如下:
public class father{...}
public class son extends father {
public void work(String name){
super.work(name);
System.out.println("和debug");
}
}
public class Demo{...}
这样我们既减少了代码量,又使子类拥有了自己特别的内容。
注意事项:
- 超类的私有方法不能重写;
- 子类重写方法的访问权限不得低于超类;
王小明的今天工作是debug
我们可以看到在子类中对超类的方法进行了重写,改变了work方法的内容。那我们如何来调用被改写的超类中的方法呢?
还是要用到super关键字,改写后的代码如下:
public class father{...}
public class son extends father {
public void work(String name){
super.work(name);
System.out.println("和debug");
}
}
public class Demo{...}
这样我们既减少了代码量,又使子类拥有了自己特别的内容。
注意事项:
- 超类的私有方法不能重写;
- 子类重写方法的访问权限不得低于超类;