Java中静态代码块、构造方法、代码块、父类与子类之间执行顺序及父类子类实例化对象

本文深入探讨Java中的静态代码块、构造代码块与普通代码块的概念及其执行顺序,同时解析父类与子类间的代码块与构造方法的关系,帮助读者更好地理解Java对象的构造与初始化过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【1】几个概念

① 静态代码块

在java中使用static关键字声明的代码块。每个静态代码块只会执行一次。JVM在加载类时会执行静态代码块,静态代码块先于主方法执行。

	static{
		System.out.println("这是静态代码块");
	}

注意: 静态代码块不能存在于任何方法体内。


② 构造代码块(实例初始化块):

直接在类中定义且没有加static关键字的代码块称为{}构造代码,在创建实例对象的时候先于构造函数被调用执行

class Test{
    int id;
    String name;
	// JVM加载class时执行
	static{
		System.out.println("这是静态代码块");
	}
    // 新建对象的时候执行,构造代码块会先于构造函数被调用时执行
    {
        this.id= 5;
        this.name = "测试";
        System.out.println("这是构造代码块");
    }
    
    Test(int id){
        this.id = id;
    }
    
    public String toString(){
        return "name: "+this.name +"  ,   "+"id: "+ this.id ;    
    }
}

③ 普通代码块

在方法或语句内出现的{}就称为普通代码块。普通代码块和一般的语句执行顺序由他们在代码中出现的次序决定–“先出现先执行”。

public class GeneralCodeBlock01{
    public static void main(String[] args){
        //如下为普通代码块
          {
            int x=3;
            System.out.println("1,普通代码块内的变量x="+x);    
          }
          
          int x=1;
          System.out.println("主方法内的变量x="+x);
          
          {
             int y=7;
             System.out.println("2,普通代码块内的变量y="+y);    
          }
        }
  }

属性、方法、构造方法和代码块都是类中的成员,在创建对象时,各成员的执行顺序如下:

  • 父类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
  • 子类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
  • 父类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
  • 执行父类构造方法;
  • 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
  • 执行子类构造方法。

【2】父类、子类之间代码块与构造方法

示例代码如下:

package com.web.test2;

public class HelloA {
    static{
        System.out.println("static A");
    }
    
    {System.out.println("I'm A class");}
    
    public HelloA(){

        System.out.println("HelloA");
    }
    public HelloA(String s){

        System.out.println(s+"HelloA");
    }
    
    public static void main(String[] args) {
        new HelloB();
    }
}
 class HelloB extends HelloA{
    public HelloB () {
    	//只能调用一个父类的构造方法
//    	super();
    	super("parent");
        System.out.println("HelloB");
    }
    {System.out.println("I'm B class");}
    static{
        System.out.println("static B");
    }

    
}

执行结果:

static A
static B
I'm A class
parentHelloA
I'm B class
HelloB


总结如下:

① 代码块于构造方法之前执行,静态于非静态之前;

② 在类第一次被调用时,加载该类,静态代码块只执行一次;

③ 项目启动时,只加载需要加载的类(比如xml中显示配置的bean,或者web.xml中的listener等),并不会将所有的类都加载(这是很可怕的事情);

④ 静态代码块只能调用静态变量;静态方法只能调用静态变量;

⑤ 非静态代码块或非静态方法可以调用任何(静态+非静态)变量。

⑥ 非静态代码块在实例化对象时,于构造方法之前执行。


【3】父类、子类与super()

示例代码如下:


public class People {
	String name;
	public People() {
		System.out.println(1);
	}
	public People(String name) {
		System.out.println(2);
		this.name = name;
	}
}
 class Child extends People{
	 People father;
	public Child () {
		//super()系统会默认添加的
		System.out.println(4);
	}
	public Child (String name) {
		//super()系统会默认添加的
		System.out.println(3);
		this.name = name;
		father = new People(name+":F");
	}
	public static void main(String[] args) {
		new Child("mike");
	}
}

故执行结果:132


【4】类中添加静态变量

静态变量与静态代码块一样,都是在类被加载的时候赋值/被执行,而且静态变量与静态代码块执行顺序是按照代码上次次序进行执行的。

示例代码如下:

public class ExA {  
	//静态成员
    private static ExA a = new ExA();  
    //静态代码块
    static {  
        System.out.println("父类--静态代码块");  
    }  
    //构造方法
    public ExA() {  
        System.out.println("父类--构造函数");  
    }  
	  // 实例化代码块
    {  
        System.out.println("父类--非静态代码块");  
    }  
  
    public static void main(String[] args) {  
        new ExB();  
    }  
}  
  
class ExB extends ExA {  
	
    private static ExB b = new ExB();
    
    static {  
        System.out.println("子类--静态代码块");  
    }  
    {  
        System.out.println("子类--非静态代码块");  
    }  
  
    public ExB() {  
        System.out.println("子类--构造函数");  
    }  
}  

  • result as follows :
// 父类静态成员
父类--非静态代码块
父类--构造函数

//父类静态代码块
父类--静态代码块

父类--非静态代码块
父类--构造函数

子类--非静态代码块
子类--构造函数

子类--静态代码块

父类--非静态代码块
父类--构造函数

子类--非静态代码块
子类--构造函数


分析如下:

① 首先加载父类静态成员和静态代码块

 private static ExA a = new ExA();  
    
 static {  
     System.out.println("父类--静态代码块");  
 }  

这里静态成员赋值为实例化A对象,故而需要先执行A的实例化代码块和构造方法:

 public ExA() {  
    System.out.println("父类--构造函数");  
  }  
 // 实例化代码块
  {  
      System.out.println("父类--非静态代码块");  
  }  

此时输入结果为:

// 父类静态成员
父类--非静态代码块
父类--构造函数

//父类静态代码块
父类--静态代码块

② 其次加载子类静态成员和静态代码块

private static ExB b = new ExB();
    
static {  
    System.out.println("子类--静态代码块");  
}  

子类静态成员赋值为实例化B对象,按照博文最上面子类实例化对象过程可知,此时该加载父类实例化块和构造方法,此时结果更新为:

// 父类静态成员
父类--非静态代码块
父类--构造函数

//父类静态代码块
父类--静态代码块

父类--非静态代码块
父类--构造函数

静态变量赋值还没有执行完!!该执行子类实例化代码块和构造方法,此时结果更新为:

# 1. 父类静态成员赋值和静态代码块
// 父类静态成员
父类--非静态代码块
父类--构造函数

//父类静态代码块
父类--静态代码块

# 2. 子类静态成员赋值和静态代码块
// 每次调用父类构造方法前都会调用父类实例化代码块
父类--非静态代码块
父类--构造函数这里写代码片

子类--非静态代码块
子类--构造函数


子类B静态变量赋值完,该执行子类静态代码块,此时结果更新如下:

# 1. 父类静态成员赋值和静态代码块
// 父类静态成员
父类--非静态代码块
父类--构造函数

//父类静态代码块
父类--静态代码块

# 2. 子类静态成员赋值和静态代码块
// 每次调用父类构造方法前都会调用父类实例化代码块
父类--非静态代码块
父类--构造函数这里写代码片

子类--非静态代码块
子类--构造函数

# 3. 子类静态代码块
子类--静态代码块


③ 静态完了,该非静态了

按照如下步骤,该执行第3/4/5/6:

父类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
子类静态成员和静态初始化块,按在代码中出现的顺序依次执行;
父类实例成员和实例初始化块,按在diam中出现的顺序依次执行;
执行父类构造方法;
子类实例成员和实例初始化块,按在代码中出现的顺序依次执行;
执行子类构造方法。

无需多考虑,结果更新如下:

# 1. 父类静态成员赋值和静态代码块
// 父类静态成员
父类--非静态代码块
父类--构造函数

//父类静态代码块
父类--静态代码块

# 2. 子类静态成员赋值和静态代码块
// 每次调用父类构造方法前都会调用父类实例化代码块
父类--非静态代码块
父类--构造函数这里写代码片

子类--非静态代码块
子类--构造函数

# 3. 子类静态代码块
子类--静态代码块

# 4. 父类非静态
父类--非静态代码块
父类--构造函数

# 5. 子类非静态
子类--非静态代码块
子类--构造函数

确实比较难理解,需要特别注意父类和子类静态变量赋值的时候取值为实例化对象,非常量或者null值!!


【5】不仅仅父子,还有祖孙

示例如下:

package com.web.test2;

public class Creature {

    public Creature(){

        System.out.println("空的");
    }
    public static void main(String[] args) {
        new wolf();
    }

}
class Animal extends Creature{

    public Animal(String name) {
        super();
        System.out.println("一个参数"+name);
    }
    public Animal(String name,int age){
        //super();错误
        this(name);
        System.out.println("这个动物带了两个属性"+age);
    }
}
class wolf extends Animal{

    public wolf(){
        super("灰太狼",3);
        System.out.println("狼带了三个属性");
    }

}

执行结果如下:

空的
一个参数灰太狼
这个动物带了两个属性3
狼带了三个属性

默认会调用父类无参构造方法,如果父类还有parent,则继续向上搜寻,直到Creature。

this()与super()在一个方法中只能存在一个。


【6】如果父类没有空参构造器呢?

父类如下所示:

public class Parent {

    private Integer id;
    private Integer age;

    public  Parent(Integer id,Integer age){
        this.id=id;
        this.age=age;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}


子类代码如下所示:

public class Child extends  Parent {

    private Integer id;
    private String name;

    @Override
    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

此时,直接报编译错误,提示如下:

There is no default constructor available in 'com.jane.model.Parent'

即,父类没有默认构造器,你需要重写父类构造器。修改代码如下:

public class Child extends  Parent {

    private Integer id;
    private String name;

    public Child(Integer id, Integer age) {
        super(id, age);
    }

    public Child(Integer id, Integer age, Integer id1, String name) {
        super(id, age);
        this.id = id1;
        this.name = name;
    }

    @Override
    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

此时子类同样不能存在空参构造器!!


【7】子类对象实例化是否会实例化父类对象

答案当然是否定的!!

首先举反例证明

如果父类是个抽象类呢?实例化子类的时候实例化父类,很显然矛盾!故而,实例化子类的时候不会实例化父类对象!!


其次相关理论

① 构造器只是负责对java对象实例变量执行初始化(也就是赋初始值),在执行构造器代码之前,该对象所占的内存已经被分配下来,这些内存里值都默认是空值——对于基本类型的变量,默认的空值就是0或false,对于引用类型的变量默认的空值就是null。

② 在处理java类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定,即成员变量被关联到了类上。必须明确,运行时(动态)绑定针对的范畴只是对象的方法。(方法的话只有static和final(所有private默认是final的)是静态绑定的.)

如果虚拟机在子类对象中找不到某个属性的时候,就到包含该构造方法中的类去找静态绑定相应的属性。

③ 当创建任何java对象时,程序总会先一次调用每个类父类非静态初始化块、父类构造器(总是从Object开始)执行初始化,最后才调用本类的非静态初始化块、构造器执行初始化。

具体请参考博文上面讲述过程。

此时当程序执行到①时,系统会先为父类中的私有属性在堆内存开辟空间

注意,这里并不会实例化父类对象,仅仅是为父类中的属性在堆中开辟了一段内存空间。

然后再为子类在堆内存空间,此时调用父类的构造方法(不写super()的话会隐式的调用),此时通过准备知识1中我们已经知道构造方法仅仅只是负责对java对象实例变量执行初始化,而不会实例化父类。

参考博文:JVM创建对象的奥秘


【8】子类继承父类什么?

常见的一句话,子类对象只能继承父类非私有的属性及方法;还有另外一句话–子类继承父类,子类拥有父类所有的属性和方法。

是否矛盾?该怎样理解?先看看第【7】部分,然后继续往下。

使用程序去验证,发现父类的私有属性和私有方法,子类是不能访问的,当然一些父类的私有属性可能可以通过相应的方法访问到,但是私有的方法似乎不能简单的访问,这里暂不考虑Java反射机制。

子类不能通过object.field的方式获取父类私有属性,这说明不属于子类成员变量!但是子类可以使用父类的public方法为父类的私有属性赋值并获取该私有属性值!这可以理解为子类拥有父类私有属性。

如下图所示,Child继承自父类Parent:

在这里插入图片描述

<think>首先,用户的问题是关于Java子类实例化父类构造器和方法的执行顺序。具体来说,用户问:"Java 子类实例化父类构造方法执行顺序 方法调用顺序"。我需要基于系统指令和提供的引用来回答。 系统指令要求: - 所有行内数学表达式用$...$格式(但这个问题可能不涉及数学表达式)。 - 独立公式用$$...$$格式(同样,可能不适用)。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 用户提供的引用包括: - [^1]: 子类调用父类构造方法Java虚拟机先执行父类构造方法,再执行子类构造方法。 - [^2]: 执行顺序父类静态代码块 -> 子类静态代码块 -> 父类构造代码块 -> 父类构造方法 -> 子类构造代码块 -> 子类构造方法。 - [^3]: 子类初始化时调用父类构造方法,通常有隐式的super()调用父类无参数构造方法。 - [^4]: 详细示例显示执行顺序,包括构造函数、属性、方法的初始化。 用户的上一个问题似乎是关于子类实例化时的顺序,所以我需要综合这些引用来构建回答。 回答结构: 1. **逐步解释执行顺序**:基于引用[^2]和[^4],列出完整的执行顺序。 2. **关键点**: - 父类构造方法执行。 - 静态代码块优先于实例初始化。 - 构造代码块构造方法之前。 - 方法调用顺序取决于实例类型。 3. **使用示例**:提供一个简单的代码示例来演示顺序。 4. **生成相关问题**:在结尾添加相关问题,如指令要求。 完整执行顺序(基于引用[^2]): - 父类静态代码块 - 子类静态代码块 - 父类构造代码块 - 父类构造方法 - 子类构造代码块 - 子类构造方法 在方法调用中,如果子类覆盖了方法,调用时会使用子类的方法版本(多态)。 回答应清晰、结构化: - 引言:简述顺序。 - 详细步骤:分步解释。 - 示例:提供代码和输出。 - 总结:强调关键点。 - 相关问题:添加相关问题。 相关问题生成(基于指令示例): 1. Java静态代码块和实例初始化块的区别? 2. 如何显式调用父类构造方法? 3. 多态在方法调用中如何工作? 4. 如果父类没有无参数构造方法子类如何处理? 确保引用自然添加:在解释中,当提到特定点时,添加引用标识,如[^2]。 最终回答用中文。</think>### Java子类实例化父类构造方法和方法的执行顺序Java中,当创建子类实例时,父类子类构造方法、初始化块(包括静态和实例块)以及方法调用遵循特定的顺序。这个顺序Java的类加载和初始化机制决定,确保对象父类继承的成员被正确初始化。下面我将逐步解释这个顺序,并提供一个代码示例来演示。整个过程基于Java的类加载和初始化规则,确保可靠性和准确性。 #### 关键执行顺序总结 当实例化子类时,完整的执行顺序如下(从类加载到对象初始化): 1. **静态初始化**:优先执行父类静态代码块,然后执行子类静态代码块(只在类首次加载时执行一次)。 2. **父类实例初始化**:执行父类的实例初始化块(构造代码块),然后执行父类构造方法。 3. **子类实例初始化**:执行子类的实例初始化块(构造代码块),然后执行子类构造方法。 4. **方法调用**:如果子类覆盖了父类的方法,在调用时会基于对象的实际类型(子类执行多态行为。 完整的顺序链为: 父类静态代码块子类静态代码块父类构造代码块父类构造方法子类构造代码块子类构造方法[^2]。 #### 详细步骤解释 1. **静态初始化阶段(类加载时)**: - Java虚拟机(JVM)加载类时,先加载父类执行静态代码块(`static { ... }`)。 - 然后加载子类执行静态代码块静态代码块执行一次,无论创建多少实例。 2. **父类实例初始化阶段**: - 当通过`new SubClass()`创建子类对象时,JVM首先隐式调用父类构造方法(通常通过`super()`,默认调用父类无参构造方法)。 - 在父类构造方法执行前,先执行父类的实例初始化块(即构造代码块,`{ ... }`)。 - 接着执行父类构造方法(例如`Parent() { ... }`),初始化父类成员变量。 3. **子类实例初始化阶段**: - 父类初始化完成后,JVM执行子类的实例初始化块(构造代码块)。 - 然后执行子类构造方法(例如`Child() { ... }`),初始化子类成员变量。 4. **方法调用顺序**: - 在构造方法或初始化块中调用方法时,如果子类覆盖了父类的方法,则调用子类版本(多态)。 - 例如,在父类构造方法中调用一个被覆盖的方法,会执行子类的实现,但此时子类成员可能尚未初始化(导致潜在问题,如`null`值)[^4]。 #### 代码示例演示 以下是一个简单的Java程序,展示子类实例化时的执行顺序父类`Parent`和子类`Child`分别包含静态代码块、实例初始化块、构造方法和覆盖的方法。 ```java class Parent { static { System.out.println("父类静态代码块"); // 步骤1 } { System.out.println("父类实例初始化块"); // 步骤3 } public Parent() { System.out.println("父类构造方法"); // 步骤4 print(); // 调用方法,多态行为 } public void print() { System.out.println("父类print方法"); } } class Child extends Parent { static { System.out.println("子类静态代码块"); // 步骤2 } { System.out.println("子类实例初始化块"); // 步骤5 } private String name = "Child"; // 子类成员变量 public Child() { System.out.println("子类构造方法"); // 步骤6 print(); // 调用方法 } @Override public void print() { System.out.println("子类print方法: name = " + name); // 多态调用 } } public class Main { public static void main(String[] args) { new Child(); // 创建子类实例 } } ``` **输出结果**: ``` 父类静态代码块 子类静态代码块 父类实例初始化块 父类构造方法 子类print方法: name = null // 注意:此时子类name未初始化 子类实例初始化块 子类构造方法 子类print方法: name = Child // 子类成员已初始化 ``` **输出解释**: - 静态代码块执行父类子类),只执行一次。 - 父类实例初始化块和构造方法先于子类执行。 - 在父类构造方法中调用`print()`时,由于多态,执行子类的`print()`方法,但此时子类的`name`尚未初始化(为`null`),显示"name = null"。 - 子类构造方法执行后,`name`被初始化,调用`print()`时显示正确值。 #### 关键注意事项 - **隐式`super()`调用**:如果子类构造方法没有显式调用`super(...)`或`this(...)`,编译器会自动添加`super()`调用父类无参构造方法[^3]。 - **多态行为**:方法调用基于对象的运行时类型(子类),但构造方法中调用方法时,子类成员可能未初始化,需避免。 - **继承链**:在多级继承中,顺序从最顶层父类开始,逐级向下执行[^1]。 - **静态成员**:静态代码块和变量在类加载时初始化,优先于实例创建。 通过这个顺序Java确保父类成员被正确初始化后,再处理子类逻辑,避免数据不一致[^4]。如果您有具体代码场景,我可以进一步分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值