Java中成员变量的初始化
- 类变量与成员变量
在java类的初始化中,一边只初始化一次,类的初始化主要是用以初始化类变量,即静态变量
在初始化的过程中存在着静态变量与静态块两部分,初始化的顺序为先加载静态变量,后加载静态块的内容,如下例:
public class Person{
public static String name="张三";
public static int age;
static{
age=20;
System.out.println("初始化age");
}
public static String address;
static{
address="北京市";
age=34;
}
public static void main(String[] args) {
System.out.println(name);
System.out.println(age);
System.out.println(address);
}
}
其初始化的顺序如下述代码所示:
public class Person{
public static String name;
public static int age;
public static String address;
static{
name="张三";
age=20;
System.out.println("初始化age");
address="北京市";
age=34;
}
public static void main(String[] args) {
System.out.println(name);
System.out.println(age);
System.out.println(address);
}
}
即先加载静态变量,后加载代码块的内容,对于类变量而言在声明处进行初始化与在静态代码块中进行初始化效果是一致的。所有的类变量都是先声明,后赋值的,并且其赋值的顺序与其声明顺序一致。
也就是说在代码顺序中即便静态代码块在前,类变量的声明在后,也是不会出现报错的,因为在加载的时候是先加载的类变量,后加载的静态代码块中的内容,同时在静态代码块中只能对之前声明的类变量进行赋值,而不可以使用之前声明的类变量,因为此时并没有声明,如果声明在前方可使用
也就是说最先加载的是类变量与静态代码块中的内容,首先会加载类变量的声明,之后再在静态代码块中按照代码顺序进行依次加载,
static{
a = 4;
System.out.println("lalal");
}
private static int a=6;
public static void main(String[] args) {
System.out.println(a);
}
会输出
lalala
6
这时的加载顺序如下:
static int a;
static{
a=4
System.out.println("lalal");
a=6
}
如下:
public class Person {
static{
a = 4;
//System.out.println(a);//这里会报出错误。
}
private static int a;
public static void main(String[] args){
System.out.println(a);
}
}
此时的输出结果为4
如果出现子类与父类都具有类变量的情况下
如下:
class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
// 变量
public String p_Field = "父类--变量";
protected int i = 9;
protected int j = 0;
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
// 构造器
public Parent() {
System.out.println("父类--构造器");
System.out.println("i=" + i + ", j=" + j);
j = 20;
}
}
public class SubClass extends Parent {
// 静态变量
public static String s_StaticField = "子类--静态变量";
// 变量
public String s_Field = "子类--变量";
// 静态初始化块
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
System.out.println("子类--构造器");
System.out.println("i=" + i + ",j=" + j);
}
// 程序入口
public static void main(String[] args) {
System.out.println("子类main方法");
new SubClass();
}
}
在输出 “子类main方法”即在进入程序入口之前首先会输出
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
当加载完类之后,进入到类对象的构造阶段
对于对象的构造与类构造有相似之处,同时多出了构造函数这一部分
1 public class Person{
2 {
3 name="李四";
4 age=56;
5 System.out.println("初始化age");
6 address="上海";
7 }
8 public String name="张三";
9 public int age=29;
10 public String address="北京市";
11 public Person(){
12 name="赵六";
13 age=23;
14 address="上海市";
15 }
16 }
如上的代码初始化顺序如下所示:
1 public class Person{
2 public String name;
3 public int age;
4 public String address;
5 public Person(){
6 name="李四";
7 age=56;
8 System.out.println("初始化age");
9 address="上海";
10 name="张三";
11 age=29;
12 address="北京市";
13 name="赵六";
14 age=23;
15 address="上海市";
16 }
17 }
对于类中成员变量的初始化的过程相当于全部挪到了构造函数中,同时初始化的顺序与之前声明的顺序一致,对同一个变量,后者的值覆盖前者的值,所以最好name值等于赵六,age=23,address=上海
静态变量、静态初始化块,变量、初始化块初始化了顺序取决于它们在类中出现的先后顺序。
对比以下代码
public class Test1 {
{
a = 4;
}
private static int a;
public static void main(String[] args){
Test3 test3 = new Test3();//注意:这里开始new了一个对象
System.out.println(test3.a);
}
}
该代码的输出结果为4,因为调用的是test3这个对象的a成员变量
public class Test2 {
{
a = 4;
System.out.println(a);//这里不会报错,但是这条语句并不会执行
}
private static int a;
public static void main(String[] args){
System.out.println(a);
}
}
输出0,因为调用Test2该类的类成员变量 且该类成员变量无初始值,默认为0
public class Test3 {
static{
a = 4;
}
private static int a;
public static void main(String[] args){
System.out.println(a);
}
}
输出为4,通过静态代码块进行了初始化
final关键字的影响
一个类一旦被设定为final,则表明该类是无法被修改,也无法被继承的。
变量被定义为final,则表明该变量有且仅有一次被赋值的机会,同时该变量必须被初始化
public class Test1 {
{
a = 4;
}
private static final int a;
public static void main(String[] args){
System.out.println(a);
}
}
对于Test1来说会出现编译错误,因为在定义static final int a时没有附初始值
public class Test2 {
static{
a = 5;
}
private static final int a;
public static void main(String[] args){
System.out.println(a);
}
}
此时a值为5,因为对于类变量在声明时赋初值与在static代码块中赋初值是一样的效果
static{
a=4;
}
private static final int a=3;
public static void main(String[] args) {
System.out.println(a);
}
}
此时同样会出现编译错误,因为已经将声明的a值赋值为4,不可以更改其赋值为3(注意赋值的顺序)
最终在此基础上引用下述代码清晰展现初始化顺序
public class InitialOrderTest {
// 静态变量
public static String staticField = "静态变量";
// 变量
public String field = "变量";
// 静态初始化块
static {
System.out.println(staticField);
System.out.println("静态初始化块");
}
// 初始化块
{
System.out.println(field);
System.out.println("初始化块");
}
// 构造器
public InitialOrderTest() {
System.out.println("构造器");
}
public static void main(String[] args) {
new InitialOrderTest();
}
}
运行以上代码,我们会得到如下的输出结果:
1. 静态变量
2. 静态初始化块
3. 变量
4. 初始化块
5. 构造器
这与上文中说的完全符合。那么对于继承情况下又会怎样呢?我们仍然以一段测试代码来获取最终结果:
class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
// 变量
public String p_Field = "父类--变量";
protected int i = 9;
protected int j = 0;
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
// 构造器
public Parent() {
System.out.println("父类--构造器");
System.out.println("i=" + i + ", j=" + j);
j = 20;
}
}
public class SubClass extends Parent {
// 静态变量
public static String s_StaticField = "子类--静态变量";
// 变量
public String s_Field = "子类--变量";
// 静态初始化块
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
System.out.println("子类--构造器");
System.out.println("i=" + i + ",j=" + j);
}
// 程序入口
public static void main(String[] args) {
System.out.println("子类main方法");
new SubClass();
}
}
运行一下上面的代码,结果马上呈现在我们的眼前:
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
子类main方法
父类--变量
父类--初始化块
父类--构造器
i=9, j=0
子类--变量
子类--初始化块
子类--构造器
i=9,j=20
执行过程如下:
1. 访问subClass的main方法,因为其为static方法,所以类装载器会寻找已经编译好的subClass.class文件,因为其有一个基类,所以首先装载其基类,之后装载该类本身
2. 装载该类的时候,其类对象会被初始化,初始化顺序如前文所示,然后是该类的派生类的类对象初始化
3. 类装载结束后,进入到main()方法,创建subClass对象
4. 由于其存在父类,因此首先进行父类对象的加载,其加载过程与类加载过程相似,先是基类的成员变量进行初始化,然后是基类的构造函数的其他部分
5. 最后是子类的成员变量进行初始化,然后是子类的构造函数其他部分。
关于类加载与类初始化要注意
- 类只是声明没有使用的时候并不会进行初始化,例如
public class ClassInitializationTest {
public static void main(String args[]) throws InterruptedException {
NotUsed o = null; //this class is not used, should not be initialized
Child t = new Child(); //initializing sub class, should trigger super class initialization
System.out.println((Object)o == (Object)t);
}
}
/**
* Super class to demonstrate that Super class is loaded and initialized before Subclass.
*/
class Parent {
static { System.out.println("static block of Super class is initialized"); }
{System.out.println("non static blocks in super class is initialized");}
}
/**
* Java class which is not used in this program, consequently not loaded by JVM
*/
class NotUsed {
static { System.out.println("NotUsed Class is initialized "); }
}
/**
* Sub class of Parent, demonstrate when exactly sub class loading and initialization occurs.
*/
class Child extends Parent {
static { System.out.println("static block of Sub class is initialized in Java "); }
{System.out.println("non static blocks in sub class is initialized");}
}
Output:
static block of Super class is initialized
static block of Sub class is initialized in Java
non static blocks in super class is initialized
non static blocks in sub class is initialized
false
并没有输出NotUsed Class is initialized ,因为并没有被使用
- 类初始化的,主动引用
- 创建类的实例(创建实例,只声明不创建不会进行类初始化)
- 访问类的静态变量 (除常量【 被final修辞的静态变量】 原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变final域的值那么每一块用到那个域的代码都需要重新编译。
- 访问类的静态方法
- 反射 如( Class.forName(“my.xyz.Test”) )
- 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
- 虚拟机启动时,定义了main()方法的那个类先初始化
- 被动引用,不进行初始化
- 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。 。 对于静态字段,只有直接定义这个字段的类才会被初始化.
2.通过数组定义来引用类,不会触发类的初始化
3.访问类的常量,不会初始化类
- 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。 。 对于静态字段,只有直接定义这个字段的类才会被初始化.
class SuperClass {
static {
System.out.println("superclass init");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("subclass init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(SubClass.value);// 被动应用1
SubClass[] sca = new SubClass[10];// 被动引用2
}
}
程序运行输出 superclass init
123
从上面的输入结果证明了被动引用1与被动引用2
class ConstClass {
static {
System.out.println("ConstClass init");
}
public static final String HELLOWORLD = "hello world";
}
public class Test {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);// 调用类常量
}
}
程序输出结果
hello world
从上面的输出结果证明了被动引用3
实例分析:
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
分析
1SingleTon singleTon = SingleTon.getInstance();调用了类的SingleTon调用了类的静态方法,触发类的初始化
2类加载的时候在准备过程中为类的静态变量分配内存并初始化默认值 singleton=null count1=0,count2=0
3类初始化化,为类的静态变量赋值和执行静态代码快。**singleton赋值为new SingleTon()调用类的构造方法**
4调用类的构造方法后count=1;count2=1
5继续为count1与count2赋值,此时count1没有赋值操作,所有count1为1,但是count2执行赋值操作就变为0
总结:
- 先明确什么时候初始化,什么时候不要初始化
- 再明确初始化顺序