十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
java中static关键字如何使用,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
成都创新互联长期为上千客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为达拉特企业提供专业的做网站、成都网站制作,达拉特网站改版等技术服务。拥有10年丰富建站经验和众多成功案例,为您定制开发。
public class Parent { static { System.out.println("Parent static initial block"); } { System.out.println("Parent initial block"); } public Parent() { System.out.println("Parent constructor block"); } } public class Child extends Parent { static { System.out.println("Child static initial block"); } { System.out.println("Child initial block"); } private Hobby hobby = new Hobby(); public Child() { System.out.println("Child constructor block"); } } public class Hobby { static{ System.out.println("Hobby static initial block"); } public Hobby() { System.out.println("hobby constructor block"); } }
当执行new Child()时,上述代码输出什么?
相信有不少同学遇到过这类问题,可能查过资料之后接着就忘了,再次遇到还是答不对。接下来课代表通过4个步骤,带大家拆解一下这段代码的执行顺序,并借此总结规律。
下面两段代码对比一下编译前后的变化:
编译前的Child.java
public class Child extends Parent { static { System.out.println("Child static initial block"); } { System.out.println("Child initial block"); } private Hobby hobby = new Hobby(); public Child() { System.out.println("Child constructor block"); } }
编译后的Child.class
public class Child extends Parent { private Hobby hobby; public Child() { System.out.println("Child initial block"); this.hobby = new Hobby(); System.out.println("Child constructor block"); } static { System.out.println("Child static initial block"); } }
通过对比可以看到,编译器把初始化块和实例字段的赋值操作,移动到了构造函数代码之前,并且保留了相关代码的先后顺序。事实上,如果构造函数有多个,初始化代码也会被复制多份移动过去。
据此可以得出第一条优先级顺序:
初始化代码 > 构造函数代码
类的加载过程可粗略分为三个阶段:加载 -> 链接 -> 初始化
初始化阶段可被8种情况周志明》P359 "触发类初始化的8种情况")触发:
使用 new 关键字实例化对象的时候
读取或设置一个类型的静态字段(常量")除外)
调用一个类型的静态方法
使用反射调用类的时候
当初始化类的时候,如果发现父类还没有进行过初始化,则先触发其父类初始化
虚拟机启动时,会先初始化主类(包含main()方法的那个类)
当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。
如果接口中定义了默认方法(default 修饰的接口方法),该接口的实现类发生了初始化,则该接口要在其之前被初始化
其中的2,3条目是被static代码触发的。
其实初始化阶段就是执行类构造器
根据条目5,JVM 会保证在子类的
小结一下:访问类变量或静态方法,会触发类的初始化,而类的初始化就是执行
由此得出第二条优先级顺序:
父类的static代码 > 子类的static代码
我们都知道,static代码(静态方法除外)只执行一次。
你有没有想过,这个机制是如何保证的呢?
答案是:双亲委派模型。
JDK8 及之前的双亲委派模型是:
应用程序类加载器 → 扩展类加载器 → 启动类加载器
平时开发中写的类,默认都是由 应用程序类加载器加载,它会委派给其父类:扩展类加载器。而扩展类加载器又会委派给其父类:启动类加载器。只有当父类加载器反馈无法完成这个加载请求时,子加载器才会尝试自己去完成加载,这个过程就是双亲委派。三者的父子关系并不是通过继承,而是通过组合模式实现的。
该过程的实现也很简单,下面展示关键实现代码:
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 首先检查该类是否被加载过 // 如果加载过,直接返回该类 Class> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 如果父类抛出ClassNotFoundException // 说明父类无法完成加载请求 } if (c == null) { // 如果父类无法加载,转由子类加载 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
结合注释相信大家很容易看懂。
由双亲委派的代码可知,同一个类加载器下,一个类只能被加载一次,也就限定了它只能被初始化一次。所以类中的 static代码(静态方法除外)只在类初始化时执行一次
前面已经介绍了编译器自动生成的类构造器:
相应的,编译器还会生成一个
所以,当我们new 一个类时,如果JVM未加载该类,则先对其进行初始化,再进行实例化。
至此,第三条优先级规则也就呼之欲出了:
静态代码(static{}块、静态字段赋值语句) > 初始化代码({}块、实例字段赋值语句)
将前文的三条规则合并,总结出如下两条:
1.静态代码(static{}块、静态字段赋值语句) > 初始化代码({}块、实例字段赋值语句) > 构造函数代码
2.父类的static代码 > 子类的static代码
根据前文总结,初始化代码和构造函数代码被编译器收集到了
父类
> 子类
> 父类
> 子类
对应到开篇的问题,我们来实践一下:
当执行new Child()时,new关键字触发了 Child 类的初始化 ,JVM 发现其有父类,则先初始化 Parent 类,开始执行Parent类的
然后开始实例化 一个Child类的对象,此时准备执行 Child 的
看完上述内容,你们掌握java中static关键字如何使用的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读!