面向对象三大特性之一:继承
为什么要有继承
直接上例子(下面图片中的代码为省略代码,建议写成标准的javabean类)
有一个学生类,学生的属性有 姓名和年龄,行为有吃饭,学习,睡觉。
有一个老师类,老师的属性有 姓名和年龄,行为有吃饭,教书,睡觉。
有一个班主任类,班主任的属性有 姓名和年龄,行为有吃饭,教书,管纪律,睡觉。
这些类都有相同的内容,比如 有着共同的属性:姓名和年龄,有着共同的行为:吃饭和睡觉。
这种重复的信息会导致需要写大量重复的代码,而程序员最烦这一点,它们会绞尽脑汁的“偷懒”。
那么可不可以将这些共同的内容提取出来呢?答案当然是可以的,利用继承。
在java中,我们可以将这些内容的共性提取到一个类中,然后将刚才的学生类、老师类、班主任类都继承它。
继承的格式
在刚才的例子中,共性的属性有:姓名和年龄,共性的行为有:吃饭和睡觉。
所以我们可以创建一个 Person类来存储这些的共性的内容
接着我们需要将原来的学生类,老师类,班主任类继承 这个Person类。
继承格式如下:
A extends B.
意思是 A 继承了 B,我们把 A 称作 B 的子类,把 B 称为 A 的 父类。
我们来看一下extend的意思
看这个单词的意思,是为了能够更好的理解 继承。
扩展的意思就是在原有 类的内容上加上一些自己的内容。
也解释了为什么刚才我们取的 类名 为Person。
学生、老师、班主任都是 在人这个类中的扩展,都属于人。
所以我们这时候就可以改一下之前的代码。
只有这个 学习的方法我们 的 Person类里面没有,所以需要写在子类里,算是学生类特有的方法。
同理 老师和班主任类也是。
继承的好处
由此可见继承 会减少代码的 重复性,同时会使 类与类之间产生了关联。
创建子类对象,调用父类的内容
我们来创建一个对象感受一下继承,还是刚才那个例子。
贴心的idea告诉我们 可以调用哪些内容,既可以调用 父类的成员变量age name和成员方法eat 和 sleep,也可以调用 子类的 study成员方法。
子类可以继承父类的哪些内容
继承并不是可以继承下子类的所有内容。
对于private的修饰的一切如变量、方法,子类都无法调用。
成员变量
我们将Person类的 name 属性 的修饰符 改成了 private。
会发现这里报错了。
所以说明了,子类是无法直接继承 父类的私有化成员变量,但是一般情况下,父类会提供对应的 get 和 set 方法,这些方法一般是public的,子类可以调用。
所以对于 成员变量来说,只要权限够,就可以继承下来。当然虽然 用 private 修饰符不能直接访问,但是往往可以通过 set and get 方法 间接进行访问。
成员方法
对于 成员方法来说,子类是无法继承权限不够的方法。
比如将Person类 的 吃饭 方法改为 private
此时如果我们想通过创建一个学生对象 来调用 父类Person类的 eat 方法,会发现idea都 点不出来。
如果硬要写,贴心的idea也会提醒 这是人家父类自己的私有方法,你没资格调用。
综上所述,其实能不能继承下来,取决于 父类的成员变量和方法是用什么权限修饰符修饰的,如果使用 public 修饰,肯定是能够继承下来的,除了 父类的构造方法,这个继承不了。
构造方法
对于构造方法来说,无论父类的构造方法用什么修饰符来修饰,都不能继承下来,但是权限如果允许的话,是可以调用父类的构造方法的。
我们再来看刚才那个例子。
在之前学习类和对象的时候,我们都知道,如果没有定义任何的构造方法时,编译器会自动补充一个 无参数的构造方法。
比如刚才的Person类,其实它会自动补充成这样。
对于我们的子类来说,刚才在 Student类中也没有写 构造方法,所以编译器也会自动补上。
其实在我们创建一个子类对象时(比如Student类时),其实也同时会创建一个父类对象(比如此时就是Person类)
这句话就体现在了子类的构造方法上。
刚才我们没有写子类的构造方法时,其实编译器会自动补充以上代码。
super关键字
这个 super(); 的意思就是调用我们的父类的构造方法,跟 this(); 会调用本类的构造方法一样。
之前我们学习了 this 关键字,它代表的是 调用者的地址值,那么同样,super则代表着调用者 的父类的地址值
在创建子类对象的时候,会先 new 一个父类的对象,此时子类会有一个 super 关键字来记录 父类 的地址值。
这个super就跟 this 的使用一模一样,接着刚才的例子我们来演示一下。
我们创建了一个父类,其中包含一个成员变量 num。
再创建一个子类继承父类。
其中包含一个成员变量 num 和 非静态方法 test 方法,其中方法内也有一个 局部变量 num,并且打印 num 变量。
如果我现在创建一个 Zi 类对象,然后调用Zi 类 的 test方法,那么会打印几呢?
答案是10.
这里打印 10 的原因是 test 方法中的 num 为一个局部变量,离打印语句最近,局部优先原则,所以会打印 10。
当然如果想打印 20的num 也是可以的,打印30的num也是可以的。
这个时候就需要用到 this关键字和 super 关键字。
如果换成this.num,那么就会打印 20的 num。
如果换成 super.num ,那么则会打印 30的 num。
当然这是父类和子类有重名的情况,没重名就更简单了,先在局部找,再在子类找,再在父类找,范围会越来越大。
调用 super 来 初始化父类的成员变量
super 关键字其实有一个很关键的作用,就是初始化父类的成员变量。
我们再次回到最开始的例子。
刚开始为了引入继承 没有写 带有全部参数的构造方法,我们现在来补上。
写成这样我们是不可以在创建对象的时候初始化的。
所以也证明了子类无法继承父类的构造方法,那么怎么解决呢,我们来修改一下子类的构造方法。
子类刚才的代码是这样的,只有一个无参构造,所以传不了参数。
所以得这样写。
通过父类的构造方法进行对父类的成员变量进行初始化。
说明一下,调用这个super的构造方法时,都必须是 该构造方法的第一行,前面不能写任何的其他语句。
方法的重写
方法的重写很简单,我们来看下面例子。
这里有一个 Animal 类,这里为了方便就不写javabean类了。
其中这个类有一个 eat方法。
接着我们 创建一个Dog类继承 Animal类。
Animal 中有一个 eat方法。
此时可以对eat方法进行重写。
也就是再写一个eat方法,其中返回值类型、函数名、函数参数都需要一样,此时就会覆盖掉父类的eat方法。
此时我们如果创建一个Dog对象,再调用 eat方法,调用的就会是子类的eat方法了,而不是父类的eat方法,是吃狗粮而不是吃东西。
这里会打印 null 的原因是 我们没有对 对象进行初始化,当一个对象的成员变量没有被初始化的时候,如果是引用数据类型,那么默认会存储 null 。
@Override注解
如果我们需要重写父类的方法,那么最好在方法前面加上 @Override,比如下面这样
这个注解的意思是 告诉编译器 我要重写方法,此时如果你重写eat方法时 与父类的 函数名不同、参数不同或返回类型不同,那么就会直接报错。
比如下面这个例子
当方法名发生改动时,这里就会直接报错。
但是如果我们没有写这个 注解时,编译器会认为这是 子类的特有方法。
这与我们的原意不符,所以加上注解可以防止 重写的时候写错。
java继承的特点
在java中,一个类只能继承一个类,不能一个类同时继承多个类,但是可以多层继承,多层继承的意思是 比如 A类 继承 B类,B类再继承与C类。
在java中,如果一个类没有继承别的类,那么会自动继承 Object类。
虚方法表
假设有这么一个继承结构
B类继承于A类,C类继承于B类,以此类推。
此时假设在A类中定义了一个下面的 方法。
根据之前的就近原则,在找 num 的时候是一层一层往上找,如果子类没有则在父类当中找,如果还没有,则在父类的父类当中继续找,如果一直找不到,那么就会编译错误,这是对于成员变量来说是这么找的。
那么如果B类、C类…到H类当中的方法很多,那么如果也是一层一层的找就会很费时间,所以便有了虚方法表。
虚方法表是在 加载类的时候就会出现的。
加载类的时候会先加载 最上头的类,比如这个例子中的A类,此时A类的方法,就会被记录到虚方法表中。(假设虚方法表当中的横线为方法)
当然这些记录的方法是有要求的,需要 非 static、非 private、非 final的。
同理 B类加载的时候也会 往虚方法表上 记录。
所以方法重写,就会把之前的方法给覆盖掉,虚方法表记录的就会是子类重写的那个方法。
之后如果调用一个方法,那么就会直接在虚方法表中找,从而提高效率
完