静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑 您所在的位置:网站首页 静态变量和静态方法执行顺序 静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑

静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑

2024-06-19 01:08| 来源: 网络整理| 查看: 265

面试总在懵逼和牛逼之间徘徊。切忌:考官手下留点情,给个机会行不行 作者:A哥(YourBatman) 公众号:BAT的乌托邦(ID:BAT-utopia) 文末是否有彩蛋:有

目录 前言继承案例case1:父类和子类有同名同类型的属性时case2:父类和子类有同名但不同类型的属性时case3:下面代码输出什么?变种面试题原因: 冷知识 case4:子类和父类有`同名同类型`的`静态`变量的时候case5:静态代码块属于类的,并且优先于main方法执行(有难度) 注解对执行顺序的影响继续补充:子类默认调用`父类构造函数`问题 总结关注A哥

前言

各位小伙伴大家好,我是A哥。如果问:Java的三大特性是什么?你顺口就能答出:封装、继承、多态。如果继续问:你真的了解Java中的继承吗?

或许你本来很懂,但被我这么一问就有点怀疑了。那么,就看看本文吧,保证你会有收获,能让你更好的理解Java中的继承机制。

继承案例 case1:父类和子类有同名同类型的属性时 public class Main { public static void main(String[] args) { // 使用多态 Parent chidParent = new Child(); System.out.println("Parent:" + chidParent.getAge()); //40 System.out.println("Parent:" + chidParent.age); //18 这个结果你能接受吗?哈哈 // 直接使用原本类型 Child child = new Child(); System.out.println("Child:" + child.getAge()); //40 System.out.println("Child:" + child.age); //40 } } @Getter @Setter class Child extends Parent { public Integer age = 40; } @Getter @Setter class Parent { public Integer age = 18; }

输出结果:

Parent:40 Parent:18 Child:40 Child:40

我相信和最初的我一样,对Parent:18这个结果大吃一惊,what?其实这就是Java的继承机制,对此说明如下:

属性属于实例自己的,所以Parent的age属性值是18,这就解释通了属性不存在覆盖(即使同名),而方法是实实在在的覆盖(复写)。所以你调用getAge()方法返回的100%是40喽 case2:父类和子类有同名但不同类型的属性时

结论同上。

case3:下面代码输出什么? public class Main { public static void main(String[] args) { new Child(); } } @Getter class Child extends Parent { static { System.out.println("Child的静态块"); } { System.out.println("Child的构造块"); } Child() { System.out.println("Child的构造方法"); } } @Getter class Parent { Integer age = 18; static { System.out.println("Parent的静态块"); } { System.out.println("Parent的构造块"); } Parent() { System.out.println("Parent的构造方法"); } }

结果输出:

Parent的静态块 Child的静态块 Parent的构造块 Parent的构造方法 Child的构造块 Child的构造方法

Tips:构造代码块优先于构造方法执行,且优先于属性初始化之前执行 @PostConstruct是对象的属性都初始化ok了之后才去执行的(注意你new的话,@PostConstruct方法是不会执行的,他是Spring给与的支持哦~)

值得注意的是,此处子类没有显示调用super(),但父类的构造还是执行了的。但是,但是,但是,如果构造快为有参构造,请记得显示调用super方法,否则父类是不能被初始化的。如果子类的构造器没有显示地调用超类的构造器,则将自动调用超类默认(没有参数) 的构造器。如果超类没有不带参数的构造器,并且在子类的构造器又没有显式地调用超类的其他构造器,则 java 编译器将报告错误~

变种面试题 public class StaticTest { public static void main(String[] args) { staticFunction(); } // 静态变量(有实例化的过程,这就是本题的重点) static StaticTest st = new StaticTest(); static { //System.out.println(b); // 编译报错:因为b在构造代码块后边,此处不能引用。因此Java代码是从上到下的顺序 System.out.println("1"); } { System.out.println("2"); } StaticTest() { System.out.println("3"); System.out.println("a=" + a + ",b=" + b); } public static void staticFunction() { System.out.println("4"); } // 这两个变量写在最后面 int a = 110; static int b = 112; }

输出:

2 3 a=110,b=0 1 4

答案五花八门,真正能答对这道题的小伙伴少之又少。从结果中,这里先给你扔个结论:

先初始化静态变量,也就是执行new StaticTest(),从而打印:2再执行构造函数,打印:3和a=110,b=0 为何a=110,而b却为0呢? 1. 执行构造函数之前,必须初始化实例属性,所以a=110 2. 静态变量从上到下初始化,而st变量还没初始化完呢,所以b此时值为0 执行紧跟着的静态代码块。打印:1执行静态方法staticFunction,打印:4

从该结果你应该能知道:static变量可不是100%一定在实例变量之前被赋值(初始化哦~),比如本例的b就在a之后初始化了

原因:

类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载。

只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值,因此只针对这两个阶段进行分析;

类的准备阶段:需要做是为类变量(static变量)分配内存并设置默认值(注意此处都是先给默认值),因此类变量st为null、b为0;

需要注意的是,如果类变量是final的,编译时javac就会为它赋上值。因此上面如果我们这样写static final int b=112它哪怕在准备阶段,值就应该是112了

类的初始化阶段:需要做的是执行类构造器(请注意:这里不是指的构造函数)。

类构造器:编译器收集所有静态语句块和类变量的赋值语句,按语句在源码中的**顺序(请注意这三者是有序的)**合并生成类构造器

因此现在执行:st = new StaticTest().此时我们发现,就会进行对象的初始化了(看到没,这个时候b变量的赋值语句还没有执行哦~~~)

而对象初始化的顺序为:成员变量 -> 普通代码块 -> 构造函数,因此这一波过后:a=110了。 输出为:

2 3 a=110,b=0

需要注意的是,此时b仍然为0,并没有被赋值哦~

到此st = new StaticTest()这句就执行结束了。继续执行类构造器,显然就会执行static语句块了~~~输出1,最后调用静态方法,就输出4了 完美~

冷知识

通过结果看,有点颠覆我们之前的认知。其实这是一个冷知识:

它的关键在于:static StaticTest st = new StaticTest()这句代码,内嵌的这个变量恰好是个静态成员,而且是本类的实例 这就导致了这个有趣的现象:“实例初始化竟然出现在静态初始化之前”。

这里面我只做一小步变化:

static StaticTest st = new StaticTest() 改成 StaticTest st = new StaticTest() 或者改成: static Object st = new Object();

最终输出结果就为(符合我们常识了吧,啊哈哈哈哈):

1 4 case4:子类和父类有同名同类型的静态变量的时候

结论就不用解释了:静态变量属于类的,和继承无关。

case5:静态代码块属于类的,并且优先于main方法执行(有难度) public class StaticDemo1 { public static void main(String[] args) { speak(); //StaticDemo1 t1 = new StaticDemo1(); //System.out.println(t1.i); } // 静态变量 static int i = 1; // 静态方法 static void speak() { System.out.println("a"); } // 静态代码块 static { i = i + 3; System.out.println(i); } // 构造函数 public StaticDemo1() { i = i + 5; System.out.println(i); } }

输出:

4 a

4在a之前输出,证明:毕竟mian方法属于StaticDemo1类的方法,所以会先执行此类的静态变量 + 静态代码块。

其它不变,改为这样:

public static void main(String[] args) { StaticDemo1 t1 = new StaticDemo1(); System.out.println(t1.i); speak(); } 4 9 9 a 执行静态代码块:打印4执行构造方法:打印9System.out.println(t1.i)直接输出,打印9(此时i的值是9)执行speak():打印1 public static void main(String[] args) { speak(); StaticDemo1 t1 = new StaticDemo1(); System.out.println(t1.i); } 4 a 9 9

这个输出,在意料之中,不再解释喽。

这是一道面试题,考察的是:static块真正的执行时机。若想真正了解类的装载,请去了解JVM吧~

注解对执行顺序的影响

特别的,这里我介绍一下各种注解影响的执行顺序,如下代码:

@Component public class InitBeanTest implements InitializingBean,ApplicationListener { @Autowired DemoService demoService; public InitBeanTest() { System.err.println("----> InitSequenceBean: constructor: "+demoService); } @PostConstruct public void postConstruct() { System.err.println("----> InitSequenceBean: @PostConstruct: "+demoService); } @Override public void afterPropertiesSet() throws Exception { System.err.println("----> InitSequenceBean: afterPropertiesSet: "+demoService); } @Override public void onApplicationEvent(ContextRefreshedEvent arg0) { System.err.println("----> InitSequenceBean: onApplicationEvent"); } } 执行结果: ----> InitSequenceBean: constructor: null ----> InitSequenceBean: @PostConstruct: com.xxx.service.impl.DemoServiceImpl@40fe544 ----> InitSequenceBean: afterPropertiesSet: com.xxx.service.impl.DemoServiceImpl@40fe544 ----> InitSequenceBean: onApplicationEvent

根据代码演示,得出文字版结论:

构造函数是每个类最先执行的,这个时候,bean属性还没有被注入@PostConstruct优先于afterPropertiesSet执行:在这执行,属性已经完成了赋值(注入) 继续补充:子类默认调用父类构造函数问题

Java有个很有趣的现象:父类有N多个构造函数,子类如果只写一个的话那么子类最终就只有一个构造函数可用,因此子类在这方面要特别的注意喽。 默认情况下,子类在使用构造函数初始化时(不管是子类使用有参构造还是无参构造),默认情况下都会调用父类的无参构造函数(相当于调用了super())。看看下面几个变种如下:

// 父类木有空的构造 只有一个有参构造 class Parent{ private Integer id; public Parent(Integer id){ System.out.println("this is parent cons..."); } }

此时候我们发现发现如下三问题: 1、子类Child必须有对应的有参构造 在这里插入图片描述 2、super(id)必须显示的写出,否则编译不通过 在这里插入图片描述 3、原则上,子类的构造函数不能多于父类的 在这里插入图片描述 4、子类构造函数若多余父类(或者类型啥的和父类不匹配),需要显示的调用父类构造函数 在这里插入图片描述 结论:

1、子类构造器执行之前必须能够先执行父类的构造函数(super(xxx)必须放在第一行代码)2、若父类有空构造,子类构造默认都会调用super()。若父类木有空构造,子类所有构造都必须显示调用super(xxx)· 总结

据反馈,看了此篇文章后,很多小伙伴感觉自己学的是另外一个Java,其实这就是JavaSE,很多架构师认为,Java基础才是精华中的精华(一流),Spring才属于应用级别的技术(二流)。

关注A哥 AuthorA哥(YourBatman)个人站点[email protected]微 信fsx641385712活跃平台 公众号BAT的乌托邦(ID:BAT-utopia)知识星球BAT的乌托邦每日文章推荐每日文章推荐

BAT的乌托邦 往期精选

Netflix OSS套件一站式学习驿站(Eureka、Hystrix、Ribbon、Feign、Zuul…)


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有