目录
8.4 例子4:this 主动调用成员变量,super 主动调用父类变量
1. 封装的意义是什么?
封装想必大家都不陌生,众所周知,Java最重要的一个特点就是面向对象,对象代表什么,就要封装什么样的数据,并提供数据对应的内容。
我们可以利用封装将以前的一些零散的属性信息,方法封装在一起,组成一个整体,这个整体就是我们常说的对象。在没有封装以前,我们就需要对这个零三的属性和方法分别做处理,将它们作为参数传入到方法中,有了封装之后,我们只需要一个对象传入方法中,就可以解决以前的所有烦恼,非常的方便。
举个最简单的例子,现在有一个打印学生信息的类,类中有一些属性和方法
现在有一业务需求,要求打印学生的相关信息。
(1)没有封装加入:我们就需要把学生的信息全部作为参数传递到方法中,有多少个属性就要传递多少个参数;
(2)有了封装的加入:我们将学生的信息封装到学生对象中,将 get 和 set 方法也封装到学生对象中,然后将对象作为参数传递到方法中,这样当我们需要打印学生信息时,只需要通过对象的 get 方法获取到属性信息,非常的方便,极大的简化的业务的复杂度。
2. 为什么需要继承?
我来说明一个场景引出一个问题,同学们或许就会明白了。
假设现在的业务中,我有两个类,一个是学生类 Student,一个是老师类 Teacher。它们的属性和方法如下图所示,已经封装完成
仔细观察上面两个类,有没有发现什么问题。其实很显然,这两个类重复的代码太多了,那么有没有什么办法能够解决这个问题呢?我们可不可以把重复的代码抽取出来重复使用呢?就像封装一样。
当然是可以的,那么接下来就需要用到今天的重点——继承。
3. 继承是什么?如何使用?
在实际开发过程中,基本都会无法避免的产生一些重复相似的属性和方法,为了避免让我们项目代码过于冗余和臃肿,就引入了继承这么一个概念。
Java 中关于继承提供了一个关键字 extend,我们使用这个关键字,就可以让一个类与另一个类之间产生继承关系,使用方法也比较简单,只需要在一个类的后面写上 "extend 被继承类类名"。
使用继承,我们就可以对上面的 Student 类和 Teacher 类进行优化,将两个类的公共部分抽取出来,生成第三个类,让 Student 类和 Teacher 类都继承这第三个类。
如下图所示,Student 类和 Teacher 类都被称为子类(也叫派生类),Person 类被称为父类(也可以叫超类或基类)。
4. 继承的好处是什么?
(1)通过继承,我们可以将一些类中相同的属性和方法,即重复的代码抽取到一个类中作为父类,提高了代码的复用性,降低的代码的冗余。
(2)青出于蓝而胜于蓝。子类在继承了父类之后,得到了父类中的所有属性和方法(私有属性和方法除外),还可以在自己的类中添加额外的属性和方法,扩展自己的功能,让子类更加强大,以便于满足更多更复杂的业务需求。
5. 设计继承需要注意什么?
请同学们牢牢记住下面这句话,"当类与类之间,存在相同(共性)的内容,并且满足子类是父类中的一种,就可以考虑使用继承优化代码"。
使用继承要满足两点:(1)存在相同内容;(2)一定一定要满足子类是父类中的一种,这一点不能忽视!!!
Student 类和 Teacher 类都是人的一种,所以他们都可以继承 Person 类;
Manager 管理者类和 Coder 程序猿类都是员工的一种,他们可以同时继承 Employee 员工类;
而大家看下面这种情况,我能否让 Coder 程序猿类和 Phone 手机类继承一个 Goods 商品类呢?它们都有相同的属性 id 和名字属性。
答案是不能的,因为在常规认知中,它们两个之间是没有直接或者间接的联系的。
所以再强调一遍,使用继承时一定要满足两点。
第一点:类与类之间有共同的内容;
第二点:子类必须是父类中的一种,如果不是一种就算有相同的属性也不能使用继承;
6. 继承的特点
"Java只支持单继承,不支持多继承,但支持多层继承"。
我们拆分成三句话来说明。
Java 只支持单继承:在 Java 中,一个类只能继承一个类,不能再继承其它的类;可以理解为一个儿子只能有一个父亲,不能有两个;
Java 不支持多继承:假设我现在的 A类 继承了 B类,而且B类中有一个方法 method 打印一句话"我继承了B类",A类 也继承了 C类,C类中也有一个方法 method 打印一句话 "我继承了C类",那么当A类的对象调用 method 方法时,它就蒙了,它不知道该调用哪个,所以为了避免这种情况的发生,Java 中不支持多继承。
Java 支持多层继承:这个意思就是A类继承了B类,B类也可以继承C类,C类也可以继承D类,类似于套娃,无限循环。也好比是四世同堂,儿子——>爸爸——>爷爷——>姥爷。
还有一点需要注意,Java 中的所有类都直接或者间接的继承了 Object 类。
7. 子类到底继承了父类的哪些内容
7.1 继承内容
在一个类中,我们通常会定义 成员变量,构造方法,成员方法。
子类继承父类时,它所能继承的内容如下图所示
(1)父类中的构造方法,不管是不是私有的,子类都继承不了;
(2)父类中的成员变量,不管是不是私有的,子类都能继承,只是父类中的成员变量被锁定了,子类可以继承,但是子类访问不了;
(3)父类中的成员变量,非私有的方法可以继承下来;
7.2 虚方法表的概念
在成员方法的继承中,我要补充一个额外的点,子类在继承父类中的成员方法时,实际上会继承父类的虚方法表。
Java 在最顶级的父类开始,会设立一个虚方法表,它会把这个类当中经常可能会用到的方法单独的抽取出来,放到虚方法表中。
判断一个方法是否会经常被使用需要满足以下三点:
(1)不被 private 修饰;
(2)不被 static 修饰;
(3)不被 final 修饰;
满足这三点的方法,就会被存放到虚方法表中。
在继承的过程中,父类就会把这个虚方法表交给子类,子类在父类虚方法表的基础上,添加自己的虚方法,然后继续交给后续的子类,层层传递,如下图所示。
8. 继承中成员变量的访问特点
在继承中,成员变量的访问特点遵循 "就近原则,谁离我近,我就用谁"。
8.1 例子1:方法优先访问局部变量
如下代码
public class Test9 {
// 定义 main 方法做测试
public static void main(String[] args) {
// 定义 Son 类对象
Son son = new Son();
// 调用方法
son.ziShow();
}
}
// 定义一个父类
class Futher{
// 定义一个字符串
String name = "父类字符串";
}
// 定义一个儿子类继承父类
class Son extends Futher{
// 定义一个字符串
String name = "子类字符串";
// 定义一个方法
public void ziShow(){
String name = "方法内部的字符串";
System.out.println(name);
}
}
运行上述代码,就可以在控制台看到,打印出的是方法内部的 name 字符串,因为方法内部的字符串距离方法最近
8.2 例子2:方法其次访问成员变量
刚才的演示中,方法内部本身就有 name 字符串,那么现在我将方法内部的字符串删除,再重新运行,就可以在控制台中看到如下结果,
所以可以得出结论,当继承中方法访问的变量在方法内部存在时,会优先就近访问方法内部的局部变量;当方法内部没有变量可以访问时,会去访问类中的成员变量。
8.3 例子3:方法最后访问父类变量
我们对上面的方法再做修改,将方法类中的成员变量 name 也删除,再次运行,得出如下结果
结合上面三个例子:可以得出结论,当方法内部没有变量可以访问时,方法会先去访问了当前类的成员变量,如果当前方法类的成员变量也没有可以访问时,会最后去访问父类中的成员变量。
8.4 例子4:this 主动调用成员变量,super 主动调用父类变量
当我们不想访问方法内部的变量时,就可以通过 this 关键字主动调用方法的成员变量,或通过 super 关键字调用父类变量,在原本的代码基础上稍微做修改;
如下所示
8.5 总结
OK,下面我们几句句话来总结上面四个例子得出的结论。
(1)在继承中,方法调用变量时会采用就近原则,当方法想要访问的变量方法内部本身就有时,会最优先访问方法内部的局部变量;
(2)当方法内部访问不到变量时,会去方法当前类成员变量中寻找访问;
(3)如果成员变量中也访问不到时,会最后去父类成员变量访问;
(4)如果想要主动访问当前方法类成员变量,可以通过 this 关键字,如果想要调用父类成员变量,则可以通过 super 关键字。
9. 继承中方法的重写详情
9.1 重写的目的与注意事项
子类在继承了父类的方法之后,可以使用父类中的方法,但在实际开发过程中,子类继承父类之后,通常会扩展自己额外的功能,那么此时从父类继承下来的方法可能就满足不了业务需求了,这个时候就需要用到方法的重写,我们可以在子类冲重新编写重复类那里继承下来的方法的执行逻辑,高造成满足我们业务需求的方法。
再进行重写时,需要满足以下几点需求。
(1)重写方法的名称,形参列表必须与父类保持一致;
(2)重写的方法上方要加上 @Override 注解;
(3)子类在重写父类方法时,方法的访问权限必须大于等与父类,方法的返回值范围必须小于等于父类;
(4)父类中的私有方法不能被重写;
(5)父类中的静态方法,即被 static 修饰的方法不能被重写,重写会报错;
9.2 虚方法表再重写中的作用
回顾我们上面提到的虚方法表,父类会把常用的方法存放到虚方法表中交给子类,其实在子类重写父类方法的过程中,其实重写的就是虚方法表中从父类那里得到的方法。
如下图所示,如果B类中重写了从父类C那里继承过来的 method2 方法,那么被重写后的 method2 方法就会被加入到虚方法表中,此时A类从B类那里继承过来的 method2 方法,实际上并不是最原始的C类中的方法,而是已经被B类重写后的 method2 方法。
10. 继承中构造方法的访问特点
(1)在继承体系中,子类不能继承父类的构造方法,但是可以通过 super 调用;
(2)子类在执行构造方法之前,会先去执行父类的无参构造;
我们用一段代码来证明一下
public class Test9 {
// 定义 main 方法做测试
public static void main(String[] args) {
// 无参定义 Son 类对象
Son son = new Son();
// 带参定义 Son 类对象
Son son2 = new Son("张三");
}
}
// 定义一个父类
class Futher{
String name;
public Futher(){
System.out.println("父类无参构造方法先执行");
}
public Futher(String name) {
System.out.println("父类带参构造");
this.name = name;
}
}
// 定义一个儿子类继承父类
class Son extends Futher{
String name;
public Son(){
System.out.println("无参构造创建 Son 对象");
}
public Son(String name) {
System.out.println("带参构造创建 Son 对象");
this.name = name;
}
}
运行得出如下结果
从运行结果我们可以看出,创建子类时不管我们使用无参构造还是带参构造,父类的无参构造方法都会执行。