小工具知识网

当前位置: 首页 > 行业知识 > Java中代码的执行顺序(附有3道例题)

行业知识

Java中代码的执行顺序(附有3道例题)

2026-10-26 17:09:43 来源:互联网转载

类加载详细过程

  Java代码的执行顺序遵循一定的规则和步骤,这些规则主要涉及类加载、初始化、静态块执行、实例化过程以及方法调用等方面。以下是Java代码执行顺序的详细说明:(如果不想看可以直接看下面简要说明和例题)

1. 程序入口:main() 方法

Java程序的执行始于指定主类(带有 public static void main(String[] args) 方法的类)中的 main() 方法。这是程序的起点,也是JVM(Java虚拟机)开始执行Java代码的地方。 

 2. 类加载与初始化

 类加载:当首次遇到一个类的引用时(如创建类的实例、访问类的静态成员或调用类的静态方法),JVM会加载该类。加载过程包括:

  • 验证:检查字节码的正确性。
  • 准备:为类的静态变量分配内存并赋予默认初始值(如整型为0,对象引用为 null)。
  • 解析:将符号引用转换为直接引用(如方法表、字段表的偏移量)。

    类初始化:当且仅当以下条件之一满足时,JVM会触发类的初始化:

    • 当new一个类的实例时。
    • 当访问类的静态变量(除了final且编译器可以确定其初始值的情况)或静态方法时。
    • 当子类初始化时,其父类未初始化,则先初始化父类。
    • 当使用反射API对类进行反射调用时。
    • 当初始化某个类的接口时,该接口未初始化 

      3. 静态初始化块与静态成员变量初始化

      在类初始化阶段,按照源代码中出现的顺序,执行以下操作:

      • 静态变量初始化:为所有声明时带有显式初始值的静态变量赋予指定值。
      • 静态初始化块(如果存在):按照在类定义中出现的顺序,依次执行静态初始化块中的代码。这些代码块通常用于集中处理静态资源的初始化或其他一次性设置。

        4. 实例初始化

        当创建类的实例时,执行以下步骤:

        • 分配内存:JVM为新对象分配内存空间。
        • 实例变量初始化:为所有声明时带有显式初始值的实例变量赋予指定值。
        • 构造函数调用:调用最合适的构造函数(根据提供的参数列表确定)。构造函数内部的代码按照编写顺序执行。如果构造函数中调用了超类构造函数(通过 super(...)),则先执行超类构造函数。
        • 实例初始化块(如果存在):按照在类定义中出现的顺序,依次执行实例初始化块中的代码。这些代码块用于初始化每个对象实例的通用设置,与构造函数互补。

          5. 方法调用

          对于非静态方法的调用,通常是在对象实例已经创建后,通过对象引用来完成。方法内部的执行顺序遵循语句的书写顺序,同时考虑到控制流(如条件、循环、异常处理等)的影响。

          总结

          Java代码的执行顺序大致如下:

          1.从main()方法开始。

          2.按需加载类并进行类初始化:

          • 静态变量初始化。
          • 静态初始化块执行。

            3.创建对象实例:

            • 分配内存。
            • 实例变量初始化。
            • 构造函数调用(可能递归调用超类构造函数)。
            • 实例初始化块执行。

              4.通过对象引用来调用非静态方法,方法内部代码按书写顺序执行。

              简单来说代码块执行的顺序:

              1. 静态代码块

              2. 构造代码块

              3. 构造器

              类加载的过程(简单版)

               类加载

                  JVM都知道基本数据类型的大小, 但是对于自定义的数据类型,JVM知不知道大小。因此,对于基本数据类型,JVM 知道占多大空间,能进行哪些操作。 但是对于我们新建的引用数据类型, 它不知道里面有多少变量,有多少方法。所以在JVM刚开始使用的时候,一定会进行一个操作: 认识该类型。    JVM认识这个类的过程,就称为类加载。
              public class Demo3 {    public static void main(String[] args) {            Student student = new Student();        int age = student.age;        String name = student.name;    }}
              创建对象的时候。 首先会进行的操作是类加载。类加载其实就是JVM了解, 里面有哪些属性,有哪些方法, 方法里面的代码是怎样的Student student = new Student(); 这行代码。 在内存上, 第一步,会需要在堆上开辟一个空间存储对象数据第二步, 在栈上创建一个引用,引用存储的是堆上的地址。类加载其实就是JVM认识一个类的过程。类加载要在创建对象之前进行,换句话说创建一个类的对象必然触发该类的类加载!

              例题

              第一题

              class SuperClass {    static int superStaticVar = 1; // 静态变量初始化    static {        System.out.println("SuperClass: Static block");    }    SuperClass() {        System.out.println("SuperClass: Constructor");    }}class SubClass extends SuperClass {    static int subStaticVar = 2; // 静态变量初始化    static {        System.out.println("SubClass: Static block");    }    {        System.out.println("SubClass: Instance initialization block");    }    SubClass(int value) {        super(); // 调用超类无参构造函数        System.out.println("SubClass: Constructor with value " + value);    }    public static void main(String[] args) {        System.out.println("Main method starts");        System.out.println("Accessing static variable: " + SubClass.subStaticVar); // 触发类初始化        SubClass instance = new SubClass(42); // 实例化SubClass对象        System.out.println("Main method ends");    }}

              执行上述代码时,控制台输出将按照以下顺序显示:

              Main method starts

              SuperClass: Static block

              SubClass: Static block

              Accessing static variable: 2

              SuperClass: Constructor

              SubClass: Instance initialization block

              SubClass: Constructor with value 42

              Main method ends

               

              1.程序从SubClass的main()方法开始执行。

              2.访问SubClass.subStaticVar时,触发SubClass及其超类SuperClass的类初始化:

                  先执行SuperClass的静态变量初始化,设置superStaticVar为1。

                  执行SuperClass的静态初始化块,打印"SuperClass: Static block"。

                  执行SubClass的静态变量初始化,设置subStaticVar为2。

                  执行SubClass的静态初始化块,打印"SubClass: Static block"。

                  输出访问的静态变量值:2。

              3.实例化SubClass对象:

                  分配内存给新对象。

                  执行SuperClass的构造函数,打印"SuperClass: Constructor"。

                  执行SubClass的实例初始化块,打印"SubClass: Instance initialization block"。

                  执行SubClass构造函数(传入参数42),打印"SubClass: Constructor with value 42"。

              4.打印"Main method ends",结束main()方法执行

              第二题

               public class Demo5 {        static {            System.out.println("Demo5类开始初始化步骤了!");        }        static Cat5 cat5 = new Cat5();        Dog5 dog5 = new Dog5();        public Demo5() {            System.out.println("Demo5 constructor");        }        public static void main(String[] args) {            System.out.println("hello world!");            Demo5 d = new Demo5();        }    }    class Cat5 {        static {            System.out.println("Cat5类开始初始化步骤了!");        }        static Dog5 dog5 = new Dog5();        public Cat5() {            System.out.println("Cat5  constructor");        }    }    class Dog5 {        static {            System.out.println("Dog5类开始初始化步骤了!");        }        static Demo5 demo = new Demo5();        public Dog5() {            System.out.println("Dog5  constructor");        }    }

              简述:程序会先进入主函数,进入后第一步会开始类加载,会开始执行各个class中的static语句。

              先按顺序加载类,第一个类是Demo4的类加载,开始执行Demo4中的static语句(static只会在类加载过程执行一次,之后不会再次执行),输出Demo4类开始初始化了。

              之后开始执行Static Cat5=new Cat5()的语句(这句会分开执行,会先执行Static Cat5,也就是JVM会先进行类加载,加载Cat5的类),进入到class Cat5,开始执行这里的static语句,第一个static语句是输出Cat5类开始初始化步骤了

              然后发现第二个static语句又是一个new语句,Static Dog5=new Dog5()的语句(和上个Cat5的new语句一样会先进行Dog5类的类加载)

              进入class Dog5来执行static语句,输出Dog5类开始初始化步骤了。Dog5类中第二个static语句是Demo4类的new语句,又会进行Demo4类的部分,Demo4类已经进行了类加载,static语句已经执行了,就不会再次执行了。那么第一个执行的语句就是Dog5的new语句,Dog5已经完成了类加载,就会按顺序执行其中的其他语句,也就是public中的语句,输出Dog5 constructor,之后返回Demo4中执行下一条语句,也就是public Demo4,输出Demo4 constructor。至此class Dog5完成了类加载

              返回上一步class Cat5,Cat5也完成了类加载,开始完成newCat5的后半句,执行class Cat5中的语句,只有public Cat5需要执行,输出Cat5 constructor。至此Demo4类加载完成

              类加载这也一过程完事,开始执行main函数中的语句,输出hello world!。

              开始执行Demo4的语句(Demo4已经完成了类加载,不需要再次执行static语句),开始执行Dog5的语句(类也加载完了),直接执行类中的输出语句,输出Dog5 constructor。然后再执行public的输出语句,输出Demo5 constructor。

              输出结果:

              Demo5类开始初始化步骤了!

              Cat5类开始初始化步骤了!

              Dog5类开始初始化步骤了!

              Dog5  constructor

              Demo5 constructor

              Dog5  constructor

              Cat5  constructor

              hello world!

              Dog5  constructor

              Demo5 constructor

               

              • static代码块,类加载的时候会直接执行。
              • static修饰的成员变量,在类加载的时候也会执行。
              • static修饰的方法,在类加载的时候,不会自动执行

                静态成员变量 

                • 和普通成员变量一样,都具有默认值(默认值和普通成员变量是一样的)

                • 静态成员变量属于类的,完全不需要创建对象使用。

                • 访问和使用静态成员变量不推荐使用"对象名.",而应该使用"类名."!

                • 静态成员变量的访问/赋值/使用都不依赖于对象, 而是依赖于类

                • 静态成员需要在类加载时期,完成准备,类加载结束就能够使用。所以访问类的静态成员,一定会触发该类的类加载。

                   内存及原理解析:

                  静态成员的访问并不依赖于创建对象,可以直接通过类名访问,其原因在于:

                  随着类加载完毕,静态成员就存在,并且能够使用了!

                  某个类的某个静态成员变量只有一份,且被所有对象共享,属于类,无需创建对象使用。

                   

                   注意:只存在静态成员变量,不存在"静态局部变量"

                  静态成员方法

                  • 无需创建对象就可以直接通过类名点直接调用。

                  • 同一个类中的static方法互相调用可以省略类名,直接用方法名调用。(这就是我们之前方法的调用)

                  • 一个类中,静态方法无法直接调用非静态的方法和属性,也不能使用this,super关键字(super后面会讲),静态的方法只能访问静态的。原因:静态方法调用的时候,有可能没有对象,没有对象普通成员就无法访问。

                  • 普通成员方法当中,既可以访问静态成员的, 也可以访问非静态成员。普通成员方法访问任意的

                  • 访问静态成员变量的时候,使用类名.变量名的形式访问,以示区别,增加代码可读性

                    第三题

                    public class ExerciseBlock {    static {        System.out.println("main方法静态代码块!");    }    {        System.out.println("main方法构造代码块!");    }    public static void main(String[] args) {        System.out.println("main方法开始执行!");        Star s = new Star(18,"马化腾");        System.out.println(Star.name);        System.out.println(s.age);    }}class Star{    {        age = 18;        Star.name = "杨超越";        System.out.println("我喜欢杨超越");    }    static String name = "王菲";    int age = 28;    static {        name = "杨幂";        System.out.println("我喜欢杨幂");    }    public Star(int age,String name) {        this(age);        System.out.println("age,name:构造器!");        Star.name = name;        Star.name = "刘亦菲";    }    public Star(int age) {        System.out.println("age:构造器!");        this.age = age;    }    public Star() {    }}

                    简述:先执行main函数,第一步永远是类加载,加载ExerciseBlock类,输出main方法静态代码块(类加载是懒加载用不到就不加载),然后执行主函数中的第一条语句(输出语句),输出main方法开始执行。然后创建s对象,开始加载Star类,执行static语句,name=王菲,然后name=杨幂米,之后输出我喜欢杨幂,之后开始进行new Star。开始传入参数执行public Star(age,name),第一句就是this(age),开始调用下面单参的构造器(先显示赋值,然后再开始构造器赋值)开始从上面class Star按顺序执行代码块,输出我喜欢杨超越, int age=28。开始单参构造器 ,输出age:构造器!,age=18。返回双参构造器,输出age,name:构造器!, 把name赋值成马化腾,在改成刘亦菲。所以最后输出的是刘亦菲和18。

                    附:构造器赋值顺序(下面详细解释)

                    1.默认初始化,具有默认值。

                    2.显示赋值,直接将值写在成员变量声明的后面。

                    3.构造器赋值

                    成员变量赋值中,构造器是最后执行的。

                    构造器的赋值顺序指的是在创建对象时,构造器内部对成员变量进行赋值的先后顺序。构造器内成员变量的赋值遵循以下规则:

                    1. 默认初始化:如果成员变量在声明时指定了默认值,那么首先会进行默认初始化。
                    2. 显式初始化:如果成员变量在声明时进行了赋值(即显式初始化),那么在构造器调用前,这些赋值会被执行。
                    3. 构造器中赋值:在构造器的主体中,按照代码书写的顺序依次对成员变量进行赋值。
                    public class User {    // 默认初始化为 null    private String name;    // 显式初始化为 0    private int age = 0;    // 显式初始化为 "Unspecified"    private String occupation = "Unspecified";    public User(String newName, int newAge) {        // 构造器中赋值,先赋值 name,再赋值 age        name = newName;        age = newAge;        // 在构造器中更改 occupation 的值        occupation = "Developer";    }    public void displayUserDetails() {        System.out.println("Name: " + name);        System.out.println("Age: " + age);        System.out.println("Occupation: " + occupation);    }    public static void main(String[] args) {        User user = new User("Alice", 30);        user.displayUserDetails();    }}
                    Name: AliceAge: 30Occupation: Developer

                     解析赋值顺序:

                    1. 默认初始化:成员变量 name 被默认初始化为 null。
                    2. 显式初始化:
                    • 成员变量 age 被显式初始化为 0。
                    • 成员变量 occupation 被显式初始化为 "Unspecified"。

                           3.构造代码块赋值 (下面详细讲述)

                           4.构造器中赋值:

                      • 构造器 User(String newName, int newAge) 被调用时,首先将传入参数 newName 赋给 name,此时 name 的值变为 "Alice"。
                      • 接着将传入参数 newAge 赋给 age,此时 age 的值变为 30。
                      • 最后,将 occupation 的值显式改为 "Developer"。

                             5.显示用户详情:

                        • 调用 displayUserDetails() 方法,输出当前 User 对象的 name、age 和 occupation 的值,即按上述顺序赋值后的结果。

                          综上所述,构造器中成员变量的赋值顺序遵循默认初始化 → 显式初始化 → 构造器中赋值的规则。在这个示例中,成员变量 name、age 和 occupation 分别经历了默认初始化、显式初始化和构造器中赋值的过程,最终输出了经过构造器赋值后的值。

                           构造代码块

                           定义在类的成员位置,使用以下声明方式声明的代码块,称之为构造代码块。

                          作用是随着构造器的执行,用于在创建对象过程中,给成员变量赋值

                          //成员位置{// 局部位置}//成员位置

                           构造代码块内部属于局部位置,在里面定义变量,就是一个仅在构造代码块中生效的局部变量。

                          public class Demo2 {    int a = 20;    // 在这个位置定义块,就是构造代码块    {        // 这个a,仅在局部生效。        int a = 10;        System.out.println(a);    }}

                          这里总结给成员变量赋值的几种方式(创建对象过程中):

                          • 默认初始化,具有默认值

                          • 显式赋值

                          • 构造代码块

                          • 构造器

                            学习对象中成员变量的赋值,和赋值顺序要遵循"掐头去尾"的原则:

                            1. 头:默认初始化,具有默认值,在对象结构存在于对象中,对象中的成员变量就已经具有了默认值。

                              77777我们程序员所有能干预的赋值方式,都是在默认初始化的基础上进行的。

                            2. 尾:构造器,构造器在整个对象的成员变量赋值过程中,处在最后的阶段,最后被执行。

                            明确以上两点后,我们现在只需要研究显式赋值和构造代码块的赋值顺序,

                            显式赋值和构造代码块的执行顺序,并不是固定的,而是按照代码的书写顺序去执行的:

                            1. 这两个结构,谁写在代码书写顺序的上面,谁就先执行。

                            2. 后执行结构的结构,自然会覆盖先执行结构的结果。

                            通过查看反编译class文件(通过IDEA),我们发现编译后的代码中并不存在构造代码块的结构,而是:

                            直接将成员变量的显式赋值和构造代码块中的代码智能地加入,类所有的构造器中的前几行:

                            所谓智能是为了保证:成员变量的显式赋值和构造代码块,按照代码的书写顺序从上到下执行!

                            于是,我们可以得出以下结论:

                            1. 使用new对象的方式创建对象,不论使用哪个构造器,构造代码块都会随之执行。

                            2. 构造器是每一次new对象都会执行一次,所以构造代码块也会随之执行一次。

                            3. 构造代码块中的代码要放入构造器的首几行,所以在同一个类中,构造代码块总是先于它的构造器而执行。

                            创建对象过程中的执行顺序

                            new对象过程中,各种结构的执行顺序:

                            1. 对象结构存在后就进行默认初始化,所有成员变量都具有默认值后,再开始其余赋值操作

                            2. 找到new对象的那个构造器

                                              如果它的首行显式地调用了另一个构造器this(实参)

                                            ( 注:显式调用构造器目前指的是this调用自身构造器,其它场景这里先不考虑)

                            那么程序会先跳转到那个构造器,但是不会立刻执行,而是:

                            1. 按照类中构造代码块和显式赋值的代码书写顺序,从上到下执行其中的代码,执行完毕后:

                            2. 跳转回this语句要指示执行的构造器,执行其中的代码,然后:

                            3. 跳转回new对象构造器,执行完毕后,创建对象结束。

                            注:整个过程中,构造代码块和显式赋值的代码只会执行一次,不会执行多次!!

                                               如果它的首行没有显式调用另一个构造器   

                            那么会先从上到下执行构造代码块和显式赋值代码

                            执行完毕后:跳转回new对象构造器,执行完毕后,创建对象结束。

                            注:整个过程中,构造代码块和显式赋值的代码只会执行一次,不会执行多次!!

上一篇:微商城虚拟卡密攻略,轻松掌握购物秘籍!

下一篇:怎么加入dream服务器?