原贴主题: ^_^散分了~~顺便纠正一个容易犯的小错误^_^
原贴地址: http://community.csdn.net/Expert/TopicView.asp?id=5478850
首先声明:因为我前几天在“考研论坛”与一群研究生争论非技术问题,但最后很多在那里的贴子
被封杀,所以我不想在CSDN争论非技术问题。我知道你最初发贴的本意是好的,也许是我指出你错误的语气让你觉得有点刺耳,
我很抱歉,我回复别人的贴子从来不在乎别人给的分数,即使是0分贴,我有兴趣
我也会回复整整一大篇。非技术问题没有对与错,但技术问题在遵循技术规范的前提下总有对与错,
个人技术水平总是在不断增长的,犯技术错误并不可耻,
有勇气承认技术错误更让人敬佩。另外已经结了贴的问题,如果还想讨论,最好重新发贴,不然会很难注意到的。
继续问题的讨论:
讨论前提:我水平有限,没办法从hotspotJVM源码级别讨论问题,
只在遵循以下两个技术规范:
1.The JavaTM Virtual Machine Specification Second Edition
(http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html)
2.The JavaTM Language Specification, Third Edition,
(http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html)
并结合程序例子的方式讨论问题(其间还会涉及javac相关的东西)
你最初的观点是:
==========================================
静态块里的代码不一定是第一次被加载时执行,
确切的说应该是第一次实例化的时候执行~
==========================================首先“类的实例化”与“类的加载、连接与初始化”完全是
两个不同的阶段,只有“类的加载、连接与初始化”都完成了
才能进行“类的实例化”。另外“类的初始化”与“类的实例化”是完全不同的两个东西,
从javac编译后的class文件的角度来说:
“类的初始化”对应的是“<clinit>”这个方法
“类的实例化”对应的是“<init>”  这个方法还有一点,静态块里的代码也不是在类被加载时执行,而是在类被初始化时执行的。类的加载简单说只是把class文件加载到内存,并没有执行任何代码。
类的初始化由JVM调用“<clinit>”这个方法来完成。javac会把源代码中的static非final字段赋值语句以及静态块
放入“<clinit>”这个方法里。
(关于javac的细节请参考:OpenJDK javac(https://openjdk.dev.java.net/)
com.sun.tools.javac.jvm.Gen类的normalizeDefs方法的源码
)
有关“类的加载、连接与初始化”的内容请参考
The JavaTM Virtual Machine Specification Second Edition 
第5 Loading, Linking, and Initializing节或参考<<深入JAVA虚拟机>>“第7章 类型的生命周期”(P153-P176)
(讲JVM的书非常非常非常的少,我只看过这一本,而且讲得确实不错)下面分析你所给的例子:1,测试类:
package com.daniel.test;public class TestStatic {
  static{
    System.out.println("执行静态块!");
  }
  public void print(){
    System.out.println("执行TestStatic.print()!");
  }
}
2,主类:
package com.daniel.test;
public class StaticTest {
  public static void main(String[] args) throws Exception {
    StaticTest st = new StaticTest();
    System.out.println("准备加载com.daniel.test.TestStatic...");
    Class clazz = st.getClass().getClassLoader().loadClass("com.daniel.test.TestStatic");
    System.out.println("加载com.daniel.test.TestStatic成功!");
    System.out.println("准备实例化com.daniel.test.TestStatic...");
    TestStatic ts = (TestStatic)clazz.newInstance();
    System.out.println("实例化com.daniel.test.TestStatic成功!");
    ts.print();
  }
}

解决方案 »

  1.   


    1: 把你的com.daniel.test.StaticTest改一下:
    package com.daniel.test;
    public class StaticTest {
      //下面的代码是我加的
      ////////////////////开始////////////////////
      static{
        System.out.println("执行StaticTest的静态块!");
      }  StaticTest(){
        System.out.println("实例化com.daniel.test.StaticTest成功!\r\n");
      }
      ////////////////////结束////////////////////  public static void main(String[] args) throws Exception {
    /*注释掉
        StaticTest st = new StaticTest();
        System.out.println("准备加载com.daniel.test.TestStatic...");
        Class clazz = st.getClass().getClassLoader().loadClass("com.daniel.test.TestStatic");
        System.out.println("加载com.daniel.test.TestStatic成功!");
        System.out.println("准备实例化com.daniel.test.TestStatic...");
        TestStatic ts = (TestStatic)clazz.newInstance();
        System.out.println("实例化com.daniel.test.TestStatic成功!");
        ts.print();
    */
      }
    }结果:
    =======================================
    执行StaticTest的静态块!
    =======================================说明:即使我还没开始想要对com.daniel.test.StaticTest进行实例化,
    StaticTest的静态块也同样被执行了。
    2: 再改成这样
    package com.daniel.test;
    public class StaticTest {
      //下面的代码是我加的
      ////////////////////开始////////////////////
      static{
        System.out.println("执行StaticTest的静态块!");
      }  StaticTest(){
        System.out.println("实例化com.daniel.test.StaticTest成功!\r\n");
      }
      ////////////////////结束////////////////////  public static void main(String[] args) throws Exception {
    System.out.println("准备实例化com.daniel.test.StaticTest...");//我加上的    StaticTest st = new StaticTest();
        System.out.println("准备加载com.daniel.test.TestStatic...");
        Class clazz = st.getClass().getClassLoader().loadClass("com.daniel.test.TestStatic");
        System.out.println("加载com.daniel.test.TestStatic成功!");
        System.out.println("准备实例化com.daniel.test.TestStatic...");
        TestStatic ts = (TestStatic)clazz.newInstance();
        System.out.println("实例化com.daniel.test.TestStatic成功!");
        ts.print();
      }
    }结果:
    =======================================
    执行StaticTest的静态块!
    准备实例化com.daniel.test.StaticTest...
    实例化com.daniel.test.StaticTest成功!准备加载com.daniel.test.TestStatic...
    加载com.daniel.test.TestStatic成功!
    准备实例化com.daniel.test.TestStatic...
    执行静态块!
    实例化com.daniel.test.TestStatic成功!
    执行TestStatic.print()!
    =======================================说明:com.daniel.test.StaticTest类的静态块还是在实例化前执行,输出结果:
    =======================================
    “准备加载com.daniel.test.TestStatic...
    加载com.daniel.test.TestStatic成功!
    准备实例化com.daniel.test.TestStatic...
    执行静态块!
    实例化com.daniel.test.TestStatic成功!
    执行TestStatic.print()!”
    =======================================
    也说明了“类的加载、连接(隐含的,看不见)与初始化”都完成了
    才到“类的实例化”3.把com.daniel.test.TestStatic与com.daniel.test.StaticTest都改一下
    package com.daniel.test;
    public class TestStatic {
      public static int field=10;
      static{
        System.out.println("通过引用静态字段执行TestStatic静态块!");
      }
    }package com.daniel.test;
    public class StaticTest {
      public static void main(String[] args) throws Exception {
    System.out.println("TestStatic.field="+TestStatic.field);
      }
    }结果:
    =======================================
    通过引用静态字段执行TestStatic静态块!
    TestStatic.field=10
    =======================================
    3.把com.daniel.test.TestStatic与com.daniel.test.StaticTest再改一下
    package com.daniel.test;
    public class TestStatic {
      public static void print() {
        System.out.println("调用TestStatic类的静态方法print()!");
      }
      static{
        System.out.println("通过调用静态方法执行TestStatic静态块!");
      }
    }package com.daniel.test;
    public class StaticTest {
      public static void main(String[] args) throws Exception {
    TestStatic.print();
      }
    }结果:
    =======================================
    通过调用静态方法执行TestStatic静态块!
    调用TestStatic类的静态方法print()!
    =======================================
      

  2.   


    回答你下面的话:
    =========================================================================
    to :   KRplusSRequalGOD(狂人+善人=神) ( ) 信誉:100  2,主类:
    package com.daniel.test;
    public class StaticTest {
      public static void main(String[] args) throws Exception {    StaticTest st = new StaticTest();
        System.out.println("准备加载com.daniel.test.TestStatic...");
        Class clazz = st.getClass().getClassLoader().loadClass("com.daniel.test.TestStatic");
        System.out.println("加载com.daniel.test.TestStatic成功!");
        System.out.println("准备实例化com.daniel.test.TestStatic...");
        TestStatic ts = (TestStatic)clazz.newInstance();
        System.out.println("实例化com.daniel.test.TestStatic成功!");
        ts.print();
        TestStatic.printStatic();//这是我加的
      }
    }试试上面代码~~~
    你是人为的把TestStatic.printStatic(),提到前面执行,
    你如果把它写到后面呢,你可以自己看看结果~
    即过很明显,static块仍就不是先执行的~~
    你的说法不正确呀~~~
    因为按照你的说法,
    TestStatic.printStatic()不管放到哪,static都要先执行才对!另外还有用
    public static int a=10;
    这一类的来说明load class的时候就执行静态块,
    但你们用的例子也是把System.out.println(StaticTest.a);
    放到load class的前面执行的,犯了上面同样的错误!按照你们的说法,System.out.println(StaticTest.a);
    不管放到那都要先执行static块的代码的,
    但是你们测试了把这段代码放到后面执行的结果了吗!?
    =========================================================================请参考
    The JavaTM Virtual Machine Specification Second Edition 
    第5.5 Initialization节
    或参考<<深入JAVA虚拟机>>第154页,从你的回复中看出你还没有弄清楚类初始化是在什么时候进行的?
    同一个类只被相关的ClassLoader加载一次,静态块只会执行一次。
    调用TestStatic.printStatic()或StaticTest.a前
    会先要求把TestStatic,StaticTest类加载、连接与初始化了,
    你再st.getClass().getClassLoader().loadClass实际上是多余的,
    根本不会再加载一次,静态块也不会再执行一次。当然,如果是先st.getClass().getClassLoader().loadClass,再newInstance(),
    此时静态块肯定执行过一次了,当再调用TestStatic.printStatic()或StaticTest.a,时,也不会
    再把TestStatic,StaticTest类加载一次,静态块也不会再执行一次。
      

  3.   

    回复你下面的话
    ================
    不过也该结贴了~~~从这些可以看出来,一些人的认识实在是太肤浅了~~~
    不排除大多数是新手~~有的人只知道书上写的,从不动手去写,但他们哪里知道书上的并不都是对的~~
    遇到问题自己去写出来,用事实证明这才是道理!!~
    不管对错,能拿出例子来,不能只凭一句xxxx书上写的清楚~~拿程序来说话才是做程序员的素质要求!
    =====================我不再谈论非技术问题,谢谢!
      

  4.   

    非常欣赏楼主这么认真的精神!
    daniel_kaka (卡卡)的帖子我也回过,他为了支持自己的论点也给出了详细的测试代码,这种精神也非常值得钦佩!只不过他的论点有点小小的错误。那篇帖子我没有继续关注下去,不知道讨论得如何了,但是在CSDN这么讨论问题才真正有价值!
    支持你们二位的行为!BTW,似乎你们都没有用javap命令的习惯,这个命令可以查看编译后的class文件的样子。语法如下:javap -c -classname(classname为编译后的类名,不包括.class扩展名)