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

您所在的位置:网站首页 祭祖的先后顺序 Java中代码的执行顺序(附有3道例题)

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

2024-07-17 01:52| 来源: 网络整理| 查看: 265

类加载详细过程

  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.通过对象引用来调用非静态方法,方法内部代码按书写顺序执行。

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

静态代码块

构造代码块

构造器

类加载的过程(简单版)  类加载 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.构造器赋值

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

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

默认初始化:如果成员变量在声明时指定了默认值,那么首先会进行默认初始化。显式初始化:如果成员变量在声明时进行了赋值(即显式初始化),那么在构造器调用前,这些赋值会被执行。构造器中赋值:在构造器的主体中,按照代码书写的顺序依次对成员变量进行赋值。 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: Alice Age: 30 Occupation: Developer

 解析赋值顺序:

默认初始化:成员变量 name 被默认初始化为 null。显式初始化: 成员变量 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); } }

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

默认初始化,具有默认值

显式赋值

构造代码块

构造器

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

找到new对象的那个构造器

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

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

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

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

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

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

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

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

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

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

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3