当我们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. 今日份学习: Spring中使用AOP并实现redis缓存?

    笔记 在Spring中如何使用AOP? Spring是如何切换JDK动态代理和CGLIB的? spring.aop.proxy-target-class=true (在下方第二个链接中,原生doc中提 ...

  2. 从ofo到乐视,变卖资产好过冬靠谱吗?

    今年年底,有很多人"被迫"离职.他们为了应对生活压力和找工作的不确定性,尝试在二手平台上卖出自己的奢侈品或心爱之物,以期度过潜在的难关.而对于很多企业来说,这个冬天也非常冷.依靠常 ...

  3. define可变参数,float数据传输

    define可变参数 一般在调试打印Debug信息的时候, 需要可变参数的宏. 从C99开始可以使编译器标准支持可变参数宏(variadic macros), 另外GCC也支持可变参数宏, 但是两种在 ...

  4. 使用vue框架开发前端项目的步骤

    前端项目的开发 1. 本地安装nodejs https://nodejs.org/en/download/ 2. 测试安装 > node -v 3. 本地安装git > git --ver ...

  5. 009、Java中超过了int的最大值或最小值的结果

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  6. Win7安装Oracle Instantclient ODBC驱动 后配置DSN时出错的解决办法 SQORAS32

    安装过程简述 oracle官网下载了 instantclient-odbc-nt--.zip instantclient-basic-nt-.zip 我这是32位版的win7,按照需要下载对应的版本. ...

  7. node重点 模块

    node模块 1.全局模块(对象)(像js中的window document) 定义:何时何地都可以访问,不需要引用 1.process.env 环境变量 计算机属性 高级系统设置 高级 环境变量 作 ...

  8. Linux每日练习-复习紧急救援模式下重改root权限密码 20200225

  9. Sass 安装到使用

    sass学习 Sass 可以通过以下三种方式使用:作为命令行工具:作为独立的 Ruby 模块 (Ruby module):或者作为 Rack-enabled 框架的插件(例如 Ruby on Rail ...

  10. shell中取字符串子串的几种方式 截取substr

    shell中取字符串子串的几种方式 echo "123456789" | awk '{print substr($0,5,2)}' 截取 1)awk中函数substrsubstr( ...