【Java18】初始化块

初始化块构造器、成员变量、成员方法一起,是唯四能在类中出现的成员。

  • 初始化块的作用和构造器类似,目的是对对象进行初始化操作;
  • 一个类中可以有多个初始化块;
  • 初始化块只有两种修饰方式:staticdefault。用static修饰的叫类初始化块,没有修饰符的叫实例初始化块
  • 类初始化块先于实例初始化块执行。同样的修饰符,位置在前的初始化块先执行。

虽然但是,同样修饰符的代码块没必要分成一个个小块块。因为代码块就是在创建Java对象的时候隐式执行的,而且总是全部执行。所以分成小块块和合并成一个大块块,没有区别。

实例初始化块
public class Person
{
  // 实例初始化块
  {
    var a = 6;
    if (a > 4)
    {
      System.out.println("Person实例初始化块:局部变量a的值大于4");
    }
    System.out.println("Person的实例初始化块");
  }
  // 第二个实例初始化块
  {
    System.out.println("Person的第二个实例初始化块");
  }
  public Person()
  {
    System.out.println("Person的无参数构造器");
  }
  public static void main(String[] args)
  {
    new Person();
  }
}

初始化块没有名字,所以无法通过显式手段调用它。它就在创建Java对象的时候隐式执行。

  1. 实例初始化块只在创建Java对象的时候隐式执行,且在构造器之前执行;
  2. 类初始化块在类初始化阶段隐式执行。
public class InstanceInitTest
{
    // 实例初始化块
    {
        a = 6;
    }
    int a = 9;
    public static void main(String[] args)
    {
        System.out.println(new InstanceInitTest().a);
    }
}

最终输出结果是9.

Why?

  1. 代码没有报错,第5行中的a的类型由第7行决定,这是编译时确定的,a是InstanceInitTest类的成员变量;
  2. 实力初始化块在其他语句之前执行。因此,a首先赋值为6,然后执行成员变量声明语句,再次被赋值为9。

Java创建对象的过程

  1. 第一次创建某个类的对象时,要先加载类(为类成员变量、类方法,以及类代码块分配内存);
  2. 其余时刻,首先为该对象的所有实例变量分配空间;
  3. 接着,对实例变量进行初始化。
  4. 初始化顺序是:
    1. 实例初始化块或声明实例变量时指定的初始值;
    2. 构造器中指定的初始值。

对上面的代码,如果调换初始化块和成员变量声明语句的顺序,结果会改变:

public class InstanceInitTest
{
    int a = 9;
    // 实例初始化块
    {
        a = 6;
    }
        public static void main(String[] args)
    {
        System.out.println(new InstanceInitTest().a);
    }
}

输出结果会变成6.

初始化块和构造器的异同

  1. 实力初始化块总是在构造器执行之前执行;
  2. 实例初始化块没法接受外部参数,因此它所能执行的初始化处理对于该类的所有对象都只能是相同的。

由此可以倒推出实例初始化块的作用:当有一段实例变量初始化代码完全相同,且不需要外部传参时,可以把这段代码放到实力初始化块中,方便代码阅读;

此外,多个构造器中不需要外部传参的操作,如果有相同的,也可以提取出来放到实例代码块中,以提升代码复用水平。

实例初始化块其实是个假象。使用javac编译Java类后,该类中的实例初始化块会消失:里面的代码会“回归”到每个构造器中,且位于构造器中代码的最前面。

在这里插入图片描述

类初始化块

类初始化块通常用于对类变量执行初始化处理。类初始化块不能对实例变量进行初始化。

类初始化块回向上追溯,先执行父类的类初始化块,依次往下。

class Root
{
    static{
        System.out.println("Root的类初始化块");
    }
    {
        System.out.println("Root的实例初始化块");
    }
}

class Mid extends Root
{
    static {
        System.out.println("Mid的类初始化块");
    }
    {
        System.out.println("Mid的实例初始化块");
    }
    public Mid()
    {
        System.out.println("Mid的无参数构造器");
    }
    public Mid(String msg)
    {
        // 通过this调用无参数构造器
        this();
        System.out.println("Mid的带参数构造器,参数值: " + msg);
    }
}

class Leaf extends Mid
{
    static {
        System.out.println("Leaf的类初始化块");
    }
    {
        System.out.println("Leaf的实例初始化块");
    }
    public Leaf()
    {
        // 通过super调用父类中有一个String参数的构造器
        super("疯狂Java讲义");
        System.out.println("执行Leaf的构造器");
    }
}

public class InstanceInitTest
{
    public static void main(String[] args)
    {
        new Leaf();
        new Leaf();
    }
}
  • 这段代码描述了三个类的继承关系,从父类到子类依次是:Root–>Mid–>Leaf
  • main中,第一次执行new Leaf(),是第一次加载类。此时系统中不存在Leaf类,所以要先加载并初始化Leaf类。这是就会
    • 先执行类初始化块。这部分又会遵循回溯机制,先执行父类的类初始化块,再往下依次执行。因此会出现先执行Root的类初始化块,然后是Mid的类初始化块,最后是Leaf的类初始化块。
    • 接着,执行实例初始化块和构造器。注意,前面说过实例初始化块其实就是构造器的一部分。所以这里会先执行Root的实例初始化块+构造器,然后是Mid的实例初始化块+构造器,最后是Leaf的实例初始化块+构造器。
  • 第二次执行new Leaf()时,就不会再进行类初始化了。

类初始化块和声明类变量(static修饰)的初始化代码和上面说过的实例初始化一样,按顺序执行。

public class StaticInitTest
{
    // 先执行类初始化块,将a赋值为6
    static {
        a = 6;
    }
    // 再将a类变量赋值为9
    static int a = 9;
    public static void main(String[] args)
    {
        // 下面的代码将输出9
        System.out.println(StaticInitTest.a);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值