关于静态代码块的执行顺序,很简单的一道题,应该所有人都会吧?

Posted 跟着小苏不加班

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于静态代码块的执行顺序,很简单的一道题,应该所有人都会吧?相关的知识,希望对你有一定的参考价值。

之前面试的时候做过代码块和构造方法的执行顺序,当时虽然半蒙半猜作对了,但是对这个还不是特别的了解,所以就想看看今天能不能彻底搞懂,即帮助大家、也帮助自己。

简单题

在 Java 中有静态代码块、非静态代码块(构造代码块)、普通代码块,还有静态变量、成员变量,来做道题看看你对这些代码块和变量赋值的执行顺序是否真的了解了?

上代码

public class Address {
    private String province;

    public Address(String province) {
        this.province=province;
        System.out.println("-- Address 的构造方法:province="+this.province+"");
    }

}
public class User {
    private static final Address address1=new Address("guangdong");
    private Address address2=new Address("guangxi");

    static {
        System.out.println("-- User 的静态代码块1--");
    }

    {
        System.out.println("-- User 的非静态代码块1--");
    }

    public User() {
        {
            System.out.println("-- User 的普通代码块1--");
        }
        System.out.println("-- User 的构造方法--");
        {
            System.out.println("-- User 的普通代码块2--");
        }
    }

    private static final Address address3=new Address("hubei");
    private Address address4=new Address("hunan");

    {
        System.out.println("-- User 的非静态代码块2--");
    }

    static {
        System.out.println("-- User 的静态代码块2--");
    }

}
public class StaticCodeTest {
    /**
     * 静态代码块、非静态代码块、构造方法、静态变量、成员变量的运执行顺序。
     */
    public static void main(String[] args) {
        new User();
    }
}

可以先写下自己的答案哦。

来对答案吧!

-- Address 的构造方法:province=guangdong
-- User 的静态代码块1--
-- Address 的构造方法:province=hubei
-- User 的静态代码块2--
-- Address 的构造方法:province=guangxi
-- User 的非静态代码块1--
-- Address 的构造方法:province=hunan
-- User 的非静态代码块2--
-- User 的普通代码块1--
-- User 的构造方法--
-- User 的普通代码块2--普通代码块2--

可以看到,静态代码块和静态变量的赋值是最快执行的,执行的顺序是按照在类中写的顺序来执行的,然后就是 User 的成员变量的赋值、User 的非静态代码块、最后再是构造方法,普通代码块实际上跟普通的代码差不多了哈哈。

好了,今天就到这里了,<typo data-origin="下课" ignoretag="true">下课</typo>。

复杂一丢丢的题

啊喂,就这么简单?我都不好意思发出来了,上面的题目几乎学过 Java 的都能做出来,来来来,加点难度,不是还有一个继承嘛,我给你们加上。

public class SonUser extends User {

    private static final Address address1=new Address("beijing");
    private  Address address2=new Address("nanjing");

    static {
        System.out.println("-- SonUser 的静态代码块1--");
    }

    {
        System.out.println("-- SonUser 的非静态代码块1--");
    }
    public SonUser(){
        {
            System.out.println("-- SonUser 的普通代码块1--");
        }
        System.out.println("-- SonUser 的构造方法--");
        {
            System.out.println("-- SonUser 的普通代码块2--");
        }
    }
    {
        System.out.println("-- SonUser 的非静态代码块2--");
    }

    static {
        System.out.println("-- SonUser 的静态代码块2--");
    }
}
public class StaticCodeTest {
    public static void main(String[] args) {
        new SonUser();
    }
}

这个答案是有点长了。不先写好答案也行吧,来一起看看。

-- Address 的构造方法:province=guangdong
-- User 的静态代码块1--
-- Address 的构造方法:province=hubei
-- User 的静态代码块2--
-- Address 的构造方法:province=beijing
-- SonUser 的静态代码块1--
-- SonUser 的静态代码块2--
-- Address 的构造方法:province=guangxi
-- User 的非静态代码块1--
-- Address 的构造方法:province=hunan
-- User 的非静态代码块2--
-- User 的普通代码块1--
-- User 的构造方法--
-- User 的普通代码块2--
-- Address 的构造方法:province=nanjing
-- SonUser 的非静态代码块1--
-- SonUser 的非静态代码块2--
-- SonUser 的普通代码块1--
-- SonUser 的构造方法--
-- SonUser 的普通代码块2--SonUser 的普通代码块2--

可以看到有关于静态的(包含父类和子类)基本上都是在一开始就执行了,静态变量以及静态代码块,只不过父类的会比子类的更快执行,在执行完静态代码块后,就会去执行父类的变量

静态变量赋值和静态代码块,可以看作是一段静态代码的打包,按照代码顺序打包到一起,而成员变量的赋值非静态代码块也打一个包,前者我们可以称作静态代码包,后者我们可以称作非静态代码包。

在类加载时就执行静态代码包,这个只执行一次;在每次声明(new)一个对象的时候就会执行非静态代码包,然后再执行构造方法,注意这里的非静态代码包会每次都执行,构造方法就看每次调用的是那个,就执行那个构造方法。

一起来看看 Byte Code

这里的静态代码包和非静态代码包虽名称是我编的,但是确实存在这个东西。

让我们看看 User 类的字节码。

public class test/other/entity/User {

  // compiled from: User.java

  // access flags 0x1A
  private final static Ltest/other/entity/Address; address1

  // access flags 0x2
  private Ltest/other/entity/Address; address2

  // access flags 0x1A
  private final static Ltest/other/entity/Address; address3

  // access flags 0x2
  private Ltest/other/entity/Address; address4

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 25 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 10 L1
    ALOAD 0
    NEW test/other/entity/Address
    DUP
    LDC "guangxi"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTFIELD test/other/entity/User.address2 : Ltest/other/entity/Address;
   L2
    LINENUMBER 22 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的非静态代码块1--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 36 L3
    ALOAD 0
    NEW test/other/entity/Address
    DUP
    LDC "hunan"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTFIELD test/other/entity/User.address4 : Ltest/other/entity/Address;
   L4
    LINENUMBER 39 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的非静态代码块2--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 27 L5
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的普通代码块1--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L6
    LINENUMBER 29 L6
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的构造方法--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L7
    LINENUMBER 31 L7
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的普通代码块2--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L8
    LINENUMBER 33 L8
    RETURN
   L9
    LOCALVARIABLE this Ltest/other/entity/User; L0 L9 0
    MAXSTACK = 4
    MAXLOCALS = 1

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 9 L0
    NEW test/other/entity/Address
    DUP
    LDC "guangdong"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTSTATIC test/other/entity/User.address1 : Ltest/other/entity/Address;
   L1
    LINENUMBER 18 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的静态代码块1--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 35 L2
    NEW test/other/entity/Address
    DUP
    LDC "hubei"
    INVOKESPECIAL test/other/entity/Address.<init> (Ljava/lang/String;)V
    PUTSTATIC test/other/entity/User.address3 : Ltest/other/entity/Address;
   L3
    LINENUMBER 43 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "-- User 的静态代码块2--"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 44 L4
    RETURN
    MAXSTACK = 3
    MAXLOCALS = 0
}

确实是吧,静态代码包就是 static <clinit>()V,而非静态代码块就是 public <init>()V。而 clinit 方法就是在这个类被加载进内存就会执行的方法,init 方法就是每次调用构造方法会调用的方法。

我看到了这一行代码INVOKESPECIAL java/lang/Object.<init> ()V,嗯哼,就是这个方法会去触发父类的构造方法啊,索嘎,所以子类会调用父类的构造方法,然后就会触发父类的加载,然后就触发了父类的静态代码包,父类的构造方法又会在第一行去触发爷类的构造方法,然后又触发爷类的加载,然后就触发了爷类的静态代码包,然后爷类。。。。。无限循环到 Object 类,就开始执行爷类的非静态代码包、父类的非静态代码包、子类的。。。

可以和递归一起联想。

我也想把父类的构造方法放在最下面,但是却只看到了 IDEA 的无情爆红,当然也是执行不了的。如果不加 super() 那就会默认加一个空参数的父类构造方法,也就是super()。

以上是关于关于静态代码块的执行顺序,很简单的一道题,应该所有人都会吧?的主要内容,如果未能解决你的问题,请参考以下文章

Java静态代码块的作用及执行顺序

JAVA静态代码块的作用及执行顺序

Java的静态变量,成员变量,静态代码块,构造块的加载顺序

关于静态块静态属性构造块构造方法的执行顺序

java父子类的初始化顺序--个人总结

java中静态代码块,非静态代码块,构造函数执行顺序