Java:初始化类、变量、程序块加载探讨

简介:
1.基本类型数据的初始值
InitialValues.java
public   class  InitialValues {
     boolean   t ;
     char   c ;
     byte   b ;
     short   s ;
     int   i ;
     long   l ;
     float   f ;
     double   d ;
 
     void  print(String s) {
       System. out .println(s);
    }
 
     void  printInitialValues() {
       print( "boolean  "  +  t );
       print( "char  "  +  c );
       print( "byte  "  +  b );
       print( "short  "  +  s );
       print( "int  "  +  i );
       print( "long  "  +  l );
       print( "float  "  +  f );
       print( "double  "  +  d );
    }
 
     public   static   void  main(String[] args) {
       InitialValues iv =  new  InitialValues();
       iv.printInitialValues();
    }
}
 
结果:
boolean  false
char  _
byte  0
short  0
int  0
long  0
float  0.0
double  0.0
2.变量初始化
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧在任何方法(包括构造器)被调用之前得到初始化。看下面的代码:
OrderOfInitialzation.java(执行顺序在代码中已标出,按类标注,罗马字母标注主类中执行顺序。)
class  Tag {
    Tag( int  marker) {
       System. out .println( "Tag("  + marker +  ")" );
     }
}
 
class  Card {
    Tag  t1  =  new  Tag(1); //  Ⅰ①
 
    Card() {
       System. out .println( "Card()" ); //  Ⅰ④
        t3  =  new  Tag(33); //  Ⅰ⑤
    }
 
    Tag  t2  =  new  Tag(2); //  Ⅰ②
 
     void  f() {
       System. out .println( "f()" ); //  Ⅱ⑥
    }
 
    Tag  t3  =  new  Tag(3); //  Ⅰ③
}
 
public   class  OrderOfInitialzation {
     public   static   void  main(String[] args) {
       Card t =  new  Card(); // 
       t.f(); // 
    }
}
 
结果:
Tag(1)
Tag(2)
Tag(3)
Card()
Tag(33)
f()
3.静态数据初始化
看下面的代码:
StaticInitialization .java
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)
由输出可见,静态初始化只有在必要时刻才会进行。如果不创建Table 对象,也不引用Table.b1Table.b2,那么静态的Bowl b1 b2 永远都不会被创建。只有在第一个Table 对象被创建(或者第一次访问静态数据)的时候,它们才会被初始化。此后,静态对象不会再次被初始化。
初始化的顺序是先“静态”,(如果它们尚未因前面的对象创建过程而被初始化),后“非静态”。从输出结果中可以观察到这一点。
4.静态块的初始化
Java 允许你将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫作“静态块”)。与其他静态初始化动作一样,这段代码仅执行一次:当你首次生成这个类的一个对象时,或者首次访问属于那个类的一个静态成员时(即便从未生成过那个类的对象)。看下面的代码:
class  Cup {
    Cup( int  marker) {
       System. out .println( "Cup("  + marker +  ")" );
    }
 
     void  f( int  marker) {
       System. out .println( "f("  + marker +  ")" );
    }
}
 
class  Cups {
     static  Cup  c1 ;
 
     static  Cup  c2 ;
     static  {
        c1  =  new  Cup(1);
        c2  =  new  Cup(2);
    }
 
    Cups() {
       System. out .println( "Cups()" );
    }
}
 
public   class  ExpilicitStatic {
     public   static   void  main(String[] args) {
       System. out .println( "Inside main()" );
       Cups. c1 .f(99); // (1)
    }
     // static Cups x=new Cups();//(2)
     // static Cups y=new Cups();//(2)
}
 
结果:
Inside main()
Cup(1)
Cup(2)
f(99)
无论是通过标为(1)的那行程序访问静态的 c1对象,还是把(1)注释掉,让它去运行标为(2) 的那行,Cups 的静态初始化动作都会得到执行。如果把(1)(2)同时注释掉,Cups 的静态初始化动作就不会进行。此外,激活一行还是两行(2)代码都无关紧要,静态初始化动作只进行一次。
5.非静态实例初始化
看下面的代码:
class  Mug {
    Mug( int  marker) {
        System. out .println( "Mug("  + marker +  ")" );
     }
 
     void  f( int  marker) {
       System. out .println( "f("  + marker +  ")" );
    }
}
 
public   class  Mugs {
    Mug  c1 ;
 
    Mug  c2 ;
    {
        c1  =  new  Mug(1);
        c2  =  new  Mug(2);
       System. out .println( "c1&c2 initialized" );
    }
 
    Mugs() {
       System. out .println( "Mugs()" );
    }
 
     public   static   void  main(String[] args) {
       System. out .println( "Inside main()" );
        new  Mugs();
       System. out .println( "===new Mugs again===" );
        new  Mugs();
    }
}
 
结果:
Inside main()
Mug(1)
Mug(2)
c1&c2 initialized
Mugs()
===new Mugs again===
Mug(1)
Mug(2)
c1&c2 initialized
Mugs()
从结果可以看到,非静态的代码块被执行了2次。所以只要实例化一个类,该类中的非静态代码块就会被执行一次。
6.数组初始化
注意区分基本类型数据与类数据的初始化。看以下代码:
int [] a; 
a =  new   int [rand.nextInt(20)];
Integer[] a =  new  Integer[rand.nextInt(20)]; 
for ( int  i = 0; i < a. length ; i++) { 
     a[i] =  new  Integer(rand.nextInt(500)); 
}
对于类数据类型的初始化,每个数组子成员都要重新new一下。
7.涉及继承关系的初始化
当创建一个导出类的对象时,该对象包含了一个基类的子对象。看下面代码:
class  Art {
    Art() {
       System. out .println( "Art constructor" );
    }
}
 
class  Drawing  extends  Art {
    Drawing() {
       System. out .println( "Drawing constructor" );
    }
}
 
public   class  Cartoon  extends  Drawing {
     public  Cartoon() {
       System. out .println( "Cartoon constructor" );
    }
 
     public   static   void  main(String[] args) {
        new  Cartoon();
    }
}
 
结果:
Art constructor
Drawing constructor
Cartoon constructor
可以发现,构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。
如果类没有缺省的参数,或者想调用一个带参数的基类构造器,就必须用关键字super显示地调用基类构造器的语句,并且配以适当的参数列表。看下面代码:
class  Game {
    Game( int  i) {
       System. out .println( "Game constructor" );
    }
}
 
class  BoardGame  extends  Game {
    BoardGame( int  i) {
        super (i);
       System. out .println( "BoardGame constructor" );
    }
}
 
public   class  Chess  extends  BoardGame {
    Chess() {
        super (11);
       System. out .println( "Chess constructor" );
    }
 
     public   static   void  main(String[] args) {
        new  Chess();
    }
}
 
结果:
Game constructor
BoardGame constructor
Chess constructor
如果不在BoardGame()中调用基类构造器,编译器将无法找到符合Game()形式的构造器。而且,调用基类构造器必须是你在导出类构造器中要做的第一件事。
8.构造器与多态
8.1构造器的调用顺序
基类的构造器总是在导出类的构造过程中被调用的,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊的任务:检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常是private类型)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。这正是编译器为什么要强制每个导出类部分都必须调用构造器的原因。
 
8.2构造器内部的多态方法的行为
看下面代码:
abstract   class  Glyph {
     abstract   void  draw();
 
    Glyph() {
       System. out .println( "Glyph() before draw()" );
       draw();
       System. out .println( "Glyph() after draw()" );
    }
}
 
class  RoundGlyph  extends  Glyph {
     private   int   radius  = 1;
 
    RoundGlyph( int  r) {
        radius  = r;
       System. out .println( "RoundGlyph.RoundGlyph(),radius="  +  radius );
    }
 
     void  draw() {
       System. out .println( "RoundGlyph.draw(),radius="  +  radius );
    }
}
 
public   class  PolyConstructors {
     public   static   void  main(String[] args) {
        new  RoundGlyph(5);
    }
}
 
结果:
Glyph() before draw()
RoundGlyph.draw(),radius=0
Glyph() after draw()
RoundGlyph.RoundGlyph(),radius=5
Glyph中,draw()方法是抽象的,这样设计是为了覆盖该方法。我们确实在RoungGlyph中强制覆盖了draw()。但是Glyph构造器会调用这个方法,结果导致了对RoundGlyph.draw()的调用,这看起来似乎是我们的目的。但是如果看到输出结果,我们会发现当Glyph的构造器调用draw()方法时,radius不是默认初始值1,而是0
解决这个问题的关键是初始化的实际过程:
1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制零。
2)如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0
3)按照声明的顺序调用成员的初始化方法。
4)调用导出类的构造器主体。
9.初始化及类的加载
看以下代码:
class  Tag {
    Tag( int  marker) {
       System. out .println( "Tag("  + marker +  ")" );
     }
}
 
class  Insect {
     private   int   i  = 9;
 
     protected   int   j m ;
 
    Insect() {
       System. out .println( "i = "  +  i  +  ", j = "  +  j );
        j  = 39;
    }
 
     private   static   int   x1  = print( "static Insect.x1 initialized" );
 
     static   int  print(String s) {
       System. out .println(s);
        return  47;
    }
 
    Tag  t1  =  new  Tag(1);
}
 
public   class  Beetle  extends  Insect {
     private   int   k  = print( "Beetle.k initialized" );
 
     public  Beetle() {
       System. out .println( "k = "  +  k );
       System. out .println( "j = "  +  j );
       System. out .println( "m = "  +  m );
    }
 
     private   static   int   x2  = print( "static Beetle.x2 initialized" );
 
     public   static   void  main(String[] args) {
       System. out .println( "Beetle constructor" );
       Beetle b =  new  Beetle();
    }
 
    Tag  t2  =  new  Tag(2);
}
 
结果:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
Tag(1)
i = 9, j = 0
Beetle.k initialized
Tag(2)
k = 47
j = 39
m = 0
你在Beetle 上运行Java 时,所发生的第一件事情就是你试图访问Beetle.main( ) (一个static 方法),于是加载器开始启动并找出 Beetle 类被编译的程序代码(它被编译到了一个名为Beetle .class 的文件之中)。在对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字 extends  告知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生。
如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的静态初始化(在此例中为Insect)即会被执行,然后是下一个导出类,以此类推。这种方式很重要,因为导出类的静态初始化可能会依赖于基类成员能否被正确初始化的。
至此为止,必要的类都已加载完毕(静态变量和静态块),对象就可以被创建了。
首先,对象中所有的原始类型都会被设为缺省值,对象引用被设为null——这是通过将对象内存设为二进制零值而一举生成的。然后,基类的构造器会被调用。在本例中,它是被自动调用的。但你也可以用super 来指定对基类构造器的调用。基类构造器和导出类的构造器一样,以相同的顺序来经历相同的过程,即向上寻找基类构造器。在基类构造器完成之后(即根部构造器找到之后),实例变量(instance variables )按其次序被初始化(注意观察代码中的Tag())。最后,构造器的其余部分被执行。


本文转自zhangjunhd51CTO博客,原文链接:http://blog.51cto.com/zhangjunhd/20927,如需转载请自行联系原作者
相关文章
|
2月前
|
安全 Java
Java异常处理:程序世界的“交通规则
Java异常处理:程序世界的“交通规则
319 98
|
3月前
|
安全 Java 数据建模
Java记录类:简化数据载体的新选择
Java记录类:简化数据载体的新选择
237 101
|
2月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
102 4
|
2月前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
168 5
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
160 1
|
2月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
216 1
|
2月前
|
存储 Java 程序员
【Java】(6)全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
java.util 包提供了 Date 类来封装当前的日期和时间。Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。Date( )第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
156 1
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
177 1
|
2月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
288 5