当一个java类第一次被使用时,虚拟机会对该类初始化。初始化的主要工作是执行java类中的静态代码快和初始化静态域。在初始化过程中,java中的静态代码块和静态域会按照在代码中出现的顺序依次执行初始化。在当前java类被初始化之前,它的直接父类也会被初始化,但是该java类实现的接口并不会被初始化。对一个接口来说,当其被初始化时,它的父接口不会被初始化。

  在初始化的过程中,静态代码快和静态域出现的顺序很重要。虚拟机会严格按照在源代码中的出现顺序来执行初始化操作。

  一下代码展示了静态域的初始化顺序可能带来的问题。静态域X的值最开始是20,所以Y得值是40.虽然随后通过静态代码块把X的值设成了30,但是Y得值不会发生变化,依然是40。

 1 public class StaticOrder {
 2     public static int X = 20;
 3     public static int Y = 2 * X;
 4     static {
 5         X = 30;
 6     }
 7     public static void main(String[] args) {
 8         System.out.println(Y);
 9     }
10 }

  需要注意的是,当访问一个java类或者接口中的静态域时,只有真正申明这个域的类或者接口才会被初始化。

  如下代码给出了一个类继承时的静态域的初始化示例。在类A中申明了静态域value。虽然应用时使用的是类B,但是由于value是在类A中申明的,因此访问B.value只会使类A被初始化。类B不会被初始化。

public class A {
    static int value = 100;
    static {
        System.out.println("A 类被初始化");
    }
}
public class B extends A {
    static {
        System.out.println("B类被初始化");
    }
}
public class StaticFieldInit {
    public static void main(String[] args) {
        System.out.println(B.value);
    }
}

最后输出的结果:

A 类被初始化
100

有很多不同的原因会使一个java类被初始化。下面列出了可能造成类被初始化的操作。

  1.创建一个java类的实例对象。比如调用“MyClass obj = new MyClass()"语句时,类MyClass会被初始化。

  2.调用一个java类中的静态方法。比如上面中的第二个例子。

  3.为类或接口中的静态域赋值。比如myField是MyClass类中的静态域,调用”MyClass.myField = 10"会使MyClass类被初始化。

  4.访问类或接口中申明的静态域,并且该域的值不是常值变量。也就是不是static final 修饰的变量。

  5.在一个顶层java类中执行assert语句也会使该java类被初始化。

  6.调用Class类和反射API中进行反射操作的方法也会初始化java类。

 

二:对象的创建和初始化。

  类的构造方法的调用主要分为三步:1.调用父类的构造方法。2.调用初始化类中实例域的值。按照实例域的初始化顺序进行初始化。3.执行类的构造方法中的其他代码。

  如下代码所示:Animal类是Dog类的父类。在创建Dog类的对象的时候,Dog的构造方法先被调用。在Dog类的构造方法中使用参数值“4”调用父类的Animal的构造方法。在Animal类的构造方法的最开始,会再调用Animal类的父类Object类的构造方法。由于Object类没有父类,不在需要继续调用父类的构造方法,而是执行自身的构造方法的逻辑。Object类的构造方法执行完毕之后,调用流程回到Animal类的构造方法。会先对Animal中的实例域进行初始化,使legs变量被初始化为0。接着Animal类的构造方法中的其他代码被调用,legs变量的值被设置为4。接着调用流程转入到Dog类的构造方法中,先把实例变量name的值初始化为“dog”,然后调用Dog类构造方法中的其他代码。此时完成了Dog类的创建。

public class Animal {
    int legs = 0;
    Animal(int legs){
        this.legs = legs;
    }
}
public class Dog extends Animal {
    String name = "dog";
    Dog(){
        super(4);
        System.out.println("something others");
    }
}
public class NewObject {
    public static void main(String[] args) {
        Dog dog =new Dog();
        System.out.println(dog.legs);
    }
}

  在编写构造方法的时候,不要在构造方法中调用可以被子类覆写的方法。这是因为如果子类覆写了该方法,那么在初始过程中进行到调用父类的构造方法时,父类的构造方法所调用的是子类所覆写的方法,而此时子类的构造方法中的代码并没有被执行,对象仍处于初始化的过程中。这时调用子类的方法很容易出现错误。

  一下代码给出了示例,在父类Parent中调用getCount() 方法,在子类Child中覆写了该方法并返回在Child类的构造方法中设置的值。这两个类初看起来并没有错误,但是当试图创建Child类的实例时,会出现除零错误。这是因为在执行Parent类的构造方法时使用的是Child的getCount()方法,而此时Child类的实例域count还没有被初始化,它的值是0。

public class Parent {
    public Parent(){
        int average = 3 / getCount();
    }
    protected int getCount(){
        return 4;
    }
}
public class Child extends Parent {
    private  int count;
    public Child(int count){
        this.count = count;
    }
    public int getCount(){
        return count;
    }
}
public class Main {
    public static void main(String[] args) {
        Child child = new Child(5);
    }
}

结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at constractor1.Parent.<init>(Parent.java:12)
    at constractor1.Child.<init>(Child.java:12)
    at constractor1.Main.main(Main.java:12)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)