`

类中静态块初始化顺序

阅读更多
类中变量的初始化顺序。变量这个词不一定准确,但本文的思路主要解释一个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静态成员变量和静态初始化块等的初始化顺序的详细介绍

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

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

    java类中静态域、块,非静态域、块,构造函数的初始化顺序,通过本例子,能够清楚了解到java类的初始化顺序

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

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

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

    我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)&gt;(变量、初始化块)&gt;构造器。我们也可以通过下面的测试代码来验证这一点:

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

    对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)&gt;(变量、初始化块)&gt;构造器

    Java类中各成员初始化的顺序

    了解java中类各个成员的初始化顺序是非常重要的,这样你可以对类有全局的认识。不说太多,直接看下面的例子 class Father { static{ System. out.println("父类静态代码块初始化" ); } { System. out.println...

    类实例化顺序讲解 附阿里巴巴一道笔试题

    2.3.1 方法详解2.3.2 静态变量和静态代码块初始化顺序2.3.3 继承中类初始化分析三 对象创建和初始化过程3.1 new对象底层字节码指令分析3.2 对象初始化过程详解【重点】3.2.2 实例变量初始化顺序分析3.2.3 继承中实例...

    简单了解java中静态初始化块的执行顺序

    主要介绍了简单了解java中静态初始化块的执行顺序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    【Java高频面试题】–类的初始化过程以及实例的初始化过程

    1.1 类的初始化过程 要创建一个类的实例,必须加载和初始化该类。 main()方法所在的类,会被优先加载并初始化 子类初始化前,会先加载并初始化它的父类 初始化一个类,其实质上就是执行了()方法 ()方法包含了,静态...

    java面试题静态加载顺序构造方法

    4.知道了static的作用,那么X类被加载,那么就会先执行X类的静态属性和静态语句块(static),执行先后顺序看谁在前面就先执行谁。只在此时执行,以后都不会。 5.所以一个输出结果为tttt,没问题了吧。 6.X类的...

    java对象初始化顺序验证示例

    以下这段小程序对调用对象构造函数时,父类构造函数、成员变量初始化函数,以及非静态初始化块调用顺序进行验证,不考虑静态成员及静态初始化块

    Java初始化顺序

    1 无继承情况下的Java初始化顺序: class Sample { Sample(String s) { System.out.println(s); } Sample() { System.out.println(Sample默认构造函数被调用); } } class Test{ static Sample sam=new...

    JAVA面试题解惑系列

    我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)&gt;(变量、初始化块)&gt;构造器。我们也可以通过下面的测试代码来验证这一点:

    有关static block静态代码块和单态设计模式

    静态代码块(static block ),不包含在任何方法体中当类被载入时,自动执行静态代码块,且只被执行一次经常用于类属性的初始化。 这是网上通行的关于静态代码块的定义。 在Java中,类装载器把一个类装入Java虚拟机...

    关于JVM的总结

    而在初始化阶段则通过程序制定的主管计划去初始化变量和其他资源,从另一个角度理解就是 执行类构造器的()方法 .()方法是由编译器自动收集类中的所有变量的复制动作和静态语句中的语句合并产生的. 静态语句块中只能...

    spring-experiement

    该项目测试了运行静态块和Spring bean初始化的顺序 实验:静态块可以访问自动装配的对象吗? 先要明白Static块是什么时候运行的 静态块是在类第一次被加载的时候运行 spring的对象初始化是在什么时候运行的 假设bean...

    杰普学习corejava总结笔记

    类加载,同时初始化类中静态的属性(赋默认值) 2.执行静态代码块 3.分配内存空间,同时初始化非静态的属性(赋默认值) 4.调用父类构造器(注意调用父类构造器之前已经给父类的非静态的属性显示赋值,如果有显示赋值的话...

    大数据面试题.pdf

    12)Java中Class.forName和ClassLoader.loadClass的区别 Class.forName("xx.xx")等同于Class.forName("xx.xx",true,CALLClass.class.getClassLoader()),第⼆个参数(bool)表⽰装载类的时候是否 初始化该类,即调⽤类...

    C++ Primer第四版【中文高清扫描版】.pdf

    12.4.5 类成员的显式初始化 396 12.5 友元 396 12.6 static类成员 398 12.6.1 static成员函数 400 12.6.2 static数据成员 400 小结 403 术语 403 第13章 复制控制 405 13.1 复制构造函数 406 13.1.1 合成的复制构造...

    疯狂JAVA讲义

    5.9.3 静态初始化块 162 5.10 本章小结 165 本章练习 165 第6章 面向对象(下) 166 6.1 基本数据类型的包装类 167 6.2 处理对象 170 6.2.1 打印对象和toString方法 170 6.2.2 ==和equals比较运算符 172 6.3...

Global site tag (gtag.js) - Google Analytics