本文对Java如何执行对象的初始化做一个详细深入地介绍。有需要的小伙伴们可以参考。


前言

在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。

自动初始化(默认值)

一个类的所有基本数据成员都会得到初始化,运行下面的例子可以查看这些默认值:

 
          class 
           Default{ 
         
 
             
          boolean 
           t; 
         
 
             
          char 
           c; 
         
 
             
          byte 
           b; 
         
 
             
          short 
           s; 
         
 
             
          int 
           i; 
         
 
             
          long 
           l; 
         
 
             
          float 
           f; 
         
 
             
          double 
           d; 
         
 
             
          public 
           void 
           show() { 
         
 
               
          System.out.println( 
          "基本类型  初始化值\n" 
          + 
         
 
                       
          "boolean<----->" 
           + t + 
          "\n" 
           + 
         
 
                       
          "char<----->" 
           + c + 
          "\n" 
           + 
         
 
                       
          "byte<----->" 
           + b +  
          "\n" 
           + 
         
 
                       
          "short<----->" 
           + s +  
          "\n" 
           + 
         
 
                       
          "int<----->" 
           + i +  
          "\n" 
           + 
         
 
                       
          "long<----->" 
           + l +  
          "\n" 
           + 
         
 
                       
          "float<----->" 
           + f +  
          "\n" 
           + 
         
 
                       
          "double<----->" 
           + d +  
          "\n" 
         
 
               
          ); 
         

             
         
 
             
          } 
         
 
          } 
         
 
          public 
           class 
           InitValue { 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         
 
               
          Default d =  
          new 
           Default(); 
         
 
               
          d.show(); 
         
 
             
          } 
         
 
          } 


【运行结果】:

基本类型   初始化值

boolean<----->false

char<----->

byte<----->0

short<----->0

int<----->0

long<----->0

float<----->0.0

double<----->0.0

其中,char类型的默认值为空(null)。

对于非基本数据类型而言,对象的句柄也会被初始化:

 
          class 
           Person { 
         
 
             
          private 
           String name; 
         
 
             
          // setter 
         
 
          } 
         
 
          class 
           Default { 
         
 
             
          Person p; 
         
 
             
          public 
           void 
           show() { 
         
 
               
          System.out.println( 
          "Person<----->" 
           + p); 
         
 
             
          } 
         
 
          } 
         
 
          public 
           class 
           InitValue { 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         
 
               
          Default d =  
          new 
           Default(); 
         
 
               
          d.show(); 
         
 
             
          } 
         
 
          } 
         


【运行结果】:

Person<----->null

p.setName的方法,就会出现异常。

规定初始化

如果需要自己为变量赋一个初始值,可以在定义变量的同时赋值。

 
          class 
           Default{ 
         
 
             
          boolean 
           t =  
          true 
          ; 
         
 
             
          char 
           c =  
          'A' 
          ; 
         
 
             
          byte 
           b =  
          47 
          ; 
         
 
             
          short 
           s =  
          0xff 
          ; 
         
 
             
          int 
           i =  
          24 
          ; 
         
 
             
          long 
           l =  
          999 
          ; 
         
 
             
          float 
           f =  
          1 
          .2f; 
         
 
             
          double 
           d =  
          1.732 
          ; 
         
 
             
          public 
           void 
           show() { 
         
 
               
          System.out.println( 
         
 
                       
          "boolean<----->" 
           + t + 
          "\n" 
           + 
         
 
                       
          "char<----->" 
           + c + 
          "\n" 
           + 
         
 
                       
          "byte<----->" 
           + b +  
          "\n" 
           + 
         
 
                       
          "short<----->" 
           + s +  
          "\n" 
           + 
         
 
                       
          "int<----->" 
           + i +  
          "\n" 
           + 
         
 
                       
          "long<----->" 
           + l +  
          "\n" 
           + 
         
 
                       
          "float<----->" 
           + f +  
          "\n" 
           + 
         
 
                       
          "double<----->" 
           + d +  
          "\n" 
         
 
               
          ); 
         

             
         
 
             
          } 
         
 
          } 
         
 
          public 
           class 
           InitValue { 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         
 
               
          Default d =  
          new 
           Default(); 
         
 
               
          d.show(); 
         
 
             
          } 
         
 
          } 


甚至可以通过一个方法来进行初始化;

 
          class 
           Person { 
         
 
             
          int 
           i = set(); 
         
 
             
          //... 
         
 
          } 


这些方法也可以使用自变量:

 
          class 
           Person { 
         
 
             
          int 
           i; 
         
 
             
          int 
           j = set(i); 
         
 
             
          //... 
         
 
          } 


构建器初始化

构建器进行初始化的优点是可以在运行期决定初始化值。例如:

 
          class 
           Person { 
         
 
             
          int 
           age; 
         
 
             
          Person() { 
         
 
               
          age =  
          89 
          ; 
         
 
             
          } 
         
 
          } 


age首先会初始化为0,然后变成89。对于所有基本类型以及对象的句柄,这种情况都是成立的。

初始化顺序

在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那么变量仍然会在调用任何方法(包括构造函数)之前得到初始化。例如:

 
          class 
           Pet { 
         
 
             
          Pet( 
          int 
           age) { 
         
 
               
          System.out.println( 
          "Pet(" 
           + age +  
          ")" 
          ); 
         
 
             
          } 
         
 
          } 
         

             
         
 
          class 
           Person { 
         
 
             
          Pet t1 =  
          new 
           Pet( 
          1 
          ); 
         

             
         
 
             
          Person() { 
         
 
               
          System.out.println( 
          "---Person()---" 
          ); 
         
 
               
          t3 =  
          new 
           Pet( 
          33 
          ); 
         
 
             
          } 
         

             
         
 
             
          Pet t2 =  
          new 
           Pet( 
          2 
          ); 
         
 
             
          void 
           show() { 
         
 
               
          System.out.println( 
          "show----running" 
          ); 
         
 
             
          } 
         
 
             
          Pet t3 =  
          new 
           Pet( 
          3 
          ); 
         
 
          } 
         

             
         

             
         
 
          public 
           class 
           OrderOfInitialization { 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         
 
               
          Person p =  
          new 
           Person(); 
         
 
               
          p.show(); 
         
 
             
          } 
         
 
          } 


【运行结果】:

Pet(1)

Pet(2)

Pet(3)

---Person()---

Pet(33)<br/>

show----running

上例中,虽然t1、t2、t3的定义遍布于类中,但是初始化的先后顺序是由t1、t2、t3的定义顺序决定的(自己动手调换t1、t2、t3看看结果),且初始化优先于构建器执行,当调用Person的构建器时,t3重新初始化。

静态数据的初始化

如果数据是静态的(static),同样的过程也会执行。若属于基本类型,而且未对其进行初始化,就会自动获得自己的标准基本类型初始值;若它是指向一个对象的句柄,除非创建一个对象同它连接起来,否则得到一个空值(null)。如果在定义时初始化,采取的方式与非静态值是不同的,这是因为static只有一个存储区域。例如:

 
          class 
           Bowl { 
         
 
             
          Bowl( 
          int 
           marker) { 
         
 
               
          System.out.println( 
          "Bowl(" 
           + marker +  
          ")" 
          ); 
         
 
             
          } 
         
 
             
          void 
           f( 
          int 
           marker) { 
         
 
               
          System.out.println( 
          "f(" 
           + marker +  
          ")" 
          ); 
         
 
             
          } 
         
 
          } 
         

             
         
 
          class 
           Table { 
         
 
             
          static 
           Bowl b1 =  
          new 
           Bowl( 
          1 
          ); 
         
 
             
          Table() { 
         
 
               
          System.out.println( 
          "Table()" 
          ); 
         
 
               
          b2.f( 
          1 
          ); 
         
 
             
          } 
         
 
             
          void 
           f2( 
          int 
           marker) { 
         
 
               
          System.out.println( 
          "f2(" 
           + marker +  
          ")" 
          ); 
         
 
             
          } 
         
 
             
          static 
           Bowl b2 =  
          new 
           Bowl( 
          2 
          ); 
         
 
          } 
         

             
         
 
          class 
           Cupboard { 
         
 
             
          Bowl b3 =  
          new 
           Bowl( 
          3 
          ); 
         
 
             
          static 
           Bowl b4 =  
          new 
           Bowl( 
          4 
          ); 
         
 
             
          Cupboard() { 
         
 
               
          System.out.println( 
          "Cupboard()" 
          ); 
         
 
               
          b4.f( 
          2 
          ); 
         
 
             
          } 
         
 
             
          void 
           f3 ( 
          int 
           marker) { 
         
 
               
          System.out.println( 
          "f3(" 
           + marker +  
          ")" 
          ); 
         
 
             
          } 
         
 
             
          static 
           Bowl b5 =  
          new 
           Bowl( 
          5 
          ); 
         
 
          } 
         

             
         
 
          public 
           class 
           StaticInitialization { 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         
 
               
          System.out.println( 
          "Creating new Cupboard() in main" 
          ); 
         
 
               
          new 
           Cupboard(); 
         
 
               
          System.out.println( 
          "Creating new Cupboard() in main" 
          ); 
         
 
               
          new 
           Cupboard(); 
         
 
               
          t2.f2( 
          1 
          ); 
         
 
               
          t3.f3( 
          1 
          ); 
         
 
             
          } 
         
 
             
          static 
           Table t2 =  
          new 
           Table(); 
         
 
             
          static 
           Cupboard t3 =  
          new 
           Cupboard(); 
         
 
          } 


【运行结果】:

Bowl(1)

Bowl(2)

Table()

f(1)

Bowl(4)

Bowl(5)

Bowl(3)

Cupboard()

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard()

f(2)

f2(1)

f3(1)

静态代码块

Java允许将其他static初始化工作划分到类内一个特殊的代码块中,这种代码块的形式为static关键字,后面跟着一个方法主体,称为静态代码块。静态代码块只有在第一次生成那个类的对象或首次访问属于那个类的static成员时执行。例如:

在标记为1的行内访问static对象p1的时候,或在行1被注释而行2未被注释是,用于Persons的static初始化模块就会运行。若1和2都被注释掉,则用于Persons的静态代码块不会执行。

静态属性和静态代码块执行的先后顺序

 
          class 
           Person { 
         
 
             
          Person( 
          int 
           age) { 
         
 
               
          System.out.println( 
          "Person(" 
           + age +  
          ")" 
          ); 
         
 
             
          } 
         
 
             
          void 
           f( 
          int 
           age) { 
         
 
               
          System.out.println( 
          "f(" 
           + age +  
          ")" 
          ); 
         
 
             
          } 
         
 
          } 
         

             
         
 
          class 
           Persons { 
         
 
             
          static 
           Person p1; 
         
 
             
          static 
           Person p2; 
         
 
             
          static 
           { 
         
 
               
          p1 =  
          new 
           Person( 
          1 
          ); 
         
 
               
          p2 =  
          new 
           Person( 
          2 
          ); 
         
 
             
          } 
         
 
             
          Persons() { 
         
 
               
          System.out.println( 
          "Persons()" 
          ); 
         
 
             
          } 
         
 
          } 
         

             
         
 
          public 
           class 
           ExplicitStatic { 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         
 
               
          System.out.println( 
          "Inside main()" 
          ); 
         
 
               
          Persons.p1.f( 
          18 
          ); 
          //1 
         
 
             
          } 
         
 
             
          static 
           Persons x =  
          new 
           Persons(); 
          //2 
         
 
             
          static 
           Persons y =  
          new 
           Persons(); 
          //2 
         
 
          } 

根据注释1保留2,注释2保留1的结果分析可知,静态属性和静态代码块的执行顺序取决于编码的顺序。谁在前面就先执行谁。

非静态属性的初始化

 
          class 
           Person { 
         
 
             
          Person( 
          int 
           age) { 
         
 
               
          System.out.println( 
          "Person(" 
          +age+ 
          ")" 
          ); 
         
 
             
          } 
         
 
          } 
         
 
          class 
           Persons { 
         
 
             
          static 
           Person p =  
          new 
           Person( 
          2 
          );  
          // 1 
         
 
             
          static 
           { 
         
 
               
          p =  
          new 
           Person( 
          3 
          ); 
         
 
             
          } 
         
 
             
          static 
           Person p =  
          new 
           Person( 
          2 
          );  
          // 2 
         

             
         
 
          } 
         
 
          public 
           class 
           CompStaticInit { 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         

             
         
 
             
          } 
         
 
             
          static 
           Persons x =  
          new 
           Persons(); 
         
 
          } 


类似于静态代码块,匿名代码块与非静态属性的初始化顺序取决于编码顺序

继承中的对象初始化过程

 
          class 
           Animal { 
         
 
             
          Animal( 
          int 
           age) { 
         
 
               
          System.out.println( 
          "Animal(" 
           + age +  
          ")" 
          ); 
         
 
             
          } 
         
 
             
          void 
           f( 
          int 
           age) { 
         
 
               
          System.out.println( 
          "f(" 
           + age +  
          ")" 
          ); 
         
 
             
          } 
         
 
          } 
         
 
          public 
           class 
           NotStaticInit { 
         
 
             
          Animal a1; 
         
 
             
          Animal a2; 
         
 
             
          { 
         
 
               
          a1 =  
          new 
           Animal( 
          1 
          ); 
         
 
               
          a2 =  
          new 
           Animal( 
          2 
          ); 
         
 
               
          System.out.println( 
          "a1 & a2 initialized" 
          ); 
         
 
             
          } 
         
 
             
          NotStaticInit() { 
         
 
               
          System.out.println( 
          "NotStaticInit" 
          ); 
         
 
             
          } 
         
 
             
          public 
           static 
           void 
           main(String[] args) { 
         
 
               
          System.out.println( 
          "Inside main()" 
          ); 
         
 
               
          NotStaticInit x =  
          new 
           NotStaticInit(); 
         
 
             
          } 
         
 
          } 
         

【运行结果】:

static Insect.x1 initialized

static Bootle.x2 initialized

Beetle constructor

i = 1, j = 0

Beeklt.k initialized

k = 4

j = 2

对Beetle运行Java时,发生的第一件事情是装载程序到外面找到那个类。在装载过程中,装载程序发现一个基础类,所以随之将其载入。无论是否生成基础类的对象,这一过程都将执行。如果基础类含有另一个基础类,则另一个基础类随即也会载入,以此类推。接下来就在根基础类中执行static初始化,再在下一个衍生类中执行,以此类推。这是因为衍生类的初始化可能要依赖于对基础类成员的初始化。

super(),也可以通过super指定基类的构建器)。基础类构建器完成后,衍生类实例变量就会按本来的顺序得到初始化,然后执行构建器的剩余的主体部分。

总结对象创建的过程:

    静态只在类加载的时候执行且只执行一次;

    非静态只有在实例化的时候执行,每次创建对象都执行;

    静态在非静态之前执行,基类静态优先于衍生类静态执行;

    静态属性和静态代码块的执行属性取决于它们在类中的位置,谁在前先执行谁;

    非静态属性和构造块的执行顺序取决于它们在类中的位置,谁在前执行谁。

总结

通过上面的介绍,我们对Java中初始化对象的几种方式以及通过何种方式执行初始化代码有了了解,同时也对何种情况下我们可能会使用到未经初始化的变量进行了介绍。在对这些问题有了详细的了解之后,就可以在编码中规避一些风险,保证一个对象在可见之前是完全被初始化的。