java初始化实例化_Java 类初始化和实例化以及多态理解
前言
本篇主要讲解了类的初始化、实例化、静态代码块、构造器、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 类初始化和实例化以及多态理解相关推荐
- java类的初始化方法_JAVA类初始化和实例初始化
一.类初始化过程 1.一个类要创建实例需要先创建和加载 (1) main方法所在的类需要先加载和实例化 2.一个子类要初始化,需要先初始化父类 3.一个类初始化就是执行方法 (1) () 方法由静态类 ...
- java类初始化顺序_Java 类的初始化顺序
静态代码块:用staitc声明,jvm加载类时执行,仅执行一次 构造代码块:类中直接用{}定义,每一次创建对象时执行 执行顺序优先级:静态块,main(),构造块,构造方法 1. 构造函数 publi ...
- java 初始化参数_JAVA类的初始化顺序与initialize参数
JAVA类的初始化顺序依次是:(静态变量.静态初始化块)->(变量.初始化块)->构造函数, 相同级别的以定义顺序为准, 且静态变量和静态初始化块只初始化一次.通过下面的代码来验证. p ...
- java类编来那个初始化顺序_java类的初始化顺序
对于静态变量.静态初始化块.变量.初始化块.构造器,它们的初始化顺序依次是(静态变量.静态初始化块)>(变量.初始化块)>构造器.我们也可以通过下面的测试代码来验证这一点: public ...
- java类编来那个初始化顺序_Java类及对象的初始化顺序
1.初始化规则: 下面规则优先级从前往后依次降低 ①.加载一个类时先加载初始化基类后加载初始化扩展类: ②.类的初始化高于实例的初始化: 类的初始化依靠(静态代码块static{....})以及(静态 ...
- Java中变量、类初始化顺序
类中声明的静态变量在递归调用中的值不会发生变化,但是对于非静态变量递归过程中会发生变化,初始化为0 static声明的为类变量,而非static声明的为实例变量 (byte,short,char)-i ...
- java成员初始化顺序_Java成员初始化顺序
1. 初始化顺序 在类的内部,变量定义的先后顺序决定了初始化的顺序.即使变量散布于方法定义之间,他们仍会在任何方法(包括构造器)被调用之前初始化. 2. 静态成员初始化顺序 1⃣️初始化类的静态成员或 ...
- java 对象初始化过程_Java——对象初始化顺序使用详解
一. 代码块的概念 在探究对象初始化顺序之前,我们先通过代码来了解一下代码块的概念. class Test{ public static String str1; //静态字段 public Stri ...
- java对象的初始化顺序_Java对象初始化顺序
初始化顺序: 1.有无父类? 有: 将父类加载进内存.直到将所有的父类加载完毕.再从顶层父类按照代码的顺序执行静态代码,执行完最顶层的,在执行下一层的,依次类推,直到执行完所有的静态代码. (1)如果 ...
最新文章
- python3 paramiko实现ssh客户端
- SAP系统搜索分页的前后台实现
- Linux安装卸载Mysql数据库
- Linux/Windows下查看同一网段下的所有活动IP
- java数据结构——树的实现
- 迅歌KTV服务器各型号,十大ktv必点歌曲排行榜 ktv点唱率最高的十首歌榜单公布...
- Hans Berger脑电图之父的人生摘要
- HC-SR04超声波测距模块的原理介绍与代码实现
- 行逻辑链接的顺序表(压缩存储稀疏矩阵)详解
- Autosar Configuration(五) Security之Csm配置
- 【愚公系列】2022年06月 ASP.NET Core下CellReport报表工具基本介绍和使用
- MFC的使用——在共享DLL中使用MFC、在静态库中使用MFC
- Spark的坑--Spark新手必看--Python Spark必读,耗费了我近三周的时间
- IBM公司利用人工智能预测化学反应结果
- 3Dmax在哪里下载 |3Dmax入门学习教程有哪些!!想学习的你还在等什么?
- 网络 路由器基本协议配置
- 360与腾讯qq的战争
- python自动注册邮箱_python2+selenium+mail,自动登录126邮箱
- QEMU零知识学习2 —— QEMU源码下载
- The IIIustrated Word2vec
热门文章
- matlab模块 python,Matlab 和Python结合使用
- 卷积层和全连接层的区别_1*1的卷积核和全连接层有什么异同?
- 如何知道当前像素的顶点坐标_GT 大神 | 如何高效渲染流体效果(绝对干货)
- 丧心病狂的代码混淆操作以及怎么破
- DNS隧道工具使用 不过其网络传输速度限制较大
- Progressive Web App是一个利用现代浏览器的能力来达到类似APP的用户体验的技术——不就是chrome OS吗?...
- 查看安卓keystore别名
- HTML5 placeholder在低版本浏览器的解决方法
- 打开eclipse出现an error has occurred.see the loh file
- LightOJ - 1265 概率