前言

本篇主要讲解了类的初始化、实例化、静态代码块、构造器、getClass()、super、this 等相关的知识点,做一个总结。

demo

老规矩,看代码:

Father.java

public class Father {

private int i = test();

private static int j = method();

static {

System.out.println("1 父类静态代码块");

}

Father() {

System.out.println("2 父类无参构造器");

}

{

System.out.println("3 父类代码块");

}

public int test() {

System.out.println("4 父类 test 方法");

return 1;

}

public static int method() {

System.out.println("5 父类 method 方法");

return 1;

}

}

Son.java

public class Son extends Father {

private int i = test();

private static int j = method();

static {

System.out.println("6 子类静态代码块");

}

Son() {

System.out.println("7 子类无参构造函数");

}

{

System.out.println("8 子类代码块");

}

@Override

public int test() {

System.out.println("9 子类 test 方法");

return 1;

}

public static int method() {

System.out.println("10 子类 method 方法");

return 1;

}

}

MainClass.java

public class MainClass {

public static void main(String[] args) {

Son s1 = new Son();

System.out.println();

Son s2 = new Son();

}

}

大家先思考一下 main 方法执行之后的打印应该是什么样的,

打印结果:

5 父类 method 方法

1 父类静态代码块

10 子类 method 方法

6 子类静态代码块

9 子类 test 方法

3 父类代码块

2 父类无参构造器

9 子类 test 方法

8 子类代码块

7 子类无参构造函数

9 子类 test 方法

3 父类代码块

2 父类无参构造器

9 子类 test 方法

8 子类代码块

7 子类无参构造函数

如果你计算的打印结果和我的不一样,那么看看我下面的讲解吧,如果一样的,老铁,你基础还不错啊。

讲解

基础知识

在讲解这个题目之前,我先总结一下需要了解的前提知识点。任何 java 程序的入口都是 main 方法

main 方法所在的类需要先加载和初始化

类初始化先执行类的()方法,大家查看 class 文件的字节码时可以找到这个方法。() 方法由静态类变量显式赋值代码【注意变量还没创建】和静态代码块组成。【包含且只包含这些。】

类变量显式赋值代码和静态代码块从上到下顺序执行,谁在前谁先执行

()方法只执行一次。【因为 class 类只被加载一次】

如果有父类,先加载和初始化父类,即先执行father.()方法,再执行子类的clinit方法

用构造方法实例化对象时,会调用方法,且每 new 一次就会调用一次几个构造方法就有几个 init 方法,可能有参,可能无参

由非静态实例变量显式赋值代码、非静态代码块、对应构造器代码组成

构造器最后执行,另外 2 个谁在上面谁先执行

每 new 一次对象,调用对应的构造器,就是执行对应的()方法

init 方法首行是 super()或者 super(参数), 代表要先执行父类的 () 方法【所以说会先调用父类构造器】

开始分析

由我们上面总结的 main 方法所在类先加载和初始化,因为我们main 方法写在了 MainClass文件,没有其他变量或者方法,直接看 main 方法的代码。

类初始化

第一行代码

Son s1 = new Son();

我们知道=号右边先执行,所以去 new 一个 son 对象,这个步骤就叫实例化对象,并且会把对象进行初始化。

我们知道在实例化对象前,jvm 需要去方法区找有没有这个对象对应的 class文件。从我们的代码看,是没有的,所以他要去装载这个 class 文件,就是进行类初始化操作。时刻记住 class 文件按需加载,如果你整个程序运行阶段都用不到这个类,那么 jvm 从启动到结束都不会去加载这个 class文件。

在加载 class 的步骤时就是我们说的类初始化,而我们知道类初始化时会调用类的()方法。

但是在执行Son.()方法的时候,我们看到 Son 是继承自 Father 的,它有个父类,按照规则又会先去看看父类初始化没有。

从我们代码看,第一行前面没有 father 相关的代码,说明 father 类没有初始化,所以又先去调用Father.()方法。

按照()规则:该方法由静态类变量显式赋值代码、静态代码块组成。

这 2 个谁在前,谁先执行。

我们看 Father 的代码,踢出不需要的,我们的 方法会执行下面 2 句

private static int j = method();

static {

System.out.println("1 父类静态代码块");

}

第一行会去调用 method 方法,所以打印了5 父类 method 方法。

然后是静态代码块,打印1 父类静态代码块

下面没有代码了,Father.()结束,开时执行子类的Son.()方法

private static int j = method();// 子类的 method 方法static {

System.out.println("6 子类静态代码块");

}

同理我们知道打印了10 子类 method 方法,6 子类静态代码块

老铁们,目前还跟得上吧?

对象实例化

类初始化之后,就是对象实例化了,因为我们调用了构造函数去初始化,所以我们看看 Son 的构造函数

Son() {

System.out.println("7 子类无参构造函数");

}

一般老铁看到这里,哈!简单啊,就一个打印,over。

但是我既然在这里列出来了,肯定不简单啊。

其实这里被隐藏了一句代码:

Son() {

// 这句写或者不写,都一定会执行,子类构造器一定会调用父类构造器。 // super(); System.out.println("7 子类无参构造函数");

}

我们看看 Son.class 的字节码,里面先调用了Father. ()V,后面的 V 标识无返回值

()V

L0

LINENUMBER 39 L0

ALOAD 0

INVOKESPECIAL top/ybq87/Father. ()V

L1

...

那我们就去父类呗

Father() {

System.out.println("2 父类无参构造器");

}

保险起见,我们再去看看父类的字节码,果然 father 还有一个父类!Object. ()V,我们知道 Object 是所有类的父类

()V

L0

LINENUMBER 22 L0

ALOAD 0

INVOKESPECIAL java/lang/Object. ()V

L1

...

Object 的字节码,此时就再也没有父类了。

public ()V

L0

LINENUMBER 37 L0

RETURN

MAXSTACK = 0

MAXLOCALS = 1

...

好了,说了这么一大堆,总结:Son.执行前先调用Father.

我们知道方法每次构造会执行:非静态实例变量显式赋值代码,有点绕口,其实就是成员变量的赋值代码,哈哈,啥又是成员变量呢?实例变量=成员变量

非静态代码块,和第一条,谁先出现,谁先执行

对应构造器代码【最后执行】

看看 father 的代码

private int i = test();

{

System.out.println("3 父类代码块");

}

Father() {

System.out.println("2 父类无参构造器");

}

调用 test 方法,打印4 父类 test 方法,代码块打印3 父类代码块,构造函数打印2 父类无参构造器。

看到这里,有眼尖的老铁就懵了,怎么回事,我们之前看到编译器这里打印的是

9 子类 test 方法 // 我们按照预期以为应该是打印的:4 父类 test 方法

3 父类代码块

2 父类无参构造器

和我们分析的不一样啊???这里就涉及到另外一个知识点,我们后面讲解,先分析完代码。

父类的方法执行完毕,轮到子类了,流程是一样的,这里就不再列出。

9 子类 test 方法

8 子类代码块

7 子类无参构造函数

到此,第一行代码执行完毕。

Son s2 = new Son();

这个就留给大家继续巩固下,我们继续说关于 test()的问题。

方法重写

我们学习 java 的时候,知道 java 有三大特性【封装、继承、多态】,很多人对封装和继承比较容易理解,但是这个多态就会一脸懵了。什么叫多态?在理解多态之前,我先讲讲继承中的方法重写。

方法重写,什么情况下会有重写?我们知道子类继承父类,是可以重写父类方法的,我们从不能被重写的定义去理解能被重写的final 修饰的方法,不允许重写

静态方法不允许重写

private 等子类中不可见的方法不能被重写

其他的都可以被重写

大家可以自己验证一下,在 father 写一个 public 的方法,然后利用编辑器提供的提示功能,看能否找到提示代码。IDEA 找到 Second Basic Completion 快捷键。

不清楚的老铁再复习一下

重写的了解完了,我们再看看 this 关键字,学海无涯啊。为了讲好重写和多态,不得不了解一下 this 关键字。我们一般写代码的时候,编辑器会自动将一些 this 省略了,导致我们理解代码有一定的误差推荐 idea 的插件:save action,设置格式化的时候加上 this 关键字,勾上:Add this to field access 和 Add this to method access。当然基础熟悉之后,可以不需要了。

经过这么一改造,我们发现 father 和 son 中的部分代码发生了一些变化

private int i = this.test();

原来方法调用是通过 this 关键字来调用的,这个 this 看来就影响了我们的打印结果,还记得我们分析Father.时的预期打印嘛?

9 子类 test 方法 // 我们按照预期以为应该是打印的:4 父类 test 方法

3 父类代码块

2 父类无参构造器

这里的结果显示的居然是调用了子类的 test() 方法的打印,也就是说 Father 中的 this 居然是子类 Son?

老铁们还跟得上不

继承关系中的this关键字

非继承的类中的 this,就是这个实例本身,这个没有什么怀疑了。

但是继承关系下的 this 关键字,子类中的 this 还是子类本身,但是父类中如果用到了 this,就有一定的区别

【注意,我们分析的是:使用子类class进行实例化一个对象,而不是直接实例化父类时,在父类中的 this 关键字的作用】this(1,2); 访问的是本类的其他构造方法,无论子类是否有相同参数的构造方法,父类中这里都是访问自己的构造方法

this.paramName; 可以访问类中的成员变量,在父类中使用 this.param 访问的始终是父类的成员变量

this.func(params..); 访问类中的成员方法时【也就是调用了 test 方法时】子类重写了父类方法,那么这里就是调用的子类的方法。看到了吧!此时我们在父类用 this.test()调用的是子类重写过的 test()方法。

子类没有重写,那么这里调用的还是父类自己的方法。

this; 当前类【或者叫运行时类型】对象的引用, this 始终代表的是子类的对象这个怎么理解?因为 this 的用法除了调用方法,调用参数,还可以被当成参数传递

比如在父类的构造器 System.out.println(this); 那么这个 this 其实是指向的是子类。

或者 System.out.println(this.getClass()); this 也是子类。

虽然看到这里我们理解了为什么在父类的Father.时会调用子类的重写过的方法,因为 java 这么规定的,是代码规则。

但是我们又引出了新的概念!运行时类型???

编译时类型和运行时类型

我们说到哪里了?嗯,上面不是说怎么理解多态么,怎么又扯到这个了?老铁们坚持住啊,胜利就在眼前了。

java 的引用变量有 2 种类型啥是引用变量?本例中 main 方法中的Son s1 = new Son();其中 s1 就是引用变量。不了解的老铁补一下基础编译时类型:由声明该变量时使用的类型决定

运行时类型:实际赋给该变量的对象决定

光文字说明不容易理解,

Father son1 = new Son();

Son son2 = new Son();

第一行代码,我们知道 son 是 father 的子类,所以这么声明 son1 是没有问题的。

那么 Father 就是 son1 的编译时类型,而 Son 是 son1 的运行时类型。

如果编译时类型和运行时类型不一致,会出现所谓的多态,看到没有,老铁,这就是多态!!!我们自己去百度多态的定义,一大堆文字描述,都给人绕晕了,看这里,敲黑板!!!编译时类型和运行时类型不一致就是多态。

第二行,编译时类型和运行时类型都一样,他不是多态。

稍微总结下我们目前了解的,多态存在的三个必要条件:

多态存在的三个必要条件:继承:son 继承自 father

重写:son 重写了 father 的 test 方法

父类引用指向之类对象:父类中 this 的指向概念

到这里你再去网上 google 一下多态的定义,我想老铁们肯定思路清晰很多了吧。

进一步理解

引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。

说人话就是:son1 在编译阶段只能调用 Father 所具有的方法,但运行时则执行 Son 所具有的方法。

假设我们在Son 写了一个新的方法,

class Son{

...

public void hello(){

System.out.println("hello");

}

}

// 定义Father son1 = new Son();

// 我们可以使用 son1 直接调用 father 存在的方法,son1.test();

// 但是如果你这么写,编译就会报错,也就是前半句:son1 在编译阶段只能调用 Father 所具有的方法son1.hello();

// 程序跑起来之后,代码执行到了 son1.test()// 则执行 Son 所具有的方法[如果子类有重写的话]

因此,编写Java代码时,引用变量只能调用声明该变量所用类里包含的方法。与方法不同的是,对象的属性则不具备多态性。通过引用变量来访问其包含的实例属性时,系统总是试图访问它编译时类所定义的属性,而不是它运行时所定义的属性,属性不能被重写。

super.getClass() 和 this.getClass()

既然说到了 this,就不能不说下 super。

按照官方的定义,this()调用构造器方法必须放在构造器的第一行,但是 super 也必须放在第一行,所以 2 个关键字调用不允许出现在同一个方法中,且都不能出现多次。

super 关键字的作用是在于当子类覆盖了父类的某个成员变量或者方法,还想要访问到父类的这个变量或者方法时,用 super。

super在一个类中用来引用其父类的成员,它是在子类中访问父类成员的一个桥梁,并不是任何一个对象的引用。

而this则表示当前类对象的引用。在代码中Object o = super;是错误的,Object o = this;则是允许的。

那么说说 getClass() ,看到这个方法的名称我们知道是获取当前对象的 class 的。那么我们打印一下

Father son1 = new Son();

...

Father() {

System.out.println(this.getClass().getName());

System.out.println("2 父类无参构造器");

}

// 打印...

top.ybq87.Son

2 父类无参构造器

...

java 中 getClass() 方法返回的是该对象的运行时类,因为我们执行的是第一行代码,son1 的运行时类是 Son。那么我们就像知道真实的父类是啥呢?

使用:

System.out.println(getClass().getSuperclass().getName());

结束

咱们结束之前在看看看这个

public class TestClass {

public static void main(String[] args) {

A a1 = new A();

A a2 = new B();

B b = new B();

C c = new C();

D d = new D();

System.out.println(a1.show(b));

System.out.println(a1.show(c));

System.out.println(a1.show(d));

System.out.println(a2.show(b));

System.out.println(a2.show(c));

System.out.println(a2.show(d));

System.out.println(b.show(b));

System.out.println(b.show(c));

System.out.println(b.show(d));

}

}

class A {

public String show(D obj) {

return ("A and D");

}

public String show(A obj) {

return ("A and A");

}

}

class B extends A {

public String show(B obj) {

return ("B and B");

}

@Override

public String show(A obj) {

return ("B and A");

}

}

class C extends B {

}

class D extends B {

}

是不是很简单了呢

public static void main(String[] args) {

A a1 = new A();

A a2 = new B();

B b = new B();

C c = new C();

D d = new D();

// A and A,A 没有 show(B obj) 方法,但是 B 是继承 A,所以找到 show(A obj)。 System.out.println(a1.show(b));

// A and A,同理 C 继承 B,B 继承 A,可以找到 show(A obj) System.out.println(a1.show(c));

// A and D,直接找到 show(D obj) System.out.println(a1.show(d));

// B and A,a2 编译时类型是 A,但是 A 没有 show(B obj) 方法,但是 b 继承 a, // 可以转为 show(A obj),但是因为多态原因,这里调用的是运行时类 B 的 show(A obj)方法。 System.out.println(a2.show(b));

// B and A,同上 c 转为了 a,调用运行时类的 show 方法 System.out.println(a2.show(c));

// A and D,编译时类的方法可以直接调用,所以这里直接调用 show(D obj) System.out.println(a2.show(d));

// B and B,直接找到 show(B obj) System.out.println(b.show(b));

// B and B,c 继承自 b,找到 show(B obj) System.out.println(b.show(c));

// A and D,因为 B 继承自 A,但是 b 又没有重写 show(D obj),而且 A 中 show(D obj)是个 public 的方法 // 所以此方法被 B 继承过来,可以直接调用。 System.out.println(b.show(d));

}

java初始化实例化_Java 类初始化和实例化以及多态理解相关推荐

  1. java类的初始化方法_JAVA类初始化和实例初始化

    一.类初始化过程 1.一个类要创建实例需要先创建和加载 (1) main方法所在的类需要先加载和实例化 2.一个子类要初始化,需要先初始化父类 3.一个类初始化就是执行方法 (1) () 方法由静态类 ...

  2. java类初始化顺序_Java 类的初始化顺序

    静态代码块:用staitc声明,jvm加载类时执行,仅执行一次 构造代码块:类中直接用{}定义,每一次创建对象时执行 执行顺序优先级:静态块,main(),构造块,构造方法 1. 构造函数 publi ...

  3. java 初始化参数_JAVA类的初始化顺序与initialize参数

    JAVA类的初始化顺序依次是:(静态变量.静态初始化块)->(变量.初始化块)->构造函数, 相同级别的以定义顺序为准,  且静态变量和静态初始化块只初始化一次.通过下面的代码来验证. p ...

  4. java类编来那个初始化顺序_java类的初始化顺序

    对于静态变量.静态初始化块.变量.初始化块.构造器,它们的初始化顺序依次是(静态变量.静态初始化块)>(变量.初始化块)>构造器.我们也可以通过下面的测试代码来验证这一点: public ...

  5. java类编来那个初始化顺序_Java类及对象的初始化顺序

    1.初始化规则: 下面规则优先级从前往后依次降低 ①.加载一个类时先加载初始化基类后加载初始化扩展类: ②.类的初始化高于实例的初始化: 类的初始化依靠(静态代码块static{....})以及(静态 ...

  6. Java中变量、类初始化顺序

    类中声明的静态变量在递归调用中的值不会发生变化,但是对于非静态变量递归过程中会发生变化,初始化为0 static声明的为类变量,而非static声明的为实例变量 (byte,short,char)-i ...

  7. java成员初始化顺序_Java成员初始化顺序

    1. 初始化顺序 在类的内部,变量定义的先后顺序决定了初始化的顺序.即使变量散布于方法定义之间,他们仍会在任何方法(包括构造器)被调用之前初始化. 2. 静态成员初始化顺序 1⃣️初始化类的静态成员或 ...

  8. java 对象初始化过程_Java——对象初始化顺序使用详解

    一. 代码块的概念 在探究对象初始化顺序之前,我们先通过代码来了解一下代码块的概念. class Test{ public static String str1; //静态字段 public Stri ...

  9. java对象的初始化顺序_Java对象初始化顺序

    初始化顺序: 1.有无父类? 有: 将父类加载进内存.直到将所有的父类加载完毕.再从顶层父类按照代码的顺序执行静态代码,执行完最顶层的,在执行下一层的,依次类推,直到执行完所有的静态代码. (1)如果 ...

最新文章

  1. python3 paramiko实现ssh客户端
  2. SAP系统搜索分页的前后台实现
  3. Linux安装卸载Mysql数据库
  4. Linux/Windows下查看同一网段下的所有活动IP
  5. java数据结构——树的实现
  6. 迅歌KTV服务器各型号,十大ktv必点歌曲排行榜 ktv点唱率最高的十首歌榜单公布...
  7. Hans Berger脑电图之父的人生摘要
  8. HC-SR04超声波测距模块的原理介绍与代码实现
  9. 行逻辑链接的顺序表(压缩存储稀疏矩阵)详解
  10. Autosar Configuration(五) Security之Csm配置
  11. 【愚公系列】2022年06月 ASP.NET Core下CellReport报表工具基本介绍和使用
  12. MFC的使用——在共享DLL中使用MFC、在静态库中使用MFC
  13. Spark的坑--Spark新手必看--Python Spark必读,耗费了我近三周的时间
  14. IBM公司利用人工智能预测化学反应结果
  15. 3Dmax在哪里下载 |3Dmax入门学习教程有哪些!!想学习的你还在等什么?
  16. 网络 路由器基本协议配置
  17. 360与腾讯qq的战争
  18. python自动注册邮箱_python2+selenium+mail,自动登录126邮箱
  19. QEMU零知识学习2 —— QEMU源码下载
  20. The IIIustrated Word2vec

热门文章

  1. matlab模块 python,Matlab 和Python结合使用
  2. 卷积层和全连接层的区别_1*1的卷积核和全连接层有什么异同?
  3. 如何知道当前像素的顶点坐标_GT 大神 | 如何高效渲染流体效果(绝对干货)
  4. 丧心病狂的代码混淆操作以及怎么破
  5. DNS隧道工具使用 不过其网络传输速度限制较大
  6. Progressive Web App是一个利用现代浏览器的能力来达到类似APP的用户体验的技术——不就是chrome OS吗?...
  7. 查看安卓keystore别名
  8. HTML5 placeholder在低版本浏览器的解决方法
  9. 打开eclipse出现an error has occurred.see the loh file
  10. LightOJ - 1265 概率