构造函数、继承、多态

一:引入

Demo演示

先看一段代码

爷爷类

public class GrandFather {
    {
        System.out.println("我是爷爷类");
    }
}

父类

public class Parent {
    {
        System.out.println("hello,parent");
    }
}

子类

public class Child extends Parent {
    {
        System.out.println("hello,我是child");
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}

控制台信息

我是爷爷类
hello,parent
hello,我是child

需要进行说明的是

{
   
}

构造代码块会在创建对象之前进行执行。通过这里可以看到new一个关键字,创建出来了三个对象,其中还有一个隐藏的对象Object类的对象,因为GrandFather默认是继承了Object类的。

所以一个new Child(),创建出来了四个对象。但是这四个对象之间是有一定关系的。

2020年9月14日13:20:21

在这里纠正一点,因为还是觉得super不是父类的对象,而是一种“不完整”的“父类对象”,表示的是仅仅代表的是父类中的属性调用和方法调用。

因为在进行总结的时候,发现,抽象类中也可以有构造代码块,但是抽象类明确规定了抽象类是不能有对象的,尽管有构造函数。那么再进一步去进行考虑接口,接口中没有构造方法,有的只是常量和方法
按照上面的思路来进行思考?也会有对象吗?答案是否定的。

所以上面的结论就出现了“”问题“”,既然没有创建对象,那么为什么构造代码为执行?
网上到处都在说构造代码块是在类对象初始化的时候使用,那么对于抽象类,一边说不能创建对象,一边又说构造代码块在类实例化的时候调用?是不是造成了很大的冲突?

但是子类在进行实现的时候,并没有将常量属性拿过来进行操作

所以无论是对于继承还是实现,super代表都是父类的一种特殊的“引用”。

在这里插入图片描述

总结一:

所有的类通过继承体系都拥有Object类的方法,Object类的方法可以参考Object方法的博文。

二: new关键字的作用

new关键字在使用的时候会调用构造器来创建对象,表示的是在内存中开辟一块对应的数据类型的空间。

这里的空间指的是占用内存单元的大小,即在内存中占用了多少个字节。

按照上面的Demo中的实例,new的时候,先创建父类的对象,然后再去创建子类的对象。

下图展示new出来的对象在内存中的显示:

在这里插入图片描述

有了对象之后,那么就说明了可以使用对象中的属性和方法。

java中指定了属性和方法所在的位置,java将操作系统分配给java虚拟机的内存分为了五大区域,但是最为常用的是栈内存、堆内存、方法区。

属性是在堆内存中,方法是在方法区。

在这里插入图片描述

对象属性都是原始的值,也就是学习java基本数据类型的数据的时候,那些出现的默认的值

这里不再进行演示,默认都懂。这是构造函数做的事情。

三:构造函数

构造函数的作用是对对象的属性进行初始化,也就是new关键字调用构造器创建对象的时候,使用构造函数进行了初始化,将所有的属性初始化每种数据类型的默认值。

public class Student {
    int age;
    String name;
    public Student(){ }
}

对应的堆内存空间是:

在这里插入图片描述

总结一:

初始化完成之后的构造函数就和普通的函数没有区别

构造函数第一行

public Student(){
    super();
}

所有的构造函数,没有没有出现this(Xxx…),那么默认的有第一行的super();

this(Xxx....)的作用是调用本类中其他的构造函数进行初始化

但是super()一定会出现在类中的某一个构造函数中

总结二:

1、在一个类中使用的this,都是当前类的对象。 super代表的是父类对象的引用

String源码中

public String() {
    this.value = "".value;
}
-----------------------------------
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

value是对象的私有属性,"“对象和"original"对象可以调用private修饰的,但是this和”"都是String类的对象

都可以在本类中调用私有属性。

第一次看到这里的时候觉得不可思议,因为一直没有见到过。这里总结一下

2、this出现的位置,可以在本类中或者父类中

public class Child(){
    public Child(){
    	super();    
    }
}
-----------------------------
public class Parent{
    private int age;
    public Parent(){
        this.age = 20;
    } 
}    

在Parent构造函数中的this就是子类的对象,只不过在大多数的情况中this都会被省略掉,在参数名字和类中属性相同的时候,会使用this来加以区分。

super()之后

此时构造函数才会执行真正的属性初始化,这里的初始化指的是对象的属性初始化。

具体的可以参见上面的构造函数的作用

说明:我理解的是,如果super()是在第一行,对对象属性的初始化就在第二行,而且都是必须执行的。

只有第一行和第二行的代码执行完毕,才会执行下面的代码。如果有,执行;如果没有,就不执行,构造函数出栈

这里的知识点会运用在下面的面试题当中去。

额外说明

在第一行中,可能出现的不只是super(),还可以有下面的类型

super(XXX...xxx);表示的是访问父类的有参或者是无参的构造方法

this(Xxx...xxx)表示的是访问本类的其他的有参或者是无参的构造方法

但是二者不能同时出现在构造函数的第一行,或者是,二者只能出现一个。一个出现,另外一个就无法出现。

二:继承

java中非常重要的知识点

对于继承体系,java提供了特殊的机制:类似于下图的流程进行执行

在这里插入图片描述

最终的结果:找到了,执行;没有找到,那么报错。

画一个图来进行解释

在这里插入图片描述

说明,我没有把每个对象的方法名字和个数写出来(Object类对象就不只有这样的方法名字和个数),只是为了简写。

每个对象都有用自己的方法和属性,并且都在类中显示出来了。

在继承体系中的属性和方法的覆盖,在我看来就是一种“误导”,误导了我们学习者的理解。

我画出来了这种图,非常清晰的理解的方式。

在这里再看一下调用的体系

在这里插入图片描述

上图中的应用方式在源码中随处可见!距离我现在学习最近的是servlet的继承体系,我在servlet中也进行了说明。

下面的模板模式也是从这里得到的启示。模仿着servlet来写一个

2.0 模板模式

这里引用模板模式是最好的练习方式,来个简单的方式。

父类:

public abstract class Animal {
    
    private void init() {
        System.out.println("我要出生了");
    }

    public abstract void service();

    private void destroy() {
        System.out.println("我要死了");
    }

    // 模拟一套生命周期方式
    public void life() {
        init();
        service();
        destroy();
    }
}

Dog子类:

public class Dog extends Animal {
    @Override
    public void service() {
        System.out.println("hello,我每天都在听我主人的使唤");
    }
}

Cat子类:

public class Cat extends Animal {
    @Override
    public void service() {
        System.out.println("hello,在我有限的生命里我想做出无限可能的事情");
    }
}

测试类:

public class DemoTest {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();
        dog.life();
        System.out.println("-----------------------");
        cat.life();
    }
}

控制台信息

我要出生了
hello,我每天都在听我主人的使唤
我要死了
-----------------------
我要出生了
hello,在我有限的生命里我想做出无限可能的事情
我要死了

再一次分析调用的执行过程,使用dog来进行举例说明。

dog.life()的时候,dog对象会先找自己的类中有没有life方法,没有找到,那么去调用父类的life方法,执行

这样的一个简单的调用流程就是这样子,比较的简单。

2.1、函数的重载

我们重载的目的是为了更加合理化的根据参数来传参的时候进行调用。

所以重点关注的是方法名称和参数。而不是所谓的返回值类型和访问修饰符,我在这里更加注重于实际开发练习

2.2、函数重载要求:

函数的方法名称必须相同的情况下:

1、参数数据类型的个数不同;

2、参数数据类型的类型不同;

3、参数数据类型的顺序不同;

说明:不要将重点放在返回值类型和访问修饰符以及形参的名字同名的情况

其中,第一种方式的使用方式是最多的,可参见于许多源码中,还有我们平常使用的各种方法。

需要进行说明的是:

可变参数和重载的区别:
可变参数是相同类型的;重载可以是不同的数据类型的。

三:多态

3.1、函数的重写

要求:在有继承或者是多态的情况下:

1、子类和父类存在方法重写。如果没有重写,那么调用将毫无意义。

额外说明:如果存在着方法重载,那么实现的是继承的体系了。

2、父类引用指向子类对象

3、父类引用调用重写方法

4、子类不能比父类抛出更多的异常,存在着必须使用catch的情况。【会使用到,但是相对于上面三种来说,这种算是少的】

通过总结发现,这里并没有成员属性的什么故事!说明了什么?多态就是为了在继承体系中使用方法的。

当然这里也会进行一下说明

父类:

public class Paraent {
     String name= "meng";
    public Paraent(){ }
    public void sayHello() {
        System.out.println("hello,paraent");
    }
}

子类:

public class Child extends Paraent {
    String name="guang" ;
    public Child(){ }
    public void sayHello(){
        System.out.println("hello,child");
    }
}

子父类中都是name属性和sayHello方法

Parent p = new Child();

首先谈谈我对这行语句的理解。

这行语句,我认为是在子类对象中使用父类引用,也就是说,我看起来使用的是子类对象,但是实际上使用的是父类对象的引用。

3.2、父类引用调用子父类都有的属性

分三种情况:

第一种:父类如果没有age属性,而子类有

在这里插入图片描述

第二种:父类如果有age属性,而子类没有

在这里插入图片描述

第三种:父类和子类都有age属性

在这里插入图片描述

总结三:

父类引用调用属性都是调用自己特有的属性

3.3、父类引用调用子父类重写的方法

分三种情况:

第一种:父类有sayHello方法,而子类没有

在这里插入图片描述

第二种:父类没有sayHello方法,而子类有

在这里插入图片描述

第三种:父类和子类都有sayHello方法,满足方法重写的条件。【重点】

在这里插入图片描述

总结:父类引用调用了子类特有的方法(不满足方法重写),报错;

特殊说明,此时并不是说,我在上面写的结论不正确了,而是说java在这里做了手脚

java将子父类或者是子类和接口中的方法进行了一种‘特殊“的操作

在这里插入图片描述

但是对于不满足于方法重写的方法来说,看以下情况

在这里插入图片描述

父类引用对于属性而言,都有独立的空间来存储属性

在这里插入图片描述

总结:

Parent p = new Child();

对于这个语句来说,执行的就是自身的属性和函数。

这也就是之前的口诀。编译看左边,运行看右边。

四:面试题

父类:

public class Paraent {
    String name= "meng";
    public Paraent(){
        sayHello();
    }
    public void sayHello() {
        System.out.println("hello,paraent,你的name是:"+name);
    }
}

子类:

public class Child extends Paraent {
    String name = "guang";
    public Child(){
        sayHello();
    }
    public void sayHello(){
        System.out.println("hello,child,你的name是:"+name);
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        new Child();
    }
}

控制台信息:

hello,child,你的name是:null
hello,child,你的name是:guang

分析

第一步:new开辟内存

new关键字执行,在堆内存中开辟Child空间,this产生了,找父类,Object类,那么super产生了。super是一定会产生的,除了Object类对象。那么第一步初始化执行

在堆内存中

 String name = null;

第二步:构造函数执行

执行super(),因为父类没有new对象,那么父类的属性name也就不会进入到堆内存中去,所以父类的属性也就无法进行调用,同样的道理,父类的方法也就无法进行调用

public class Paraent {
     String name= "meng";
    public Paraent(){
        super.name = "lg";
        sayHello();
        super.sayHello();
    }
    public void sayHello() {
        System.out.println("hello,paraent,你的name是:"+name);
    }
}

此时,

super.name = "lg";
super.sayHello();

在idea中报红,这也就意味着,在堆内存中没有对象,那么意味着非静态方法不会被加载到方法区,属性也不会在对象中产生。

接着最开始的面试题进行分析,在父类的构造函数中调用了sayHello方法,sayHello方法为非静态的,那么说明了只能是有对象的方法,那么这里的对象是谁?只能是this,这里的this是省略的。

所以调用子类对象的,此时子类对象的属性还是处于默认状态的,那么对其进行输出。

第三步:

父类方法执行完毕,开始执行super()下面的内容,也就是第二步初始化,默认的没有写,自己想的。

第四步:

执行第二步以后的代码。也就是上面代码中的第一行代码

    public Child(){
        sayHello();
    }

其实这里隐藏了几行数据:

public Child(){
    super();
    this.Xxx = yyy;
    // 下面才是真正的方法
    sayHello();
}

调用的是本类中的方法。

五:父类引用指向的对象

父类引用指向的对象是什么时候产生的?

Parent p = new Child();

通过这个语句在进行调用的时候,不难发现,此时的父类对象是存在的。

子类对象在new的时候就已经创建了父类的对象,然后父类对象的引用进行了指向。只有这种方法才能够满足条件。

使用图来进行展示:

在这里插入图片描述

所以说new出来了一个对象,在堆内存中的对象远远不止是只有一个对象,而是多个。

六、构造器和构造函数

构造器是用来创建对象的;

构造函数是用来初始化对象属性的,类似于一个普通函数

虽然在表现上没有区别,但是在作用上有区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值