作者:困了电视剧
专栏:《JavaSE语法与底层详解》
文章分布:这是一篇关于Java面向对象三大特性——继承的文章,在本篇文章中我会分享继承的一些基础语法以及类在继承时代码的底层逻辑和执行顺序。
目录
举个栗子
Java使用类这一引用类型对现实中的实体进行描述,通过类中的成员描述一个事物的属性,通过实例化对象来进行相关的操作,但是世界上的事物错综复杂,事物与事物之间很大可能会存在关联,在程序设计的时候就需要考虑。
我们举一个日常生活中的栗子:人们在日常生活中可能会养一只猫或者是养一条狗,现在我需要用Java对他们进行描述:
- class Cat{
- String name;
- String sex;
- int age;
- public void sleep(){}
- public void eat(){
- System.out.println("吃猫粮");
- }
- public void speak(){
- System.out.println("喵");
- }
- }
-
- class Dog{
- String name;
- String sex;
- int age;
- public void sleep(){}
- public void eat(){
- System.out.println("吃狗粮");
- }
- public void speak(){
- System.out.println("汪");
- }
- }
对于上述代码,我们可以清晰地看到,他们虽然是两个不同的事物,但是他们的属性相同,行为也有相同的地方,那我们定义两个类是不是就造成了代码的冗长,增加了出错的可能?
为了解决这一问题Java 的研发团队引入了继承这一概念,专门用来进行共性抽取,实现代码复用。
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性 的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
为了表示继承关系,Java中用extends关键字来表示类与类之间的继承。比如在上述栗子中,由于猫和狗都是动物,所以我可以写个动物类 ,让猫和狗都对其进行继承:
- class Animal{
- String name;
- String sex;
- int age;
- public void sleep(){}
- }
- class Cat extends Animal{
- public void eat(){
- System.out.println("吃猫粮");
- }
- public void speak(){
- System.out.println("喵");
- }
- }
-
- class Dog extends Animal{
- public void eat(){
- System.out.println("吃狗粮");
- }
- public void speak(){
- System.out.println("汪");
- }
- }
对程序进行debug一下:
发现实例化出来的猫和狗对象中仍有相关的属性。
其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的 子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态。
对于子类从父类中继承而来的变量有三种情况。
1.子类中有的变量
2.子类中没有,父类中有的变量
3.子类中的与父类中的同名的变量
这三种情况下子类中的变量应该如何进行访问,我们用一段代码来进行解释:
- public class Javabit_Code{
- public static void main(String[] args) {
- Derived der=new Derived();
- System.out.println(der.a);
- System.out.println(der.b);
- System.out.println(der.c);
- System.out.println(der.d);
- System.out.println("============");
- }
- }
- class Base{
- int a=10;
- int b=10;
- int d=10;
- }
- class Derived extends Base{
- int a=20;
- int c=20;
- double d=20;
- }
运行结果如图所示:
通过a和c输出的值可以看出不管父类中有没有,只要子类中有那就优先访问子类中的变量。
由b可知,当子类中没有时会访问父类中的变量。
由d可知,如果父类和子类中有同名的变量,当类型不同时也是优先访问子类的变量。
我们一定会有一个需求,那就是虽然子类中有这个变量名的变量,但我仍需要父类中的这个变量的数据,但由于我的子类中有这个变量了,我无法对父类中的那个变量进行直接访问,为了解决这一问题,super关键字诞生了。
- class Base{
- int a=10;
- int b=10;
- int d=10;
- }
- class Derived extends Base{
- int a=super.a;
- int c=20;
- double d=20;
- }
输出结果:
注意:super关键字只是在代码层次上提高了代码的阅读性。在子类中代表继承过来的父类型的特征,不包含在子类对象中。通过super.data就能知道访问的是父类的data。
解释一下:子类在实例化的时候并不会实例化父类,这里的父类型的特征只是因为子类先执行了父类的构造方法(以自身意愿进行的初始化形式)进行了初始化,然后调用子类的构造方法在进行一次初始化。
super不是引用类型,super中存储的不是内存地址,super指向的不是父类对象,super代表的是当前子类对象中的父类型特征。
this是引用型变量,保存对象的内存地址(和super区别),super和this都需要依托对象的存在,才可以使用(所以static修饰的成员变量和成员方法都无法使用this和super)
在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执 行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
- class Base{
- int a=10;
- int b=10;
- int d=10;
- Base(){}
- Base(int a,int b){
- this.a=a;
- this.b=b;
- }
- }
- class Derived extends Base{
- int a=20;
- int b=20;
- int c=20;
- double d=20;
- Derived(){}//没有写super是因为父类中有无参的构造方法,实际上有super()只是省略了
- Derived(int a,int b,int c){
- super(a,b);
- this.c=c;
- }
- }
现在我们已知的类在加载和实例化对象的过程中,有静态代码块,实例代码块,构造方法和成员变量等,那么他们在这一过程中的执行顺序是什么?或者说一个类从被加载到实例化一个对象他都进行了那些步骤?
- public class Javabit_Code{
- public static void main(String[] args) {
- Derived der1=new Derived();
- System.out.println("==================");
- Derived der2=new Derived();
- }
- }
- class Number{
- public Number(){
- System.out.println("Number的构造方法");
- }
- }
- class Base{
- Number num=new Number();
- public Base(){
- System.out.println("父类的构造方法");
- }
- //实例代码块
- {
- System.out.println("父类的实例代码块");
- }
- //静态代码块
- static {
- System.out.println("父类的静态代码块");
- }
- }
- class Derived extends Base{
- public Derived(){
- System.out.println("子类的构造方法");
- }
- //实例代码块
- {
- System.out.println("子类的实例代码块");
- }
- //静态代码块
- static {
- System.out.println("子类的静态代码块");
- }
- }
运行结果如图所示:
由此我们可以得出结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行。
2、父类实例代码块和父类构造方法紧接着执行。
3、子类的实例代码块和子类构造方法紧接着再执行。
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行。
用父类的构造方法(super)初始化父类的成员变量,然后子类在 初始化自己独有的成员变量。
一个java文件被javac编译成.class字节码文件——》需要new一个对象的时候先检测类是否已经加载,没有加载则加载——》类加载并被放入jvm的同时会初始化静态变量,执行静态代码块——》为实例化的对象分配内存空间——》处理并发安全问题——》初始化所分配的空间即为成员变量默认初始化——》设置对象头信息——》先调用实例代码块在调用构造方法给对象中的各个成员赋值。
- class X{
- Y y=new Y();
- public X(){
- System.out.print("X");
- }
- }
- class Y{
- public Y(){
- System.out.print("Y");
- }
- }
- public class Z extends X{
- Y y=new Y();
- public Z(){
- System.out.print("Z");
- }
- public static void main(String[] args) {
- new Z();
- }
- }
这段代码的运行结果是YXYZ。
执行顺序:new一个Z对象——》Z继承X——》先将X的成员构造完整——》调用了Y的构造方法输出了一个Y——》X的构造方法中也输出了一个X——》回到Z,将Z中的成员构造完整——》调用Y在输出一个Y(此时的Y对象为子类的Y对象,与从父类继承的Y对象同名但不相同)——》Z中的构造方法输出一个Z。
以上就是我关于继承的讲解,如果对你有帮助还请三连,如果疏漏,欢迎大佬指正!