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.b1或Table.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())。最後,構造器的其余部分被執行。
本文出自 “子 孑” 博客,請務必保留此出處http://zhangjunhd.blog.51cto.com/113473/20927