`

类中静态块初始化顺序

阅读更多
类中变量的初始化顺序。变量这个词不一定准确,但本文的思路主要解释一个java类中初始化的一个顺序问题。例子:
public  class A 
{
public A(){
   System.out.println("constructor");
}
public static void main(String[] a)
{
     A tt=new A();	
A.d();//
 tt.d();//类方法既可以用类直接访问,也可以用对象访问,但是两者最终的实现却都是用类访问。
   s();
}
static {
System.out.println("static block");
}
public static void d()
{
    System.out.println("d");
}
public static void s()
{
    System.out.println("s");
}
}

输出结果:
static block
constructor
d
d
s
    这个结果通过程序运行很容易得出来,但是原因这个好像没有太多人去注意,这里我也是大概弄了个半懂。对于所有的类变量初始化语句和类型的静态初始化器被Java编译器收集在一起,放到一个特殊的方法中,对于类来说,这个方法称作类初始化方法;对于接口来说,它被称为接口初始化方法。在类和接口的Java class 文件中,这个方法被称为"<clinit >".通常的Java程序方法是无法调用这个<clinit>方法的。这中方法只能被Java虚拟机调用。专门用把类型静态变量设置为它们的正确初始值。
    
  初始化一个类包含两个步骤:
1)如果类存在直接超类的话,且直接超类还没有被初始化,就先初始化直接超类。这个与类的加载机制,双亲委托机制是相通的,类加载器的默认顺序是引导类加载器->标准扩展类加载器->系统类加载器->用户自定义加载器。在加载过程中必须保证类全名与类加载器的完全一致,才能够产生相同的class文件,否则将会出错。对于类加载机制为什么采用双亲委托机制,Java1.2开始使用双亲委托机制。为什么了?思考中!
2)如果一个类存在一个类初始化方法 <clinit>方法,就执行此方法。
    对于< clinit >方法并非所有的类都在他们的class文件中有一个这个方法。如果类没有任何声明类变量,也没有静态初始化语句,那么它就不会有< clinit ()>方法,这就是我们通常程序运行的顺序。但如果类变量是final变量,并且这种final类变量初始化语句采用编译时常量表态式,类也不会有< clinit >()方法。所以通常的说,< clinit ()>方法应该在类运行是最先加载。顺便讲一句类变量理论上放在jvm的方法区,但是类变量如果又是final型,并且使用一个编译型常量表达式,Java编译器把这样的字段解析成对常量的本地拷贝,该常量那么就存在引用者类的常量池中或者字节码流中,或者二者都有.举个列:
     static final int a=4;//这就是上面所讲的类变量,final,并且编译型常量;
   
     对于成员变量,不是static型的,理论上放在堆上,在new出来对象后,每个对象都拥有一份成员变量(非静态)。对于静态变量一个类拥有一个。扯远点,谈谈Java对象的分配。java虚拟机规范并没有具体给出java对象在堆中是如何表示。所以对于具体分配我们无法知道,不过给出了两种可能的设计情况。 一个可能的堆的设计是将堆分为两个部分:引用池和对象池。一个对象的引用就是指向引用池的本地指针。每一个引用池中的条目都包含两个部分:指向对象池中对 象数据的指针和方法区中对象类数据的指针。这种设计能够方便Java虚拟机堆碎片的整理。当虚拟机在对象池中移动一个对象的时候,只需要修改对应引用池中 的指针地址。但是每次访问对象的数据都需要处理两次指针。 如下图:



另一种堆的设计是:一个对象的引用就是一个指向一堆数据和指向相应对象的偏移指针。这种设计方便了对象的访问,可是对象的移动要变的异常复杂。如下图:




Java虚拟机里面的东西实在是比较复杂。涉及到太多东西,并且很多东西都是连在一起的,一通全通。对于static块的主动使用和被动使用,以后有时间再具体讨论。

      一旦一个类被初始化,它就随时都可能使用了,可以创建它的实例了。类的实例化有四种途径。明确的使用new操作符,调用class或者java.lang.reflect.constructor对象的newInstance()方法;调用现有对象的clone()方法。或者通过java.io.ObjectInputStream类的getObject()方法反序列化。这里谈谈Java的构造方法。Java编译器为它编译的每一个类都至少生产一个实例初始化方法,Java编译器都产生一个<init>()方法,这个方法针对每一个类的构造方法,对于每一个类的构造方法,Java编译器都产生一个<init>()方法,如果类没有明确声明任何构造方法,编译器默认产生一个无参数的构造方法,它仅仅调用超类的无参数构造方法。和其他的构造器方法一样,编译器在class文件中创建一个<init>()方法。对应它的默认构造方法。一个<init()>方法可能包含三种代码:调用另一个<init>()方法。实现对任何实例变量的初始化,构造方法体的代码。如果构造方法通过明确地调用同一个类中的另一个构造方法开始(一个this()调用)开始,

      补充一下,谈了静态初始化块,也要谈谈非静态初始化块。在上面的程序增加一个方法,顺序随意。这里只有一个的时候是随意,但是有多个初始化块的时候,初始化块之间是顺序的。如下:
{
System.out.println("non static block");
}
程序输出结果如下:
static block
non static block
constructor
d
d
s
很明显静态初始化块最先,接着非静态初始化块,接着构造器,但是我们通过反编译会发现一个结果。构造器字节码如下:
public A();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   7:   ldc     #3; //String  non static block
   9:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   12:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:  ldc     #5; //String constructor
   17:  invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   20:  return

      考虑到篇幅的原因,我没有全部把字节码拷过来,但是我们明显的看的出,在编译期。非静态初始化块的代码放到了构造器代码中,放到了<init()>方法中。这里要谈谈<init()>方法,前面谈到了<init()>方法,说到<init()>方法有3中可能,结合具体情况来讲讲。如果构造方法通过明确地调用同一个类中的另一个构造方法(一个this()调用)开始。它对应的<init()>方法由两部分组成:
1) 一个同类的<init()>方法调用。
2) 实现了对应的构造方法的方法体字节码。
举例说就是,有两个构造方法,一个用this(),调用另一个。
如果构造方法不是通过一个this()调用开始的,而且这个对象不是object,<init()>方法则由三部分组成:
1) 一个超类的<init()>方法的调用;
2) 任意实例变量初始化方法的字节码;
3) 实现了对应的构造方法的方法体的字节码
很明显我们的非静态初始化块就是放到<init()>方法中的。并且顺序在当前类的构造方法之前。

     综合来说静态语句块,是在初始化时完成的,构造器的调用是在实例化时调用的,所以静态语句在构造器实例化之前访问。虚拟机通过调用某个指定类的方法main启动,在可以调用main之前,必须初始化类。Main方法是虚拟机默认调用的指定静态方法。
     对于静态方法为何不能访问实例方法,原因:类方法”(“静态方法”)与“实例方法”在概念中的JVM上的区别:在调用类方法时,所有参数按顺序存放于被调用方法的局部变量区中的连续区域,从局部变量0开始;在调用实例方法时,局部变量0用于存放传入的该方法所属的对象实例(Java语言中的“this”),所有参数从局部变量1开始存放在局部变量区的连续区域中。 从效果上看,这就等于在调用实例方法时总是把“this”作为第一个参数传入被调用方法。静态方法是不传this参数(当前对象的引用)的,所以实例方法不能在静态方法中访问。Java中静态的理解有有两种,一是对方法而言,分为静态和实例方法。二是对应与Java的动态绑定机制而言,有静态绑定。这是c语言等应用的的静态绑定。我理解Java里面的继承,多态。类加载机制。内存分配回收,等等都是连在一起的。谈到多态,我们如果不理解继承,那也没什么意义。谈到继承,如果对类加载机制不理解,也不能很好的理解继承。等等,而这些知识都同Java的内存分配回收,有关。知识点不是孤立的。现在理解还比较散。

  • 大小: 18.2 KB
  • 大小: 16.9 KB
分享到:
评论

相关推荐

    类继承的初始化顺序类,继承的初始化顺序

    本篇文章将围绕“类继承的初始化顺序”这一主题展开,详细解析初始化过程中涉及的关键概念和技术细节。 ### 类继承的初始化顺序概述 在面向对象语言中(如Java、C#等),当创建一个继承自某个基类的子类对象时,会...

    java类中静态域、块,非静态域、块,构造函数的初始化顺序

    ### Java 类中静态域、块,非静态域、块,构造函数的初始化顺序 #### 一、概述 在 Java 编程语言中,类的初始化顺序对于理解程序的行为至关重要。特别是当涉及到静态域(静态变量)、非静态域(实例变量)、静态块...

    java面试题-类的初始化顺序.doc

    这个顺序表明,无论类之间的继承关系如何,初始化顺序始终是:静态变量和静态初始化块先于非静态成员。在创建对象时,父类的初始化先于子类。这是Java语言规范所规定的,确保在子类访问父类的静态或非静态成员时,...

    Java静态初始化块和对象初始化块

    总之,理解并有效地使用静态和对象初始化块是Java开发中的关键技能,它们可以帮助我们更好地控制类和对象的初始化过程,确保代码的高效性和正确性。在实际编程中,我们应该根据需求谨慎选择合适的初始化策略,以优化...

    java中类的初始化顺序

    ### Java中类的初始化顺序详解 #### 一、概述 在Java编程语言中,类的初始化是一个非常重要的概念。类的初始化涉及到多个方面,包括静态成员变量、实例成员变量、静态初始化块、实例初始化块以及构造函数等。本文...

    Java类继承初始化顺序

    总之,Java类继承初始化顺序涉及到静态和非静态初始化块、构造器的调用以及方法的覆盖。理解这些概念对于编写健壮的、易于维护的Java代码至关重要。在实际编程中,应合理利用继承特性,同时注意避免不必要的复杂性和...

    java程序初始化顺序

    在Java编程语言中,程序初始化的顺序是一个关键概念,它涉及到类加载、对象创建以及执行流程的安排。了解这些顺序对于编写高效、无错误的...在实际项目中,合理利用初始化顺序可以帮助我们优化资源加载,提高程序性能。

    关于Java静态成员变量和静态初始化块等的初始化顺序的详细介绍

    详细介绍了Java的静态成员变量、静态数据块、非静态成员变量和非静态成员变量等初始化顺序

    类初始化顺序示例讲解

    ### 类初始化顺序详解 本文将基于给定的Java示例代码深入探讨类初始化的顺序问题。这不仅是Java语言的关键特性之一,在其他面向对象语言(如C++、.NET)中也有相似的概念。理解这一概念有助于程序员更好地掌握面向...

    Java中的静态块初始化块及main方法.doc

    Java编程语言中,静态块(static block)和初始化块(instance initialization block)是两种特殊的代码块,它们在程序运行的不同阶段被执行,对于类和对象的初始化有着重要作用。同时,`main`方法是Java程序的入口...

    学习java静态数据初始化.doc

    例如,在 `Bowl` 类中,`b6` 和 `b9` 是两个静态变量,它们的初始化顺序是按照它们在类中的定义顺序进行的。 在 `main` 函数中,我们可以看到创建了两个 `Cupboard` 对象,每个对象的创建都会触发静态变量的初始化...

    JAVA面试题解惑系列——类的初始化顺序

    首先,需要了解Java类初始化的基本规则,即在类的静态变量、静态初始化块、变量、初始化块、构造器这五个部分中,它们的初始化顺序依次是:静态变量、静态初始化块、变量、初始化块、构造器。这个顺序在单个类中是...

    Java类加载器:静态变量初始化.docx

    在本文中,我们讨论了 Java 类加载器中的静态变量初始化机制,了解了静态变量的初始化顺序和类加载器的生命周期。通过对静态变量初始化机制的理解,我们可以更好地掌握 Java 语言的基础知识,并更好地应用 Java 语言...

    探究java的ClassLoader及类变量初始化顺序

    同时,掌握类变量初始化顺序可以避免因误解而导致的错误,特别是在多线程环境中,对静态变量的并发访问和初始化顺序的控制需要特别注意。 总之,深入理解Java的ClassLoader机制和类变量初始化顺序是提升Java编程...

    构造函数与静态块的执行顺序

    构造函数主要用于初始化对象的状态,而静态块则是在类加载到内存中时执行的一段代码,通常用于类级别的初始化。 ### 构造函数与静态块的基本概念 构造函数是一种特殊的方法,它与类同名,并没有返回类型,其主要...

    java代码的初始化顺序demo

    总之,Java代码的初始化顺序是类加载的必然过程,涉及到静态和实例初始化块、构造函数、成员变量初始化以及继承关系的影响。这个demo是学习和理解这些概念的重要工具,通过实际操作可以加深对Java内存管理和对象生命...

    java 静态非静态 字段方法 子类父类构造_初始化顺序!

    java 静态_非静态 字段_方法_代码块 子类父类构造_初始化顺序! 三个class 让你清清楚楚 第一个class java代码如下: package initialOrder; class Parent { // 静态变量 public static String p_StaticField...

    JAVA面试题解惑系列(一)——类的初始化顺序-JAVA程序员JAVA工程师面试必看.pdf,这是一份不错的文件

    ANSWER: 静态变量和静态初始化块的初始化顺序是最高的,因此 Father 类中的静态变量和静态初始化块将首先被初始化,接着是 Son 类中的静态变量和静态初始化块。然后是变量和初始化块,最后是构造器。 以下是一个...

    java类中元素初始化顺序详解

    在 Java 中,类的元素初始化顺序遵循以下规则: 1. **静态变量与静态初始化块**: 首先,Java 解释器会执行类中的静态变量初始化和静态初始化块。这些静态元素的初始化只会在类加载时执行一次,并且按照它们在源...

    JAVA类的初始化顺序文.pdf

    子类 --构造器 这个输出揭示了在继承关系中类的初始化顺序: 1. **静态成员和静态初始化块**:首先,会按照父类到子类的顺序初始化静态变量和执行静态初始化块。在上面的例子中,"父类 --静态变量" 和 "子类 --静态...

Global site tag (gtag.js) - Google Analytics