Java类初始化和实例化

时间:2021-04-12 09:22:35

Java有以下几种方式创建类对象:

  • 利用new关键字
  • 利用反射Class.newInstance
  • 利用Constructor.newIntance(相比Class.newInstance多了有参和私有构造函数)
  • 利用Cloneable/Object.clone()
  • 利用反序列化

Constructor.newInstance不支持带原型入参的构造函数。

调用Class.getConstructor()方法获取无参默认构造Constructor时,如果用户自定义了有参构造函数,因为此时java并不会生成默认构造函数,所以Class.getConstructor()方法因找不到无参默认构造函数而抛异常。此时需要显示定义默认构造函数:

// Initialization.java
public class Initialization {
    private int age = 2000;
    private int salary = age + 1000;
    private String name = "Tom";

    public Initialization() {
        print();
    }

    public Initialization(Integer salary, String name) {
        print();

        this.salary = salary;
        this.name = name;

        print();
    }

    /** * Static code */
    {
        salary += 500;
    }

    private void print() {
        System.out.println("age=" + this.age);
        System.out.println("salary=" + this.salary);
        System.out.println("name=" + this.name);
    }

    public static Initialization construct(int salary, String name) throws Exception {
        Constructor<Initialization> constructorWithNoParams = Initialization.class.getConstructor();
        Constructor<Initialization> constructorWithParams = Initialization.class.getConstructor(Integer.class, String.class);
        return salary <= 0 || name == null ? constructorWithNoParams.newInstance() : constructorWithParams.newInstance(salary, name);
    }

    public Initialization deSerialize() throws Exception {
        // 写对象
        ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("student.txt"));
        output.writeObject(this);
        output.close();

        // 读取对象
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("student.txt"));
        return (Initialization) input.readObject();
    }
}

再来看下Initialization类被编译为.class文件后的信息:

public class Initialization {
    private int age = 2000;
    private int salary;
    private String name;

    public Initialization() {
        this.salary = this.age + 1000;
        this.name = "Tom";
        this.salary += 500;
        this.print();
    }

    public Initialization(Integer salary, String name) {
        this.salary = this.age + 1000;
        this.name = "Tom";
        this.salary += 500;
        this.print();
        this.salary = salary.intValue();
        this.name = name;
        this.print();
    }

    private void print() {
        System.out.println("age=" + this.age);
        System.out.println("salary=" + this.salary);
        System.out.println("name=" + this.name);
    }

    public static Initialization construct(int salary, String name) throws Exception {
        Constructor constructorWithNoParams = Initialization.class.getConstructor(new Class[0]);
        Constructor constructorWithParams = Initialization.class.getConstructor(new Class[]{Integer.class, String.class});
        return salary > 0 && name != null?(Initialization)constructorWithParams.newInstance(new Object[]{Integer.valueOf(salary), name}):(Initialization)constructorWithNoParams.newInstance(new Object[0]);
    }

    public Initialization deSerialize() throws Exception {
        ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("student.txt"));
        output.writeObject(this);
        output.close();
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("student.txt"));
        return (Initialization)input.readObject();
    }

    public static void main(String[] args) throws Exception {
        Initialization result = construct(0, "Paul");
    }
}

1、无论实例变量还是实例代码块,均遵从先父类后子类的初始化顺序。
2、对实例变量直接赋值或者利用实例代码块赋值,编译器会其代码填充到类的构造函数中。不允许书写顺序靠前的实例代码初始化在其后定义的实例变量。

Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用。这样确保当前对象完成初始化前其父类已完成初始化,从而构建完整的对象。

如果默认构造函数内部调用了有参构造函数,仅允许在有参构造函数里调用父类构造函数。

静态代码块

  • 多个static按编码顺序依次处理
  • static变量的申明和初始化是两个不同的操作
  • static变量在编译期已确认值

以下代码是等价的:

// code list1
public class StaticInitialization {
    static {
        data = 1;
    }

    public static void main(String[] args) {
        System.out.println(data);
    }

    private static int data = 2;
}
// code list 2
public class StaticInitialization {
    private static int data;
    static {
        data = 1;
        data = 2;
    }

    public static void main(String[] args) {
        System.out.println(data);
    }
}

code list 2的字节码为:

public class StaticInitialization {
  public StaticInitialization();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: getstatic     #3 // Field data:I
       6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
       9: return

  static {};
    Code:
       0: iconst_1
       1: putstatic     #3 // Field data:I
       4: iconst_2
       5: putstatic     #3 // Field data:I
       8: return
}

可以看到static变量在编译期就已放到常量const指向的内存地址里。

初始化顺序

如果没有继承关系,初始化顺序为:静态代码、静态代码块 > 成员变量、实例代码块 > 构造函数。否则,初始化顺序为先父后子。

所以初始化顺序:父类static静态变量 > 父类static代码块 > 子类static静态变量 > 子类static代码块 > 父类变量 > 父类实例代码块 > 父类构造函数 > 子类变量 > 子类实例代码块 > 子类构造函数

举个例子:

public class StaticTest {
    public static void main(String[] args) {
        staticFunction();
    }

    private static int data;

    static StaticTest st;

    static {   //静态代码块
        System.out.println("1");
    }

    {       // 实例代码块
        System.out.println("2");
    }

    static {
        data = 1;
    }

    static {
        st = new StaticTest();
    }

    StaticTest() {    // 实例构造器
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }

    public static void staticFunction() {   // 静态方法
        System.out.println("4");
    }

    int a = 110;    // 实例变量
    static int b = 112;     // 静态变量
}
/** * 输出结果 * 1 * 2 * 3 * a=110,b=0 * 4 */