当我们new一个GirlFriend时,我们都做了什么?

一个例子搞懂Java程序运行顺序

public class Girl {
Person person = new Person("Girl");
/*构造代码块忘记加了,不过构造代码块和成员变量一样,每次构造方法时都会执行一次
{
System.out.println("构造代码块");
}
*/
static{
System.out.println("Girl static");
} static Person staticPerson = new Person("GirlStaticPerson"); public Girl() {
System.out.println("Girl constructor");
} public static void main(String[] args) {
new MyGirlFriend();
}
} class Person{
static{
System.out.println("person static");
} static Person staticPerson = new Person("PersonStaticPerson"); public Person(String str) {
System.out.println("person "+str);
}
} class MyGirlFriend extends Girl {
Person person = new Person("MyGirlFriend"); static Person myStaticPerson = new Person("MyStaticPerson"); static{
System.out.println("MyGirlFriend static");
} public MyGirlFriend() {
System.out.println("MyGirlFriend constructor");
}
}

  这段代码同时包含了继承和静态,下面我来仔细分析这段代码的运行结果会是什么。

  首先,在执行java Girl之后,jvm会寻找Girl.class,找到之后加载,加载的时候会首先给成员变量开辟空间并隐式赋值(默认值),然后执行静态代码块和静态成员变量显式赋值(按顺序执行),因此首先执行static{ System.out.println("Girl static"); }

  然后静态成员变量,执行这一句:static Person staticPerson = new Person("GirlStaticPerson");,接着会寻找Person类并加载(因为赋值的时候使用到了Person类),因此开始执行Person的静态代码块和静态成员变量,即执行这一句:static{ System.out.println("person static"); },接着是静态成员变量:static Person staticPerson = new Person("PersonStaticPerson");此时已经加载了Person类,即开始执行public Person(String str) { System.out.println("person "+str); }方法

  接着回到Girl类,Person类已经加载成功,执行person的构造方法并打印person GirlStaticPerson

  此时Girl类的静态区域已经执行完,开始执行main方法,即程序主体:new MyGirlFriend();然后寻找MyGirlFriend类并加载,加载后执行静态成员变量:static Person myStaticPerson = new Person("MyStaticPerson");执行Person构造方法打印person MyStaticPerson,然后是静态代码块:static{ System.out.println("MyGirlFriend static"); }

  此时MyGirlFriend已经加载完毕,准备执行构造方法,然后发现MyGirlFriend 继承 Girl,需要先执行父类构造方法,执行父类构造方法前又需要初始化父类成员变量和执行父类构造代码块,即执行Girl类的Person person = new Person("Girl");该行调用Person的构造方法,打印person Girl,接着才会执行父类构造方法:public Girl() { System.out.println("Girl constructor"); }

  父类构造方法执行完之后回到MyGirlFriend类,准备执行子类构造方法,在执行构造方法前需要对其成员变量初始化,也就是执行:Person person = new Person("MyGirlFriend");,最后才是子类构造方法:public MyGirlFriend() { System.out.println("MyGirlFriend constructor"); }

  至此,回到main方法,该行代码执行完毕。运行结果是:

Girl static//Girl类静态代码块
person static//Person类静态代码块
person PersonStaticPerson//Person类中的静态Person成员变量
person GirlStaticPerson//Girl类中的静态Person成员变量
person MyStaticPerson//MyGirlFriend类中的静态成员变量
MyGirlFriend static//MyGirlFriend类中的静态代码块
person Girl//Girl类的成员变量
Girl constructor//Girl类的构造方法
person MyGirlFriend//MyGirlFriend类的成员变量
MyGirlFriend constructor//MyGirlFriend构造方法

但是,在当在IDEA里把断点打到这一行时:



偶尔会出现下面的奇怪运行结果:



个人猜测是idea的debug模式会提前加载类,从而使静态成员变量和静态代码块都提前运行了

总结: 没有女朋友就new一个呀o((>ω< ))o

先静后非,先父后子,先块后器


  更新:例子中缺少了构造代码块(也就是不带static的代码块),构造代码块和成员变量是同等位置,每次执行构造器前都会执行构造代码块,初始化成员变量。


再次更新:再来分析一个例子看看有没有掌握:

public class Demo2 {
public static int k = 0;
public static Demo2 t1 = new Demo2("t1");
public static Demo2 t2 = new Demo2("t2");
public static int i = print("i");
public static int j = print("j");
public static int n = 99;
//int a = print("a"); {
print("constructor code");
} static {
print("static code");
} public static int print(String s) {
System.out.println("i=" + i + " " + s + " k=" + k + " n=" + n + " j=" + j);
++i;
++k;
++n;
return i;
} public Demo2(String string) { print(string);
} public static void main(String[] args) {
Demo2 d = new Demo2("T");
}
}

问打印了多少次,每次打印的值分别是多少?如果例子中注释的int a = print("a");取消注释呢?

答案

当前执行次数为9次,去掉注释会加3次,也就是12次。需要注意的是每次调用new Demo2("")会执行两次print,去掉注释执行三次。

i=0   constructor code  k=0  n=0   j=0
i=1 t1 k=1 n=1 j=0
i=2 constructor code k=2 n=2 j=0
i=3 t2 k=3 n=3 j=0
i=4 i k=4 n=4 j=0
i=5 j k=5 n=5 j=0
i=6 static code k=6 n=99 j=6
i=7 constructor code k=7 n=100 j=6
i=8 T k=8 n=101 j=6
i=0   a  k=0  n=0   j=0
i=1 constructor code k=1 n=1 j=0
i=2 t1 k=2 n=2 j=0
i=3 a k=3 n=3 j=0
i=4 constructor code k=4 n=4 j=0
i=5 t2 k=5 n=5 j=0
i=6 i k=6 n=6 j=0
i=7 j k=7 n=7 j=0
i=8 static code k=8 n=99 j=8
i=9 a k=9 n=100 j=8
i=10 constructor code k=10 n=101 j=8
i=11 T k=11 n=102 j=8

答出来了吗( •̀ ω •́ )✧


再来一道:

public class Father {
private String s = "father"; public Father(){
m();
} public void m(){
System.out.println(s);
}
} public class Son extends Father {
String s2 = "son"; @Override
public void m() {
System.out.println(s2);
} public static void main(String[] args) {
Father f1 = new Son();//第一句
f1.m();//第二句
Father f2 = new Father();//第三句
}
}

答案:

null

son

father

  第一句,我本来以为执行父类构造方法的时候,m方法是在父类调用的,因此会执行父类的m方法,此时s已经赋值,便会打印“Father”,但实际上却并非如此。需要注意的是,此时是多态形式实例化Son,并且m()已经被重写,因此即使在父类中调用,调用的仍然是子类的m()方法!,此时子类中s2并未赋值,因此打印null。

  假如不使用多态,直接构造父类对象,此时则调用父类的m方法。

  直接实例化子类或者将f1向下转型,调用m()时均为子类。

  这里解释下为什么“多态形式实例化Son,并且m()已经被重写,因此即使在父类中调用,调用的仍然是子类的m()方法”:需要知道的是我们在方法中调用其他成员方法,比如上例中父类的构造方法调用m()方法,其实都是this.m(),只不过this是可以省略的,并且在上例中,this只有一个,那就是子类对象,因此父类构造方法public Father(){ m(); }其实就是public Father(){ this.m(); }this是子类对象,调用的当然是子类重写的方法,只有子类没有找到该方法的情况下才会执行父类的方法。


参考此博客

一个例子搞清楚Java程序执行顺序的更多相关文章

  1. [转]JAVA程序执行顺序,你了解了吗:JAVA中执行顺序,JAVA中赋值顺序

    本文主要介绍以下两块内容的执行顺序,熟悉的大虾可以直接飘过. 一.JAVA中执行顺序 静态块 块 构造器 父类构造器 二.JAVA中赋值顺序 静态块直接赋值 块直接赋值 父类继承的属性已赋值 静态变量 ...

  2. 深入了解Java程序执行顺序

    Java中main方法,静态,非静态的执行顺序详解 Java程序运行时,第一件事情就是试图访问main方法,因为main相等于程序的入口,如果没有main方法,程序将无法启动,main方法更是占一个独 ...

  3. java程序执行顺序

    原来自己一直都没弄明白Java程序的执行顺序问题,今天,自己写了个测试,果然与自己考虑的有差距 测试代码: 一个父类Animal 一个子类Dog 测试类Test 运行结果: 所以执行顺序是: 父类An ...

  4. 深入了解类加载过程及Java程序执行顺序

    前言 在Java中,静态 Static关键字使用十分常见 本文全面 & 详细解析静态 Static关键字,希望你们会喜欢 目录 1. 定义 一种 表示静态属性的 关键字 / 修饰符 2. 作用 ...

  5. JAVA程序执行顺序(静态代码块》非静态代码块》静态方法》构造函数)

    总结:静态代码块总是最先执行. 非静态代码块跟非静态方法一样,跟对象有关.只不过非静态代码块在构造函数之前执行. 父类非静态代码块.构造函数执行完毕后(相当于父类对象初始化完成), 才开始执行子类的非 ...

  6. java 程序执行顺序之继承

    1.首先会初始化父类,因为没有父类子类也无从谈起.第一步初始化static 变量 或者 静态初始化话块 2.初始化子类的static 变量 或者 静态初始化块 3.顺序初始化父类普通变量 或者 父类普 ...

  7. Java程序执行过程及内存机制

    本讲将介绍Java代码是如何一步步运行起来的,其中涉及的编译器,类加载器,字节码校验器,解释器和JIT编译器在整个过程中是发挥着怎样的作用.此外还会介绍Java程序所占用的内存是被如何管理的:堆.栈和 ...

  8. java中子类继承父类程序执行顺序

    java中子类继承父类程序执行顺序 FatherTest.java public class FatherTest { private String name; public FatherTest() ...

  9. PHPWind 8.7中代码结构与程序执行顺序

    pw9在此不谈,他是完全重构的作品,是完全MVC下的体系.当然,其中很多东西在PW8.7下已经可见端倪. 主要代码结构 1. 以现代的观点,PW是多入口应用模式,程序根目录下的文件几乎都是入口: 2. ...

随机推荐

  1. java 寒假作业

    寒假作业 现在小学的数学题目也不是那么好玩的. 看看这个寒假作业: □ + □ = □ □ - □ = □ □ × □ = □ □ ÷ □ = □ (如果显示不出来,可以参见[图1.jpg]) 每个方 ...

  2. 【转】Windows中使用TortoiseGit提交项目到GitLab配置

    转  原文地址 https://www.cnblogs.com/xiangwengao/p/4134492.html   下文来给各位介绍Windows中使用TortoiseGit提交项目到GitLa ...

  3. flask 常用数据模型模板

    1.一对多关系模型 示例代码 class Role(db.Model): """角色表""" __tablename__ = 'roles' ...

  4. MQTT 协议学习:007-Keep Alive 连接保活 与 对应报文(PINGREQ、PINGRESP)

    背景 keep alive 是 CONNECT 报文中可变头的一部分. 我们提到过 Broker 需要知道 Client 是否非正常地断开了和它的连接,以发送遗愿消息.实际上 Client 也需要能够 ...

  5. 《ES6标准入门》(阮一峰)--3.变量的解构赋值

    1.数组的解构赋值 基本用法 ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring). 以前,为变量赋值,只能直接指定值. let a = 1; l ...

  6. P1002 写出这个数(Basic Level)

    转跳点:

  7. springcloud--Feign(WebService客户端)

    Feign是一个声明式的Web服务客户端,使用Feign可使得Web服务客户端的写入更加方便. 它具有可插拔注释支持,包括Feign注解和JAX-RS注解.Feign还支持可插拔编码器和解码器.Spr ...

  8. CentOs 后台运行jar

    1.启动jar包,后台运行 nohup java -jar xxxx.jar & 2.结束运行 查出正在运行的进程 ps -ef | grep java 杀掉进程 kill pid 上面橙色字 ...

  9. SpringBoot#RestControllerAdvice

    __震惊! 不可避免的访问一些控制器会产生一些异常,这些异常不经处理传递到前台页面,会很难看. 涉及到的注解: org.springframework.web.bind.annotation.Rest ...

  10. 101-PHP二维数组的元素输出三,封装成函数

    <?php $arr=array(array(76,87,68), array(65,89,95), array(90,80,66), array(90,95,65),5,234,56,'Hel ...