java中类的起始化【分分快三计划】

作者:编程技术

 

例1:main()方法中为空(即无调用只加载)

public class Singleton {

    //静态变量  
    public static int num;

    //静态初始化块  
    static{  
        System.out.println(num ":静态初始化块");  
    }

    //实例初始化块
    {  
        System.out.println(num ":实例初始化块");  
    }

    //构造方法
    public Singleton(){  
        System.out.println(num ":构造函数初始化");  
        num  ;  
    }  

    //静态方法
    public static void getInstance(){
        System.out.println(num ":静态方法初始化");
    }

    public static void main(String[] args) {    

    }
}

结果为:0:静态初始化块

上述结果说明:

在加载时,静态变量就进行了初始化并赋了默认值;静态初始化块也执行了;其他并没有执行。

2.1 对象的创建过程总结

假设有个名为Dog的类:

  1. 当首次创建类型为Dog的对象时,或者Dog类的静态方法/静态域首次被访问时,java解释器必须查找类路径,以定位Dog.class文件。
  2. 然后载入Dog.class(这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在class对象首次加载的时候进行一次。
  3. 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间。
  4. 这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说就是0,对布尔型与字符型也相同),而引用则被设置成了null。
  5. 执行所有出现于字段定义处的初始化动作。
  6. 执行构造器。
---《Thinking in java》

 

子类静态变量初始化>子类静态构造函数>父类静态变量初始化 > 父类静态构造函数 > 父类实例变量初始化>父类实例构造函数 > 子类实例变量初始化>本身实例构造函数。

Java中类初始化

有三种: 静态初始化块、实例初始化块、构造器。

静态初始化块,在类被JVM的类加载器加载时就被执行了;

而实例初始化块和构造器是在类实例化对象时才被执行的。

执行顺序:

父类静态初始化块 > 子类静态初始化块 > 父类实例初始化块 > 父类构造器 > 子类实例初始化块 > 子类构造器

三、练习

下面以阿里巴巴2014校招笔试题来练手,看看大家是否真正理解对象创建过程:

public class Alibaba
{
    public static int        k    = 0;
    public static Alibaba    t1    = new Alibaba("t1");
    public static Alibaba    t2    = new Alibaba("t2");
    public static int        i    = print("i");
    public static int        n    = 99;
    private int                a    = 0;
    public int                j    = print("j");
    {
        print("构造块");
    }
    static
    {
        print("静态块");
    }

    public Alibaba(String str)
    {
        System.out.println((  k)   ":"   str   "   i="   i   "    n="   n);
          i;
          n;
    }

    public static int print(String str)
    {
        System.out.println((  k)   ":"   str   "   i="   i   "    n="   n);
          n;
        return   i;
    }

    public static void main(String args[])
    {
        Alibaba t = new Alibaba("init");
    }
}

 

输出:
1:j   i=0    n=0
2:构造块   i=1    n=1
3:t1   i=2    n=2
4:j   i=3    n=3
5:构造块   i=4    n=4
6:t2   i=5    n=5
7:i   i=6    n=6
8:静态块   i=7    n=99
9:j   i=8    n=100
10:构造块   i=9    n=101
11:init   i=10    n=102

 

详细结果分析: Java类中的初始化顺序

参考:

1、Java初始化理解与总结

 

例2:main()方法实例化两次

public class Singleton {

    //静态变量  
    public static int num = 0;

    //静态初始化块  
    static{  
        System.out.println(num ":静态块初始化");  
    } 

    //构造方法
    public Singleton(){  
        System.out.println(num ":构造函数初始化");  
        num  ;  
    }

     //实例初始化块
    {  
        System.out.println(num ":实例块初始化");  
    }
    //静态方法
    public static void getInstance(){
        System.out.println(num ":静态方法初始化");
    }

    public static void main(String[] args) {    
        new Singleton();
        new Singleton();
    }
}

结果为:

0:静态初始化块
0:实例初始化块
0:构造函数初始化
1:实例块初始化
1:构造函数初始化

说明:

(1)静态初始化块只是在类加载时执行一次,对类实例化时并不会再执行;

(2)类中静态方法并没有执行,因此只有在调用时才会执行;

(3)这三种类的初始化块的执行顺序:静态初始化块(加载时)---实例初始化块(实例化时)---构造函数初始化(实例化时)

小结

     初始化主要包括五个部分:静态变量、静态初始化块、变量、初始化块、构造器。

     它们的初始化顺序为(静态变量、静态初始化块)>(变量、初始化块)>构造器。

 

然而刚介绍的初始化类仅仅包括静态变量、静态初始化块。

 

子类:

例3:

public class Singleton {

    //静态变量  
    public static int num = 0;

    static Singleton ai = new Singleton();

    //静态初始化块  
    static{  
        System.out.println(num ":静态块初始化");  
    } 

    //构造方法
    public Singleton(){  
        System.out.println(num ":构造函数初始化");  
        num  ;  
    }

     //实例初始化块
    {  
        System.out.println(num ":实例块初始化");  
    }

    //静态方法
    public static void getInstance(){
        System.out.println(num ":静态方法初始化");
    } 

    public static void main(String[] args) {    

    }
}

结果:

0:实例块初始化
0:构造函数初始化
1:静态块初始化

说明:

(1)加载时,静态成员是依次运行的,这里先是静态变量,然后是静态引用,然后是静态块;

(2)当把创建引用即new对象的语句前的static关键字去掉后,该语句就不执行了,所以说明加载时是识别的static而不是new。

 

注:这里所谓的加载是笼统说法,还有细分可参看:

二、对象创建过程中的Java初始化

分分快三计划 1

1.3 类的初始化规则

1、类初始化时,该类的父类将首先被初始化,此过程一直递归到 java.lang.Object为止。但是父类实现的接口并不会被初始化。

class Parent implements J{
 { System.out.println("父类初始化块"); }  
 static{
     System.out.println("父类静态初始化块");  
 }
}
class Child extends Parent implements I{
 { System.out.println("子类初始化块"); }  
 static{
     System.out.println("子类静态初始化块");  
 }
}
interface I {  
 int i = Test.out("interface : i", 1);  
}  
interface J {  
 int j = Test.out("interface : j", 2);  
}  
public class Test {    
 static int out(String s, int i) {  
     System.out.println(s   "="   i);  
     return i;  
 }  
 public static void main(String [] args){
     new Child();
 }
}
接口只有被用到时才会被初始化



输出: 
父类静态初始化块
子类静态初始化块
父类初始化块
子类初始化块

 

2、接口初始化时,只会初始化该接口本身,并不会初始化它的父接口。

interface I {  
  int i = Test.out("interface : i", 1);  
  int ii = Test.out("interface : ii", 11);  
}  
interface J extends I{  
  int j = Test.out("interface : j", 2);  
  int jj = Test.out("interface : jj", 22);  
}  
public class Test {    
  static int out(String s, int i) {  
      System.out.println(s   "="   i);  
      return i;  
  }  
  public static void main(String [] args){
      System.out.println(J.j);  
  }
}



输出:
interface : j=2
interface : jj=22
2

3、如果类的初始化是由于访问静态域而触发,那么只有真正定义该静态域的类才被初始化,而不会触发超类的初始化或者子类的初始化即使静态域被子类或子接口或者它的实现类所引用。
示例1:(如上所述)

class Parent{
  static int p = 10;
  static{
      System.out.println("父类静态初始化块");  
  }
}
class Child extends Parent{ 
  static int c = 20; 
  static{
      System.out.println("子类静态初始化块");  
  }
} 
public class Test {    
  public static void main(String [] args){
      System.out.println(Child.p);    //静态域p被子类引用
  }
}



父类静态初始化块
10

此规则暂时还有点疑惑。

示例2:(满足类的初始化条件,父类也会被初始化,与示例1不同)

public class Test
{
    public static void main(String[] args)
    {
        System.out.println(Child.c);     //满足类的初始化条件,父类也会被初始化,与示例1不同
    }
}



父类静态初始化块
子类静态初始化块
20

 

4、如果一个静态变量是编译时常量,则对它的引用不会引起定义它的类的初始化。
示例1:

class Parent{
  static{
      System.out.println("父类静态初始化块");  
  }
}
class Child extends Parent{
  static final int x = 2005;  
  static{
      System.out.println("子类静态初始化块");  
  }
} 
public class Test {    
  public static void main(String [] args){
      System.out.println(Child.x); 
  }
}



输出:
2005

示例2:(I.i是一个编译时常量,因此它不会引起I被初始化。)

interface I {  
  int i = 1;  
  int ii = Test.out("ii", 2);  
}  
public class Test {    
  static int out(String s, int i) {  
      System.out.println(s   "="   i);  
      return i;  
  }  
  public static void main(String [] args){
      System.out.println(I.i);  
  }
}



输出:
1

5、初始化类是指初始化static静态变量和执行static静态代码块,所以只会进行一次。
初始化接口也只会进行一次。

示例1:

class Child extends Parent{ 
  static int c = 20; 
  static{
      System.out.println("子类静态初始化块");  
  }
} 
public class Test {    
  public static void main(String [] args){
      System.out.println(Child.c); 
      System.out.println(Child.c); 
  }
}



父类静态初始化块
子类静态初始化块
20
20

三、初始化时机(使用类引用任何静态成员之前对或者首次实例化类时会调用静态构造函数,通过静态构造函数初始化类级别的项,即静态字段。非静态的则要在创建对象的时候才初始化);

1.1 概念介绍:

一个类(class)要被使用必须经过装载,连接,初始化这样的过程。

  • 在装载阶段,类装载器会把编译形成的class文件载入内存,创建类相关的Class对象,这个Class对象封装了我们要使用的类的类型信息。
  • 连接阶段又可以分为三个子步骤:验证、准备和解析。
    • 验证就是要确保java类型数据格式 的正确性,并适于JVM使用。
    • 准备阶段,JVM为静态变量分配内存空间,并设置默认值,注意,这里是设置默认值,比如说int型的变量会被赋予默认值0 。在这个阶段,JVM可能还会为一些数据结构分配内存,目的是提高运行程序的性能,比如说方法表。
    • 解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用。这个阶段可以被推迟到初始化之后,当程序运行的过程中真正使用某个符号引用的时候 再去解析它。
  • 类的初始化:
    • 初始化类,是指初始化static静态变量和执行static静态代码块。
    • 初始化接口,是指初始化定义在该接口中的filed。

分分快三计划 2

2.3 对象创建过程的说明

  • 1、静态域的初始化是在类的初始化期间,非静态域的初始化时在类的实例创建期间。这意味这静态域初始化在非静态域之前。
  • 2、非静态域通过构造器初始化,子类在做任何初始化之前构造器会隐含地调用父类的构造器,这保证了父类非静态实例变量初始化早于子类。
  • 3、调用Class的类成员变量时,构造函数和成员变量不会执行

    public class Test {      public static void main(String [] args){       System.out.println(Parent.a);   } } class Parent{   public static int a = 10;   {       System.out.println("父类普通成员初始化块");   }   public Parent(){       System.out.println("父类构造函数");   } }

输出:10

 

  • 4、在类的内部,变量定义的先后顺序决定了初始化的顺序;
    即使变量定义散布于方法定义之间,它们仍会在任何方法(包括构造器)被调用之前得到初始化。

    public class Test {      public static void main(String [] args){       new Parent();   } } class Parent{   {       System.out.println("普通成员初始化块1");   }   public Parent(){       System.out.println("构造函数");   }   {       System.out.println("普通成员初始化块2");   } }

普通成员初始化块1
普通成员初始化块2
构造函数
  • 5、多态情况下的对象初始化

    public class Test {    public static void main(String [] args){     Parent parent = new Child(); } } class Parent{ {     System.out.println("父类普通成员初始化块"); }  static{     System.out.println("父类静态成员及初始化块"); }

    public Parent(){     System.out.println("父类构造函数"); } } class Child extends Parent{ {     System.out.println("子类普通成员初始化块"); }  static{     System.out.println("子类静态成员及初始化块"); }

    public Child(){     super();     System.out.println("子类构造函数"); } }

 

父类静态成员及初始化块
子类静态成员及初始化块
父类普通成员初始化块
父类构造函数
子类普通成员初始化块
子类构造函数

可见多态情况下的初始化与示例1中继承情况下的初始化是一样的,因为都是创建的子类的实例。

  • 6、程序的执行过程,即Java虚拟机执行Test 的静态方法main,这也会引起Test 类的初始化。(理解面向对象而非面向过程)

    public class Test {      public static void main(String [] args){     System.out.println("Test- main");   }

    java中类的起始化【分分快三计划】。  static{     System.out.println("静态成员及初始化块");   } }

静态成员及初始化块
Test- main

1.实例构造函数与静态构造函数执行顺序

1.2 类的初始化条件

类会在首次被“主动使用”时执行初始化,以下情况第一次发生之前类会被初始化:

  1. 创建类的实例;
  2. 调用类的静态方法;
  3. 调用类的静态变量,并且该变量不是一个常变量
  4. 为类的静态字段赋值;
  5. 在顶层类中执行assert语句;(?)
  6. 调用类Class及包java.lang.reflect中的某些反射方法;

JLS严格的说明:在任何其他的情况下,都不会对类或接口进行初始化。

分分快三计划 3

2.2 对象创建过程中初始化顺序

父静态成员>子静态成员>父普通成员初始化>父构造>子普通成员初始化>子构造.
( 静态初始化块以静态变量对待)

示例1:

public class Test {    
  public static void main(String [] args){
      new Child();
  }
}
class Parent{
  { 
      System.out.println("父类普通成员初始化块"); 
  }  
  static{
      System.out.println("父类静态成员及初始化块");
  }

  public Parent(){
      System.out.println("父类构造函数"); 
  }
}
class Child extends Parent{
  { 
      System.out.println("子类普通成员初始化块"); 
  }  
  static{
      System.out.println("子类静态成员及初始化块");
  }

  public Child(){
      super();
      System.out.println("子类构造函数"); 
  }
}



父类静态成员及初始化块
子类静态成员及初始化块
父类普通成员初始化块
父类构造函数
子类普通成员初始化块
子类构造函数

示例2:(证明 父构造>子普通成员初始化>子构造)

public class Test {    
  public static void main(String [] args){
      new Child();
  }
}
class Parent{
  public Parent(){
      System.out.println("父类构造函数"); 
      System.out.println("子类成员变量 height:"   ((Child)this).height);
  }
}
class Child extends Parent{
  public int height= 20;
  { 
      System.out.println("子类非静态成员初始化块"); 
      System.out.println("子类成员变量 height:"   this.height);
  }  

  public Child(){
      super();
      System.out.println("子类构造函数"); 
  }
}



父类构造函数
子类成员变量 height:0
子类非静态成员初始化块
子类成员变量 height:20
子类构造函数

一、初始化顺序(依次是静态变量、静态构造函数、实例变量、实例构造函数)

一、类的初始化

 

Java的初始化可以分为两个部分:
(a)类的初始化
(b)对象的创建

一、结论:

 

 

主函数:

二、例子:

 

父类:

跟实例构造函数的调用顺序刚好相反,所以创建子类实例时,其所有构造函数和变量的调用顺序为:

分分快三计划 4

 

二、初始化次数(静态的都只会初始化一次)

 

当我创建子类Dog的实例时,执行结果如下:

 

2.创建子类实例时,其所有构造函数和变量的调用顺序为:

 

 

子类静态变量初始化>子类静态构造函数>父类静态变量初始化 > 父类静态构造函数 > 父类实例变量初始化>父类实例构造函数 > 子类实例变量初始化>本身实例构造函数。

 

本文由分分快三计划发布,转载请注明来源

关键词: 分分快三计划 jvm 基本知识 Java技术 初始化