Java程序员的日常 —— Java类加载中的顺序
之前说过Java中类的加载顺序,这次看完继承部分,就结合继承再来说说类的加载顺序。
继承的加载顺序
由于static块会在首次加载类的时候执行,因此下面的例子就是用static块来测试类的加载顺序。
package xing.test.thinking.chap7;
class A{
static{
System.out.println("A static");
}
}
class B extends A{
static{
System.out.println("B static");
}
}
class C extends B{
private static D d = new D();
static{
System.out.println("C static");
}
}
class D{
static{
System.out.println("D static");
}
}
public class ExtendTest {
public static void main(String[] args) {
C c = new C();
}
}
在上面的例子中,类C继承B,B继承A,而C有依赖于D。因此当创建C的时候,会自动加载C继承的B和依赖的D,然后B又会加载继承的A。只有A加载完,才能顺利的加载B;BD加载完,才能加载C。这就是类的加载顺序了。
A static
B static
D static
C static
所有的变量初始化完,才会执行构造方法
在类的加载过程中,只有内部的变量创建完,才会去执行这个类的构造方法。
package xing.test.thinking.chap7;
class A2{
B2 b2 = new B2();
static{
System.out.println("A static");
}
public A2() {
System.out.println("A2()");
}
}
class B2{
C2 c2 = new C2();
D2 d2 = new D2();
static{
System.out.println("B static");
}
public B2() {
System.out.println("B2()");
}
}
class C2{
static{
System.out.println("C static");
}
public C2() {
System.out.println("C2()");
}
}
class D2{
static{
System.out.println("D static");
}
public D2() {
System.out.println("D2()");
}
}
public class VarTest {
public static void main(String[] args) {
A2 a2 = new A2();
}
}
在上面的例子中,A2里面有B2变量,B2则有C2D2变量。因此类的加载还是先读取到哪个,就执行相应的静态块。
当依赖的对象都定义完,才会执行构造方法:
A static
B static
C static
C2()
D static
D2()
B2()
A2()
静态成员与普通成员类的加载区别
在类的加载过程中,静态成员类的对象,会优先加载;而普通成员类的对象则是使用的时候才回去加载。
package xing.test.thinking.chap7;
class A3{
B3 b3 = new B3();
static C3 c4 = new C3();
static{
System.out.println("A3");
}
}
class B3{
static{
System.out.println("B3");
}
}
class C3{
static{
System.out.println("C3");
}
}
public class StaticTest {
public static void main(String[] args) {
A3 a3 = new A3();
}
}
输出:
C3
A3
B3
总结
第一点,所有的类都会优先加载基类
第二点,静态成员的初始化优先
第三点,成员初始化后,才会执行构造方法
第四点,静态成员的初始化与静态块的执行,发生在类加载的时候。
第四点,类对象的创建以及静态块的访问,都会触发类的加载。
补充类构造方法的顺序
看代码:
package xing.test.thinking.chap8;
class A{
public A() {
System.out.println("A");
}
}
class B extends A{
public B() {
System.out.println("B");
}
}
class C extends B {
private D d1 = new D("d1");
private D d2 = new D("d2");
public C() {
System.out.println("C");
}
}
class D {
public D(String str) {
System.out.println("D "+str);
}
}
public class ExtendTest {
public static void main(String[] args) {
C c = new C();
}
}
执行结果:
A
B
D d1
D d2
C
因此可以得出结论:
- 首先会调用基类的构造方法
- 其次,调用成员的构造方法
- 最后,调用自己的构造方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?