黑马程序员技术交流社区

标题: 有关Java(静态)初始化块的疑问 [打印本页]

作者: 李国荧    时间: 2014-8-7 20:20
标题: 有关Java(静态)初始化块的疑问
本帖最后由 李国荧 于 2014-8-7 20:19 编辑

代码如下:
  1. public class InitBlock{
  2.     {
  3.         a = 10;
  4.         //此处,为什么可以给a赋值,却不可以让a参加运算或者输出a的值??
  5.         //System.out.println(a);
  6.     }
  7.     static{
  8.         s_a = 20;
  9.         //此处,为什么可以给s_a赋值,却不可以让s_a参加运算或者输出a的值??
  10.         //System.out.println(s_a);
  11.     }
  12.         
  13.     private Integer a = 1;
  14.     private static Integer s_a = 2;
  15.         
  16.     {
  17.         System.out.println("a = " + a);
  18.     }
  19.     static{
  20.         System.out.println("s_a = " + s_a);
  21.     }
  22.         
  23.     public static void main(String[] args){
  24.         new InitBlock();
  25.     }
  26. }
复制代码
1、首次使用某个类时,JVM会将该类对应的class文件加载至内存
2、JVM为该类的所有静态成员变量分配内存空间,并进行默认初始化(即赋值为0、0.0、false、null等)
3、JVM按照静态初始化块和静态成员变量声明语句的先后顺序依次执行它们,完成初始化
4、程序要求创建对象
5、JVM按照普通初始化块和实例成员变量声明语句的先后顺序依次执行它们,完成初始化
6、执行构造函数完成初始化

既然在执行(静态)初始化块执行之前,已经为静态或实例成员变量分配了内存空间、并进行了默认初始化,
那为什么在如上代码中(05和10行)只能给成员变量赋值、却不能让成员变量参加运算或输出成员变量的值呢??

关于以上1~6分析的正确性请参考
http://fishermen.iteye.com/blog/24025
http://guoying252166655.iteye.com/blog/2101457





作者: yqj    时间: 2014-8-7 23:49
好深的问题,同待高手!
作者: 李国荧    时间: 2014-8-25 07:14
啥也不说了……
作者: 李国荧    时间: 2014-8-25 07:16
此贴已经沉了……
作者: 李国荧    时间: 2014-8-25 07:17
怎么会这样呢??
作者: 李国荧    时间: 2014-8-25 07:18
大神在哪??
作者: 李国荧    时间: 2014-8-25 07:19
没有人知道吗??
作者: ddewym123    时间: 2014-8-25 09:11
1、创建类的对象时,其内部的初始化顺序是如此的:
1)静态成员变量与静态始化块按照出现顺序先后执行;
2)调用本对象的构造函数(但还未执行构造方法体);
3)调用父类的构造方法;
4)成员变量与构造代码块按照出现顺序先后执行;
5)执行本对象的构造方法。
注意:3)调用父类构造方法时,也按照1-5的顺序依次执行父类的初始化。

2、静态成员变量与成员变量只有在定义时,才会分配内存。

3、根据上述原因,你的代码中,a与s_a定义位置在于静态代码块与构造代码后面,所以前两个代码块的赋值其实是毫无意义的。这从结果打印为a=1,s_a=2可以看出。
实际上,此代码初始化顺序是这样的:
1)代码7;无意义
2)代码14;无意义
3)代码19;
4)代码2;
5)代码13;
6)代码16;
7)本类构造方法;
作者: 孙雯    时间: 2014-8-31 23:12
ddewym123 发表于 2014-8-25 09:11
1、创建类的对象时,其内部的初始化顺序是如此的:
1)静态成员变量与静态始化块按照出现顺序先后执行;
2 ...

路过。说得很详细啊~
作者: 李国荧    时间: 2014-9-3 07:27
ddewym123 发表于 2014-8-25 09:11
1、创建类的对象时,其内部的初始化顺序是如此的:
1)静态成员变量与静态始化块按照出现顺序先后执行;
2 ...

首先感谢您的回复。

您的回复中的第2点说“静态成员变量与成员变量只有在定义时,才会分配内存”,我觉得不正确,在我的代码中静态成员变量和非静态成员变量的定义放在了静态初始化块和普通初始化块之后,但是我依然可以在初始化块中为成员变量赋值,能够为其赋值就说明已经为它分配内存空间。对吧?
问题来了,我既然能通过变量名来为变量赋值,那为什么我不能同过变量名来输出变量的值呢??(注:编译失败的提示是InitBlock.java:6: error: illegal forward reference)

关于第3点,我觉得您的分析师稍微有点儿问题,您是不是想说代码7、代码2无意义?
我觉得您说代码7、代码2无意义的原因是:它们的赋值会被代码14、代码13的赋值覆盖掉,所以在7、2处赋值无意义。
您可以把代码13、14改为“private Integer a; private static Integer s_a;”(即只声明不初始化),您会发现输出“s_a = 20  a = 10”,这样代码7、2不就有意义了吗??
作者: ddewym123    时间: 2014-9-3 09:59
李国荧 发表于 2014-9-3 07:27
首先感谢您的回复。

您的回复中的第2点说“静态成员变量与成员变量只有在定义时,才会分配内存”,我觉 ...

真是抱歉,我的第二点确实是错误的。
我把下述代码进行debug调试,发现其执行顺序是如下的:
  1. package com.cn.brainfreeze;

  2. public class Test21{
  3.     {
  4.         a = 10;
  5.         //此处,为什么可以给a赋值,却不可以让a参加运算或者输出a的值??
  6.         //System.out.println(a);
  7.     }
  8.     static{
  9.         s_a = 20;
  10.         //此处,为什么可以给s_a赋值,却不可以让s_a参加运算或者输出a的值??
  11.         //System.out.println(s_a);
  12.     }
  13.         
  14.     private Integer a ;
  15.     private static Integer s_a ;
  16.         
  17.     {
  18.         System.out.println("a = " + a);
  19.     }
  20.     static{
  21.         System.out.println("s_a = " + s_a);
  22.     }
  23.         
  24.     public static void main(String[] args){
  25.         new Test21();
  26.     }
  27. }
复制代码
1、line10;s_a=null;a errors during the evaluation;
2、line22;s_a=20;a errors during the evaluation;
3、line26;
4、line5;a=null;
5、line19;a=10;
6、line3;

如果将15、16行代码改为:
  1. private Integer a =1;
  2. private static Integer s_a=2 ;
复制代码
则执行顺序变为:
1、line10;s_a=null;a errors during the evaluation;
2、line16;s_a=20;a errors during the evaluation;
3、line22;s_a=2;a errors during the evaluation;
4、line26;
5、line5;a=null;
6、line15;a=10;
7、line19;a=;
8、line3;


我不了解编译时的情况以及实际内存分配的过程。我从上述代码,推测:
1、分配内存是通过定义变量语句实现的。静态成员变量是在类加载时(或者编译时。具体不了解。)就已经分配了内存。而成员变量只有在创建对象时才分配内存。
2、无论变量的定义语句是在什么位置,哪怕在变量赋值语句之后。因为通过1可知,在执行变量赋值语句时,内存都已经分配了,所以可以赋值。
3、变量的初始化与分配内存不是同时进行的。可以把初始化看做一般的赋值语句,按照其在代码中的位置顺序进行执行。
4、虽然,赋值语句可以不管定义变量语句的顺序。但要使用变量,如打印输出,必须保证该语句是在定义变量语句之后。原因不解~

这些都是推测,求大神解释\介绍讲解内部机理的书籍.

作者: 李国荧    时间: 2014-9-3 17:43
ddewym123 发表于 2014-9-3 09:59
真是抱歉,我的第二点确实是错误的。
我把下述代码进行debug调试,发现其执行顺序是如下的:
1、line10; ...

感谢您的回复。

关于静态成员变量的内存分配,我觉得是在第一次加载类的时候,而不是在编译时(当我们调用javac命令编译一个源文件时,会启动一个JVM进程,这个JVM进程在编译完源文件后,即生成class文件后,就退出了。在编译的过程中我们肯定无法使用正在编译的文件,无法使用就不能了解它有什么内容了,那就不可能给它分配内存了。)

关于第3点,默认初始化(即初始化为0、0.0、false、null)和分配内存是同时进行的(也不能说是同时,但肯定是先分配内存,再默认初始化,再显式初始化)。

之所以在初始化完成员变量后输出变量的值,是因为我想看看到底有没有初始化,再就是看看初始化的次序,但是无意中碰上了不能输出的问题。
今天看到你用debug模式,忽然想到用它就可以查看运行过程中变量的值的变化情况了。(复习基础一直在用notepad++,没用eclipse)

虽然还有点儿小遗憾,不过最初的目的达到了。
太感谢了……
作者: ddewym123    时间: 2014-9-3 19:02
李国荧 发表于 2014-9-3 17:43
感谢您的回复。

关于静态成员变量的内存分配,我觉得是在第一次加载类的时候,而不是在编译时(当我们调 ...

我所说的初始化就是指显示初始化...





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2