java中init方法是怎样生成的?

本文深入探讨Java中的对象初始化过程,包括构造函数、实例变量初始化、实例初始化块等三种方法,解析init结构,以及this()和super()的使用限制。同时,文章通过实例展示了继承和初始化的顺序,并讨论了提前引用的两种合法方式。


一个类可以有多个<init>方法,但只能有一个<clinit>方法。需要注意的是方法只初始化本类中的实例变量

1 对象的三种初始化方法

  • 构造函数
  • 实例变量初始化(Instance variable initializers)
    class CoffeeCup {
     private int innerCoffee = 355; // "= 355" 就是初始化器 initializer
     // no constructor here
       // ...	
     }
    
  • 实例初始化块(instance initialization block or instance initializer)
    class CoffeeCup {
    private int innerCoffee;
    // 实例初始化块
    {
        innerCoffee = 355;
    }
    // no constructor here
    // ...
    }
    

2 init的结构:

在看结果之前,先看个例子:


public class Test {
	public static void main(String[] args)  {
		new X();//XYYX
	}
}

class X extends Y{
	{
		System.out.print("Y");
	}
	X(){
		System.out.print("X");
	}
}
class Y{
	{
		System.out.print("X");
	}
	Y(){
		System.out.print("Y");
	}
}

问?输出结果是什么?

  1. 调用其他构造函数。An invocation of another constructor
  2. 实例初始化,应该还包括实例初始化块。Instance variable initializers
  3. 当前构造函数的代码。The constructor body

回到这个例子,结果是:XYYX

3 this()和super()上不能try……catch

在this()和super()上try……catch是会炸的。如果你都catch了的话,你就可能忽略这个exception,进而返回不正确的对象。

4 遇到new时

继承和初始化顺序,遇到new时:
先分配空间(子类中的实例变量和父类中的实例变量)
初始化默认值
调用当前类的<init>(注意<init的结构)

5 不能这样提前引用。会拒绝编译

class VirtualCafe {
    private int chairsCount = 4 * tablesCount;
    private int tablesCount = 20;
    //...
}

6 提前引用的两种方式

但是有两种方式来进行提前引用。但是得到的值为默认值。
(1) 初始化实例变量时用一个函数,且这个函数用到了在这个实例变量之后定义的变量。

class VirtualCafe {
    private int chairsCount = initChairsCount();//这样chairsCount的值为0
    private int tablesCount = 20;
    private int initChairsCount() {
        return tablesCount * 4;
    }
    //...
}

(2)在父类中的构造函数中调用父类中被重写的方法(前提是这个父类的构造函数是由子类的构造函数调用的),就可以提前引用。如:


public class Test {
	public static void main(String[] args)  {
		new Coffee(1, 2.5f, true, true);
	}
}

//In source packet in file init/ex16/Liquid.java
class Liquid {
	private int mlVolume;
	private float temperature; // in Celsius
	public Liquid(int mlVolume, float temperature) {
		this.mlVolume = mlVolume;
		this.temperature = temperature;
		method();//如果是从子类调用这个方法,这里的method使用的是子类的方法
	}
	public void method() {
		System.out.println("我在父类");
	}
}

class Coffee extends Liquid {
	private boolean swirling;
	private boolean clockwise;
	public Coffee(int mlVolume, float temperature, boolean swirling, boolean clockwise)  {
		super(mlVolume, temperature);
		this.swirling = swirling;
		if (swirling) {
			this.clockwise = clockwise;
		} 
		System.out.println("success");
	}
	//父类的method被重写了
	public void method() {
		System.out.println(swirling);
	}
}

输入结果:
在这里插入图片描述
而不是:
我在父类
success

7 参考文献

https://www.javaworld.com/article/2076614/core-java/object-initialization-in-java.html?page=2

<think>好的,用户想知道Javainit方法的调用位置和时机。首先,我需要回忆一下Java加载和实例化的过程。根据之前学过的知识,Java的生命周期包括加载、验证、准备、解析、初始化、使用和卸载这几个阶段。其中初始化阶段会执行<clinit>方法,而实例初始化时会执行<init>方法。 用户提到的init方法应该指的是实例初始化方法,也就是构造函数和实例初始化块。在Java中,每次创建对象时,都会调用<init>方法,这包括实例变量初始化、实例初始化块以及构造函数的代码。需要确认的是,<init>方法是由编译器自动生成的,将实例变量初始化和实例块按顺序合并到构造函数中。 接下来,我需要参考用户提供的引用资料。引用[3]提到,在初始化阶段执行<clinit>方法处理静态变量和静态块,而<init>方法处理实例级别的初始化。引用[4]中的代码示例展示了父和子中实例块、变量初始化和构造函数的执行顺序,比如父的静态块先执行,然后是子的静态块,接着是父的实例块和变量初始化,最后是父构造函数,子同理。 用户的问题可能涉及到几个方面:1. init方法是什么时候调用的?2. 在继承关系中,父和子init方法执行顺序如何?3. 实例变量初始化和实例块在init方法中的顺序是怎样的?4. 多态情况下,比如在父构造函数中调用被子覆盖的方法,会如何影响初始化? 需要结合引用中的例子详细说明。例如,引用[1]中的Foo和Bar,当创建Bar实例时,父Foo的实例块i=2会在Foo的构造函数之前执行,而构造函数中调用的getValue方法由于多态会执行子Bar的方法,此时Bar的实例变量j可能还未初始化,导致返回0。这说明在父构造函数中调用可覆盖方法可能导致问题。 此外,引用[4]中的示例展示了具体的执行顺序:父静态块->子静态块->父实例块和变量初始化->父构造函数->子实例块和变量初始化->子构造函数。需要指出的是,实例变量初始化和实例块按代码顺序合并到<init>方法中,且在构造函数之前执行。 总结回答结构:首先解释init方法的定义,由编译器生成,包含实例变量初始化、实例块和构造函数代码。然后说明调用时机是在每次创建对象实例时。接着详细说明执行顺序,包括继承情况下的父和子顺序,以及实例变量和块之间的顺序。最后提到注意事项,如在构造函数中调用可覆盖方法可能导致未初始化的字段问题。 还需要检查是否有遗漏点,比如是否提到静态初始化与实例初始化的区别,或者多线程下的情况。根据引用[3],静态初始化由<clinit>处理,且是线程安全的,而实例初始化每个对象都有自己的init过程。 最后,确保回答引用正确的示例,如引用[1]和[4]中的代码,说明实际执行顺序和潜在问题。这样用户就能清晰理解init方法的调用时机和位置,以及相关注意事项。</think>在Java中,`<init>`方法指代**实例初始化方法**,由编译器自动生成,包含实例变量初始化、实例初始化块和构造函数的代码。其调用时机与对象创建过程密切相关,具体规则如下: --- ### 一、调用时机 1. **对象实例化时触发** 每次通过`new`关键字创建对象时,都会触发对应的`<init>`方法。例如: ```java Bar bar = new Bar(); // 触发Bar的<init>方法 ``` 2. **继承链中的顺序** 在继承关系中,子构造器会**隐式或显式调用父构造器**(通过`super()`),因此**父的`<init>`方法优先于子执行**。例如: ```java class Parent { Parent() {} } class Child extends Parent { Child() { super(); } } // 父<init>先执行 ``` --- ### 二、执行顺序[^4] 1. **同一内的执行顺序** 编译器将实例变量初始化和实例初始化块**按代码顺序合并到`<init>`方法中**,并**置于构造器代码之前**。例如: ```java class Foo { { System.out.println("实例块"); } // 步骤1 int i = test(); // 步骤2 Foo() { System.out.println("构造器"); } // 步骤3 } ``` 执行顺序为:**实例块 → 变量初始化 → 构造器**。 2. **继承场景下的完整流程** 以下列父子为例: ```java class Father { { System.out.print("(2)"); } // 父实例块 Father() { System.out.print("(3)"); } // 父构造器 } class Son extends Father { { System.out.print("(7)"); } // 子实例块 Son() { System.out.print("(8)"); } // 子构造器 } ``` 执行`new Son()`时,顺序为: **父实例块 → 父构造器 → 子实例块 → 子构造器**,输出结果为`(2)(3)(7)(8)`。 --- ### 三、关键注意事项 1. **多态对初始化的影响** 若父构造器中调用被子重写的方法(如引用[1]中的`getValue()`),此时子实例变量可能尚未初始化,导致返回默认值(如`int`型为0): ```java class Foo { Foo() { System.out.println(getValue()); } // 调用子重写的方法 protected int getValue() { return 1; } } class Bar extends Foo { int j = 1; // 初始化在父构造器之后执行 @Override protected int getValue() { return j; } } // 输出:0(因父构造器执行时,子j尚未初始化) ``` 2. **静态初始化与实例初始化的分离** 静态变量和静态块由`<clinit>`方法处理,在加载的初始化阶段执行,**早于所有`<init>`方法**(引用[3])。 --- ### 四、典型场景总结 | 场景 | 触发条件 | 执行内容 | |---------------------|-----------------------------|---------------------------------| | 首次加载 | 访问静态成员或`Class.forName` | 执行`<clinit>`(静态初始化) | | 创建对象实例 | `new`关键字 | 执行父→子的`<init>`方法链 | | 子重写方法被父调用 | 父构造器中调用可覆盖方法 | 可能访问未初始化的子字段 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值