鄙人为兴趣爱好,0基础入门学习Java,有些心得想法,记录于此,与君分享。

然毕竟新手,学识尚浅,错误之处,希望多多指正批评,也是对我最大的帮助!

前言:本篇文章,主要讨论在子类继承父类之后,一些继承在内存中构建的过程,以及this和super的特点和异同。

文章内所有内容均为个人猜测和想法,不代表任何学科结论。

一、我是孙子!

  既然有孙子,那肯定是指三代传承,所以,我准备了三个类,A是爷爷,B是爸爸,C是孙子(也就是我,一下均以孙子代替)。代码如下:

类A(爷爷):

 public class A {
//属性部分
public int i_A;
public String str_A = "我是类A里str属性的初始值";
//一般方法部分
public void function (){
System.out.println("我是类A里的“function”方法,我不接受参数");
}
public void function_A(){
System.out.println("我是类A里的“function_A”方法,我不接收参数");
}
public void function_A(int n){
System.out.println("我是类A里的“function_A”方法,我接收一个值为"+n+"的int类型参数");
}
//构造方法部分
public A(){
System.out.println("我是类A的无参数构造方法");
}
public A(int n){
System.out.println("我是类A的带参数构造方法,我接收一个值为"+n+"的int类型参数");
}
//代码块部分
{
System.out.println("我是类A的代码块,我受过严格的训练");
}
}

Class A

类B(爸爸):

 public class B extends A {
//属性部分
public int i_B;
public String str_B = "我是类B里str属性的初始值";
//一般方法部分
public void function (){//父类A里一般方法的重写
System.out.println("我是类B里的“function”方法,是对父类A方法的重写,我不接受参数");
}
public void function_B(){
System.out.println("我是类B里的“function_B”方法,我不接收参数");
}
public void function_B(int n){
System.out.println("我是类B里的“function_B”方法,我接收一个值为"+n+"的int类型参数");
}
//构造方法部分
public B(){
System.out.println("我是类B的无参数构造方法");
}
public B(int n){
System.out.println("我是类B的带参数构造方法,我接收一个值为"+n+"的int类型参数");
}
//代码块部分
{
System.out.println("我是类B的代码块,无论多好笑,我都不会笑");
}
}

Class B

类C(孙子):

public class C extends B{
//属性部分
public int i_C;
public String str_C = "我是类C里str属性的初始值";
//一般方法部分
public void function (){//父类A里一般方法的重写
System.out.println("我是类C里的“function”方法,是对父类B方法的重写,我不接受参数");
}
public void function_C(){
System.out.println("我是类C里的“function_C”方法,我不接收参数");
}
public void function_C(int n){
System.out.println("我是类C里的“function_C”方法,我接收一个值为"+n+"的int类型参数");
}
//构造方法部分
public C(){
System.out.println("我是类C的无参数构造方法");
}
public C(int n){
System.out.println("我是类C的带参数构造方法,我接收一个值为"+n+"的int类型参数");
}
//代码块部分
{
System.out.println("我是类C的代码块,除非忍不住");
}
}

Class C

  准备好了三个类,那我就要开始生成一个孙子。首先,我们不带参数new一个C类对象c_new,在main中的代码如下:

 public class Relation {
public static void main(String[] args){
C new_c = new C();
}
}

main方法

  执行结果如下图:

分析一:

  通过结果来看

  第一、给我的直观感受是,java在new一个孙子对象的过程中,最先是new了一个爷爷类,再在爷爷类的后面new了一个爸爸类,最后在爸爸类的后面,new了一个孙子类。又由于object是所有类的父类,object也是爷爷类的父类,所以爷爷类其实是new在object类之后。

  第二、我认为,所有的“new”操作,一定都是在内存中开辟地址连续的空间(如果new出来的空间不连续,我估计整个java体系就会坍塌),而能100%保证正确申请到所需空间的步骤应该都是:先确定大小,再开辟空间,这个开辟的过程我觉得应该是JVM做的,而且开辟的空间必须等于或者大于所需要的空间。(我要是设计者,我会设计会大于所需的空间,多余的空间可以存储一些代码信息,用于其他的用途,比如底层的一些保护或者回收机制)

  所以,我产生了一个想法:

  这个开辟空间的过程并非动态的(先开辟A大小空间,再接着内存地址开辟B大小空间,再……),而是在编译的过程中,JVM会把A extends Object(隐式) 、B extends A、C extends B这个关系额外记录成一条信息,这条信息告诉电脑,如果我需要生成一个C类对象,你需要给我对应大小空间的连续内存块。于是,在得到指令需要生成一个C类对象后,内存会被开辟出一个能装得下从object到C类所有大小的地址连续的内存空间。

  但是又会存在一个问题,如果我把main中“ C new_c = new C(); ”这条语句注释掉,再编译执行,能通过,既然main里面什么都没做也可以执行通过,那此时ABC和object这四个类到底在不在内存中呢?还是说只有new出现了,才会产生我上述的编译过程?

  为了证明这一点,我在main()里设计了一个while(true)的方法,可以无限选择生成三种类中的任意一种类对象,我发现,在已经编译后的程序执行过程中,我可以随意选择生成A、B或者C类的对象,这说明,类A,B,C甚至object肯定还是在内存中存在的,但是存在的空间是否连续,我无法确定,而且没有一个具体的媒介可以用到他们,而且我猜想,很大可能性,这个内存状态下,各个类所占用的空间也仅仅是类里面代码描述内容所需占用的内存空间,并不是像生成的具体对象那样的内存空间。

  这个确定存在的推论让我又得到一条信息,new的过程肯定都是复制的某块内存的数据,而这块内存正好是所需要生成对象的类存在的内存块,不然怎么可能在不操作内存数据的情况下,把可能不连续的内存块,变成一定连续的内存块呢?(因为我觉得如果通过操作内存数据来使内存连续实在太费力了,不符合java的特性)。

  综上所述,以我的代码为例,我觉得整个 “C new_c = new C();” 的过程应该是下面这个步骤:

  1)内存中已经有几块区域存放了类Object、类A、类B和类C的主体。假如这四个类所占空间分别是50字节,100字节,100字节,100字节。合计:350字节。

  2)在内存的另外一块足够大的地方,开辟出一个350字节的连续地址空间,从object类到C类,依次将其内存的内容赋值到这块新的内存中,这块新内存是连续的,并产生一个代表这块内存的地址值。

  3)将这个地址值,存到另外一块名为c_new的内存里。

  至此,我觉得整个new的过程就完成了,以后对于c_new的操作,都是在c_new的值指向的新内存里面进行的反复读写与计算。

  

分析二:

  通过执行顺序来看:

  众所周知,构造一个类的具体对象是通过类里面的构造方法实现的,那么我们按照这个原则,开始从C();分析代码的执行顺序。

  进入C();第一行,直接就是输出字符串 "我是类C的无参数构造方法" ,这和结果完全不一样。从教程中,我了解到,其实构造函数的第一行,再未显式调用的情况下,永远是隐式调用了一个方法,super(),即调用当前类的父类的构造方法。所以我写的构造函数,实际上是这样的:

public C(){
super();
System.out.println("我是类C的无参数构造方法");
}

C();

public C(int n){
super();
System.out.println("我是类C的带参数构造方法,我接收一个值为"+n+"的int类型参数");
}

C(int n);

  依次类推,B和A类的构造方法应该均是如此。所以,整个执行过程应该是C();→B();→A();→object();,这是几个方法的嵌套使用(object有没有构造函数我不确定,目前只讨论ABC类,姑且这样写吧),在object();执行完毕之后,要执行的理应是A();里的下一条语句,System.out.plintln();,但是,结果显示的却是先执行了代码块的部分,而且,B、C类也是如此,我觉得这应该是java设计的一种类的规则,并不是某种机制产生的客观结果(也可能是,只是我还不懂),而且代码块的调用是在super()和输出之间发生,只有这种解释,才能将输出结果与构造过程一一对应。可以new一个带参数的C类对象佐证。

public class Relation {
public static void main(String[] args){
C new_c = new C(5);//带参数
}
}

main()

结果如下:

  完美!

二、我到底是谁!

  是的,我又变成了this,我是一个指代关键字,在类的代码里面,指代当前类的具体对象,用来调用当前类的各种属性和方法。比如,在类C里面,我是代表类C的对象,在类B里面,我代表B的对象,在A类里,我代表A的对象。。。但是,真的如此吗?我到底是谁?

  为了查明真想,首先,我们在C类的function_C方法里加入一行代码,

public void function_C(){
this.function();
System.out.println("我是类C里的“function_C”方法,我不接收参数");
}

function_C()

  通过main里生成的new_c调用function_C方法,执行后结果如下

  function_C方法正确调用到了this所在C类的function方法。

  但是,如果在B类的function_B方法里加入调用方法,那this到底调用的哪一个function方法?让我来测试一下。

 public void function_B(){
this.function();
System.out.println("我是类B里的“function_B”方法,我不接收参数");
}

function_b()

  结果如下

  没错,function_B里的this.function()调用的却是C类的function方法。如果在A类里面测试,结果一样。

  看上去,this并不是严格指代当前类的对象,起码在父类里通过this调用重写的方法时不符合这个说法。那么如果测试的重写的方法,而是重写的属性(这个说法可能并不专业和准确)呢?在三个类里面设置一个同名同类型的变量,经过同上步骤的测试,this指代的又变成了当前类的对象。

  考虑到function方法实际上是子类对父类的重写,情况可能有些特殊,所以关于this的指代性,可以做一个一般归纳:

  在继承中,this确实在一般情况下,指代的是一个当前类的对象,但是如果在父类或者父父类(父类的父类)中通过this调用重写的方法(同名同参数),那么实际调用的就是new出来的具体对象的重写方法,不会调用到父类,或者父父类。(这个结论的佐证就是,在父类中输入this.后,IDE不会提示到子类的各种属性和方法,除了重写的方法)

  接下来,我们讨论一下this();,通过this调用构造方法。

  首先,我们在C类的C(int n)方法里,第一行加入this(),然后带参数运行,结果如下:

  通过追踪代码的执行顺序,我们不难得出一个结论,this()把原来的隐藏的super()真正的屏蔽了起来,而如果显式调用super(),编译不会通过,这说明,this()和super()不能同时存在。其实这也很好理解,如果同时存在,那这个类的生成后就会非常混乱。所以这也应该是Java设定的特性。

三、我就是你爸爸!

  super就是爸爸,他比this的理解简单多了,在任何情况下,super都指代当前类的父类的对象,没有其他特例。就算是在父类里写super.function(),调用的也是父父类的function()方法,测试如下:

  在B类的function_B方法的程序块第一行写super.function();,然后main中用生成的C类对象调用到function_B查看结果。

 public void function_B(){
super.function();
System.out.println("我是类B里的“function_B”方法,我不接收参数");
}

function_B()

  结果如下:

  从结果不难看出,即使实际调用的是C类对象,但是super是写在B类的function_B方法里的,所以会找到B类的父类里的对应的方法或者属性。

  而super()就更不用讨论什么了,因为在任何子类的构造函数里面的第一行默认都是super(),不管显式或者隐式,他都必须在那里。

  this()和super()的共同点就是:

    ①必须在构造函数里存在。在其他位置没有实际意义,编译也不会通过。

    ②都必须在构造函数的第一行。作为用来生成对象的方法,肯定是先有了对象你才能使用对象。

    ③由于第二条的存在,那么this()和super()肯定不能同时存在。如果构造函数内是this(),那编译器应该会默认注释掉隐式的super()。

四、结尾

  到这里,已经写完了我所理解的继承时内存构建、this和super特性的大部分内容,其实这里面应该有很多不正确的地方或者没有考虑到的地方,但是作为心得体会,留下记录,可便于日后重新审视,发现自己的不足,从而提升自己,更欢迎欢迎大家多多批评指正,让我更快速的进步!

Java学习之旅(一):探索extends的更多相关文章

  1. Java学习之旅开篇:运行机制及环境搭建

    在写这篇博客之前,我想对自己进行简单概括:我从事软件开发工作已经三年多了,并且一直在从事.NET相关项目的开发.为什么突然间想学习Java呢?有以下几个原因: 1. 开发程序三年多来,已经对.NET相 ...

  2. Java学习之旅基础知识篇:面向对象之封装、继承及多态

    Java是一种面向对象设计的高级语言,支持继承.封装和多态三大基本特征,首先我们从面向对象两大概念:类和对象(也称为实例)谈起.来看看最基本的类定义语法: /*命名规则: *类名(首字母大写,多个单词 ...

  3. 第一篇,java学习之旅

    在java的这座殿堂中,我才刚刚推开了大门,就像是在岔路口找到了一条,走向前进java大门的路. 下面是一些java算法的问题 第一题: package project.model; import j ...

  4. java学习之旅

    jar文件其实就是一个压缩包,里面包含很多class文件(一个class文件是一个类的字节码).方便在网络上传输.可以规定版本号,更容易进行版本控制. var只能在方法内使用,不能用于定义成员变量. ...

  5. Java学习之旅基础知识篇:数组及引用类型内存分配

    在上一篇中,我们已经了解了数组,它是一种引用类型,本篇将详细介绍数组的内存分配等知识点.数组用来存储同一种数据类型的数据,一旦初始化完成,即所占的空间就已固定下来,即使某个元素被清空,但其所在空间仍然 ...

  6. Java学习之旅基础知识篇:数据类型及流程控制

    经过开篇对Java运行机制及相关环境搭建,本篇主要讨论Java程序开发的基础知识点,我简单的梳理一下.在讲解数据类型之前,我顺便提及一下Java注释:单行注释.多行注释以及文档注释,这里重点强调文档注 ...

  7. 我的java学习之旅--一些基础

    (因为我粗略学过C,C++,Python,了解过他们的一些语法,所以为了使得java的入门更为顺畅,便会忽略一些和C语法相类似的地方,着重点明一些java自己的特色之处.也减轻一下自己写文字的负担.) ...

  8. Java学习之旅(二):生病的狗1(逻辑推导)

    前言:本篇文章属于个人笔记,例化了一些代码,不知是否合理,请指教. 中午看到一位同学的面试题,觉得很烧脑,烧脑不能一个人烧,要大家一起烧. 村子中有50个人,每人有一条狗.在这50条狗中有病狗(这种病 ...

  9. 面向对象编程(OOP)的五大特征-java学习之旅(1)

    这是Alan Kay关于第一个成功的面向对象语言SmallTalk的总结: 1.所有的东西都是对象.可将对象想象成一种新型的变量:它保存着数据,但是可要求它对自身进行操作,理论上讲,可从要解决的问题身 ...

随机推荐

  1. 【python+selenium学习】Python常见错误之:IndentationError: unexpected indent

    初入python+selenium学习之路,总会遇到这样那样的问题.IndentationError: unexpected indent,这个坑我已经踏进数次了,索性记录下来.都知道Python对代 ...

  2. 解决PL/SQL使用无法导出dmp

    解决PL/SQL使用无法导出dmp 1.配置plsql Export Executable:D:\app\product\11.2.0\dbhome_1\BIN\exp.exe 2.配置环境变量ORA ...

  3. sql server 大数据, 统计分组查询,数据量比较大计算每秒钟执行数据执行次数

    -- 数据量比较大的情况,统计十分钟内每秒钟执行次数 ); -- 开始时间 ); -- 结束时间 declare @num int; -- 结束时间 set @begintime = '2019-08 ...

  4. 基于windows 10打造的kali工具集

    基于windows 10打造的kali工具集.iso,适合于习惯使用windows的安全从业者.if you like it,please touch star! 作为安全从业主,Kali都是必备工具 ...

  5. windows docker 安装 Kitematic

    在已经安装好docker for windows的基础上, 右键docker任务栏小图标, 选择Kitematic 然后放在docker的安装目录中C:\Program Files\Docker: 文 ...

  6. 简要分析一下java中线程的生命周期

    面试题:您了解线程吗?简单叙述一下线程的生命周期? 答:之前学过一些有关于线程方面的知识,并且在编写代码的过程中还是要经常考虑线程,所以,我对线程还是了解一点的. 首先,创建一个线程,线程进入初始状态 ...

  7. sql学习(一),sqlpuls

    原创作品,转载请注明来源https://www.cnblogs.com/sogeisetsu/ oracle的特殊语法 注意,上方的语法只适用于oracle,并不适用于mysql,比如,mysql需要 ...

  8. rtmpdump应用在window中

    rtmp.c 中RTMP_GetTime()函数要改成如下: #pragma comment(lib, "winmm.lib ")uint32_tRTMP_GetTime(){// ...

  9. PHP随机获取预设的值

    前面我们讲了php怎么获取随机数,<?php echo rand(1000,2000); ?> 一行代码就能搞定,如果要获取ASP,PHP,JAVASCRIPT,AJAX,CSS,JQUE ...

  10. 【转】linux 下清空或删除大文件的一些方法

    原文:https://linux.cn/article-8024-1.html 在 Linux 终端下处理文件时,有时我们想直接清空文件的内容但又不必使用任何 Linux 命令行编辑器 去打开这些文件 ...