Java是一種面向對象設計的高級語言,支持繼承、封裝和多態三大基本特征,首先我們從面向對象兩大概念:類和對象(也稱為實例)談起。來看看最基本的類定義語法:
/*命名規則: *類名(首字母大寫,多個單詞組合時每個單詞首字母大寫,單詞之間不加任何連接符號) *字段名、方法名(首字母小寫,多個單詞組合時第一個單詞首字母小寫,之后每個單詞首字母大寫,單詞之間不加任何連接符號) */ [public][final] class 類名 { [public|protected|private 類名() {}] //構造器 [public|protected|private][static|final] 類型 字段名 [=默認值];//字段列表 [public|protected|private][static|final|abstract] 返回值 方法名(參數) {} //方法列表 }
從以上的語法中發現幾個知識點:(1).構造器名稱為類名相同且沒有任何返回值(甚至都不能返回void);(2).類的修飾符要么為public,要么沒有;(3).字段可以添加默認值;(4).方法的修飾符中final和abstract不能同時使用;(5).字段與方法都可以使用static進行修飾。基於這些,我先解釋1,3,5,剩下的將在后續的講解中逐漸涉及到。首先為什么構造器沒有任何返回值呢?實質上,在Java中當利用new來調用構造器時是有返回值的,總是返回當前類的實例,因此無須定義返回值類型,但也不能顯式使用return來返回,因為構造器的返回值是隱式的。其次,可以在聲明字段的同時為之添加默認值,如未添加系統會自動添加默認值。最后,使用static修飾的字段和方法我們稱為類字段和類方法,可以使用類和實例來調用,但是在類方法中不能訪問任何實例字段和實例方法(即沒有static修飾)。
利用類的構造器來創建類的實例,如未提供構造器,系統將默認提供一個無參構造器,如提供構造器,系統則不會提供默認構造器(因此自定義構造器的同時建議添加一個無參構造器)。但請注意:構造器雖然是創建對象的重要途徑,但是不完全負責對象的創建,實際上當調用構造器時,系統會為這個對象分配內存空間並執行默認初始化,即在構造器執行之前這個對象就已經產生了,只是還不能被外界訪問,只能在構造器中以this來引用,當構造器執行完畢這個對象作為其返回值返回,可被外界訪問或賦值給其他的引用變量。
class Person { public String name; public int age; public Person() {} public Person(String n, int a) { name = n; age = a; } public void display() { System.out.println("Name: " + name + ", Age: " + age); } } public class TestPerson { public static void main(String[] args) { Person p = new Person(); p.display();//Name: null, Age: 0 p.name = "Miracle"; p.age = 28; //p = new Person("Miracle", 28); p.display();//Name: Miracle, Age: 28 } }
與上篇提到過的數組一樣,類也是引用類型,以上Person類定義的引用變量p存放在棧內存中(只是存放實際Person對象的地址,沒有任何實際數據,類似於C的指針),而實際的Person對象存放在堆內存中,下圖顯示了Person對象初始化完畢后的內存分布情況。

class Person { public String name; public int age; public Person() {} //重載構造器之間調用時,需使用this public Person(String name) { this(name, 0); } //構造器中形參與字段同名,需使用this public Person(String name, int age) { this.name = name; this.age = age; } //方法中形參name與字段name同名,需使用this public void changeName(String name) { this.name = name; //方法中還引用實例方法display,此時可使用this,也可不使用 this.display(); //display(); } public void display() { System.out.println("Name: " + name + ", Age: " + age); } } public class TestPerson { public static void main(String[] args) { Person p = new Person("Miracle", 28); p.display(); p.changeName("Miracle He"); } }
方法是類或對象行為特征的抽象,跟C語言的函數相似,但也有顯著的不同。方法必須屬於類或對象,只能在類中定義,函數則是結構化語言的組成單元。方法可以采用static進行定義,代表這個方法是屬於類或對象,但在static方法中不能調用this或其他非static方法或字段。Java方法的參數傳遞方式只有一種:值傳遞,即將實參的副本傳遞給方法,參數本身不發生任何變化。先看看基本類型參數的傳遞示例:
public class TestPassPrimitiveArgs { public static void swap(int a, int b) { System.out.println("交換前,a = " + a + ", b = " + b);//a = 3,b = 5 int temp = a; a = b; b = temp; System.out.println("交換后,a = " + a + ", b = " + b);//a = 5,b = 3 } public static void main(String[] args) { int a = 3; int b = 5; swap(a, b); System.out.println("交換結束后,a = " + a + ", b = " + b);//a = 3,b = 5 } }
從運行結果來看,在swap()中交換之前是3和5,交換后變成5和3,而實參在main()中始終變成不變,因此在main()傳遞給swap()的實參只是a和b的副本,而不是a和b本身。我們以內存分布來說明執行狀況,當在main()中傳參給swap()時,實際上就是在main()方法棧區向swap()方法棧區傳遞一份a和b的副本,如下圖:

當執行swap()時,swap()方法棧區將a和b副本進行交換,交換完成后進入main()方法棧區,此時僅僅a和b的副本發生改變,其本身沒有發生任何變化。接下來我們來看看引用類型的交換,前面我們說了只能通過值傳遞的方式來傳參,可能對有些朋友來說稍顯疑惑。
class DataSwap { public int a; public int b; } public class TestPassReferenceArgs { public static void swap(DataSwap ds) { System.out.println("交換前,ds.a = " + ds.a + ", ds.b = " + ds.b);//ds.a = 3, ds.b = 5 int temp = ds.a; ds.a = ds.b; ds.b = temp; System.out.println("交換后,ds.a = " + ds.a + ", ds.b = " + ds.b);//ds.a = 5, ds.b = 3 } public static void main(String[] args) { DataSwap ds = new DataSwap(); ds.a = 3; ds.b = 5; swap(ds); System.out.println("交換結束后,ds.a = " + ds.a + ", ds.b = " + ds.b);//ds.a = 5, ds.b = 3 } }
從運行結果來看,確實不僅在swap中交換成功,在main中仍然是交換之后的結果。讓人一下覺得:從main中傳遞給swap似乎不是ds對象的副本了,而是ds本身,這與我們前面談到的Java方法傳參只能按值傳遞相違背了,下面我詳細說明一下。我們都知道,此時傳遞的是引用類型DataSwap,而引用類型的內存方式已經談過了,在main()方法棧區中實際存放的是ds對象的地址,而實際的數據(a,b)是存放在堆內存中。現在將ds對象引用由main傳遞給swap,實際上是ds對象的地址復制一份到swap方法棧區中,此時main和swap中都已擁有ds對象的地址,且都指向在堆內存中實際存放的數據。也就是說引用類型參數數據傳遞方式是不折不扣的值傳遞方式,只不過傳遞的僅僅是引用變量,而不是引用變量所指向的引用類型數據。當然這里對main或swap中任何一個ds對象數據的更改都會影響到另一方,同時我們還可以驗證main和swap中的ds是兩個不同的引用變量,試着在swap種方法最后添加: ds=null.也就是切斷swap中對ds的引用,查看一下main中ds對象的a和b是否受到影響(結果是不會)。


接下來,談一談可變參數的實現方式(在類型后添加三個點...),即形參可以輸入任意個參數(類似於C#的params),在看實際例子前,需要說明:可變參數必須是方法的最后一個參數,且最多只有一個可變參數,相當於傳入了一個對應類型的數組(只是長度可變)。
public class TestVarityArgs { public static void readBooks(String name, String... books) { if(books.length == 0) { System.out.println(name + " has not a book to read"); } else { String result = name + " is reading: "; for(String book : books) { result += book + " "; } System.out.println(result); } } public static void main(String[] args) { readBooks("Miracle"); readBooks("Miracle", "Java", ".Net", "J2EE"); readBooks("Miracle", new String[] { "Java", ".Net", "J2EE" }); } }
談到了可變參數,似乎跟重載函數非常相似,都是同一個方法有多種調用形式,但是它們有着顯著的區別。重載函數必須滿足"兩同一不同":同一個的重載方法名的必須相同,但是形參列表不同(返回值、修飾符不能作為重載的標准)。請注意,盡量別對包含可變參數的方法進行重載,因為這樣可能會引起歧義。
public void readBooks(String name) { //... } public void readBooks(String name, String book) { //... } public boolean readBooks(String name, String[] books, int count) { //... }
我不知道是否有朋友會問返回值為什么不能作為重載的標准呢?假設現在有以下兩個重載方法:int f(){}、void f() {},當執行調用時:int r = f();很明顯是調用前者,但是如果沒有將函數結果賦值呢?直接調用f(),此時你可能就不知道該調用誰了,當然java編譯器比你還糊塗,更不知道怎么辦了,因此返回值不能作為重載的標准。
前面一直提到static這個概念,接下來我以例子來說明它的應用,可以看出static和非static字段和方法的區別所在。
class Person { public String name; public int age; public static int courses = 2; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } public static void AddCourse(int count) { //在static方法中只能訪問static字段,不能訪問實例字段 //System.out.println(name + " 's course: "); courses += count; } public void display() { //在實例方法中可以訪問static字段 System.out.println("Name: " + name + ", Age: " + age + ", Course: " + courses); } } public class TestPerson { public static void main(String[] args) { Person p = new Person("Miracle", 28); p.display(); //static方法可通過類調用,也可通過實例調用,調用效果一致,會對該類的所有實例產生影響 Person.AddCourse(1);//p.AddCourse(1); p.display(); //p1.courses現在也變成3 Person p1 = new Person("Miracle He", 28); p1.display(); } }
那在實際開發中,怎樣例區分static和非static的引用呢?簡單的建議是:如果定義的變量是用來描述每個對象的固有信息(如每個人都有姓名、年齡),則應該使用實例變量,相反如果描述的類的固有信息(如只要是人就只能有兩只眼睛),則應該使用類變量。
不是說面向對象有三大特征嗎?封裝、繼承、多態。那到底是怎么回事呢?首先封裝,就是將對象的屬性等信息隱藏在類的內部,僅提供給外部一些滿足預設條件的方法供調用。拿上面的例子來說明:每個人的年齡只能在0~150之間來進行浮動,現在的情況是我可以隨意更改年齡(想多少歲就多少歲),那肯定就不對了。我們必須將這些不滿足條件的操作及時的過濾掉,Java提供了訪問權限控制: private->default->protected->public(權限依次擴大)來封裝內部屬性和提供外部接口(對字段采用private或protected等修飾符來限制,采用getter和setter來進行有效控制)。
class Person { private String name; private int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return this.name; } public void setName(String name) { if(name.length() < 2 || name.length() > 20) { System.out.println("你設置的名字不合法"); return; } else { this.name = name; } } public int getAge() { return this.age; } public void setAge(int age) { if(age < 0 || age > 100) { System.out.println("你設置的年齡不合法"); return; } else { this.age = age; } } public void display() { System.out.println("Name: " + name + ", Age: " + age); } } public class TestAccessControl { public static void main(String[] args) { Person p = new Person("Miracle", 28); p.display(); //p.age = 150;//此時age已經是private的,無法訪問 p.setAge(150);//這里已經不合法了,無法修改 p.setName("Miracle He"); p.setAge(35); p.display(); } }
關於訪問控制符,有以下建議:類的絕大部分字段(有些少數的static字段需要public修飾)和輔助方法都采用private來修飾,並提供getter和setter訪問器來對其讀取和修改;如果值希望同一個包(即將講到)中其他類訪問,則不添加任何修飾符;如果只希望子類也能使用父類的成員而不被外界知曉,則采用protected來修飾;如果可以在任何地方都能訪問到,則采用public來修飾。
接下來,將討論上文中談到的包。所謂包,就是為不同特征的類隔離起來,即使這些彼此隔離的包中包含同名的類也無所謂(就像在同一個班級中有兩個都叫"Miracle"的同學,老師會叫其中一個"Older Miracle",另一個叫"Little Miracle"來區分)。一般一個類中只能包含在一個包中,且該語句只能為非注釋語句的第一句。
/*命名規則: *包名(全部小寫,以公司或項目組織的順序倒寫,中間以.分隔,如: miracle.java.basic) *類名(首字母大寫,多個單詞組合時每個單詞首字母大寫,單詞之間不加任何連接符號) *字段名、方法名(首字母小寫,多個單詞組合時第一個單詞首字母小寫,之后每個單詞首字母大寫,單詞之間不加任何連接符號) */ package 包名;
[public][final] class 類名 [public|protected|private 類名() {}] //構造器 [public|protected|private][static|final] 類型 字段名 [=默認值];//字段列表 [public|protected|private][static|final|abstract] 返回值 方法名(參數) {} //方法列表 }
位於包中的類,在文件系統中也必須保持與包名相同層次的目錄結構。
package miracle.java.basic; public class Person { private String name; private int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } public void display() { System.out.println("Name: " + name + ", Age: " + age); } }
import miracle.java.basic.Person; public class TestPerson { public static void main(String[] args) { Person p = new Person("Miracle", 28); p.display(); } }
我設計的項目組織結構如下圖:

cd E:\project\src javac -d ..\bin TestPerson.java java -classpath ..\bin TestPerson (或: java -cp ..\bin TestPerson)
運行完畢之后,我們發現源文件和字節碼文件已經徹底分離了,並且在bin文件夾中還生成了跟src文件夾中Person類指定包對應的文件夾目錄(細心的朋友可能發現跟IDE的目錄結構有點相似了,是的我們今天又進步了一點)。在竊喜之余,我們發現在主類中加入了import miracle.java.basic.Person, 這條語句相對於package而言的,正因為我們建立了包,當需要在其他類中引用包中的類時,則需要使用import引入以方便書寫,不然則需要帶全名稱,這樣既繁瑣又不具可讀性。
miracle.java.basic.Person p = new miracle.java.basic.Person("Miracle", 28);
不過有時還必須使用全名稱來書寫。常見的java包:java.lang、java.util、java.net、java.io、java.text、java.sql、java.awt、java.swing等。
import java.sql.*; import java.util.*; public class TestPackage { public static void main(String[] args) { //Date d = new Date();//錯誤,此時真的不明確到底是sql下的Date還是util下的Date //正確寫法 java.sql.Date sd = new java.sql.Date(); java.util.Date ud = new java.util.Date(); } }
此外,Java還是import static語法用來引入指定包下所有的靜態成員。
import static java.lang.System.*; import static java.lang.Math.*; public class TestPackage { public static void main(String[] args) { out.println(PI); } }
再次擴展java類的定義格式:
/*命名規則: *包名(全部小寫,以公司或項目組織的順序倒寫,中間以.分隔,如: miracle.java.basic) *類名(首字母大寫,多個單詞組合時每個單詞首字母大寫,單詞之間不加任何連接符號) *字段名、方法名(首字母小寫,多個單詞組合時第一個單詞首字母小寫,之后每個單詞首字母大寫,單詞之間不加任何連接符號) */ package 包名; import|import static 包名.類名|*;//包名.類名代表僅引入該包指定的類,包名.*代表引入該包所有的類或靜態成員 [public][final] class 類名 { [public|protected|private 類名() {}] //構造器 [public|protected|private][static|final] 類型 字段名 [=默認值];//字段列表 [public|protected|private][static|final|abstract] 返回值 方法名(參數) {} //方法列表 }
接下來,我們將探討面向對象的另外兩大特征:繼承和多態。其實這兩者是相互關聯的,繼承就是在已有類的基礎上擴展新的子類,而不改變原有父類的數據和行為,即我們通常所說的父類和子類(遵從"子類 is a 父類"原則),子類可繼承父類的非私有成員(建議將成員修飾符改為protected),同時也可重寫父類相同的成員(遵從"兩同兩小一大"原則:即方法名相同,方法形參相同;子類方法返回類型比父類更小或相等,子類方法拋出的異常比父類更小或相等;子類方法的訪問權限比父類更大或相等,重寫的方法要么都是類方法,要么都是實例方法,如父類有一個實例方法,子類添加一個同名的類方法則不算重寫,而是子類的新方法)。
package miracle.java.basic; public class Person { protected String name; protected int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; System.out.println("調用Person的構造器"); } public String getName() { return this.name; } public void setName(String name) { if(name.length() < 2 || name.length() > 20) { System.out.println("你設置的名字不合法"); return; } else { this.name = name; } } public int getAge() { return this.age; } public void setAge(int age) { if(age < 0 || age > 100) { System.out.println("你設置的年齡不合法"); return; } else { this.age = age; } } public void display() { System.out.println("Name: " + name + ", Age: " + age); } }
package miracle.java.basic; public class Student extends Person { private int grade; public Student() {} public Student(String name, int age) { super(name, age); } public int getGrade() { return this.grade; } public void setGrade(int grade) { if(grade < 0 || grade > 100) { System.out.println("你設置的分數不合法"); return; } else { this.grade = grade; } } public void display() { System.out.println("Name: " + name + ", Age: " + age + ", Grade: " + grade); } /* public static void display() { //不能算作重寫,只是Student的新方法 } */ }
import miracle.java.basic.*; public class TestInheritance { public static void main(String[] args) { Student miracle = new Student("Miracle", 28); miracle.setGrade(85); miracle.display(); } }
如果在子類中先調用已重寫的父類方法,該怎么辦呢?Java提供了super引用,指向其直接父類的默認引用。當創建一個對象時,系統會隱式創建其父類的對象(Java所有類都繼承自java.lang.Object),只要該類有子類存在,就一定會產生super引用,指向其對應的直接父類,當子類方法中使用某個成員變量時,首先會查找當前類中是否存在,如不存在則查找直接父類中是否存在,如不存在會依次追溯到java.lang.Object中是否存在,如仍然不存在將不能通過編譯。跟前面的this引用很類似,都不能在類方法中引用,只不過this是指向當前子類的對象而已。除此之外,super還能在子類構造器中調用其父類的構造器,如實例化子類的對象,會依次調用所有上層父類的構造器,最后才調用自身的構造器完成對象的構建。如子類調用父類已重寫的類方法,則使用父類.方法()來完成。
package miracle.java.basic; public class Person { protected String name; protected int age; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; System.out.println("調用Person的構造器"); } public String getName() { return this.name; } public void setName(String name) { if(name.length() < 2 || name.length() > 20) { System.out.println("你設置的名字不合法"); return; } else { this.name = name; } } public int getAge() { return this.age; } public void setAge(int age) { if(age < 0 || age > 100) { System.out.println("你設置的年齡不合法"); return; } else { this.age = age; } } public void display() { System.out.println("Name: " + name + ", Age: " + age); } public static void notice(int age) { if(age >= 18) { System.out.println("今天看電影"); } else { System.out.println("兒童不宜參加"); } } }
package miracle.java.basic; public class Student extends Person { private int grade; public Student() {} public Student(String name) { this(name, 0); } public Student(String name, int age) { super(name, age);//只能為第一句且不能跟this混用 System.out.println("調用Student的構造器"); } public int getGrade() { return this.grade; } public void setGrade(int grade) { if(grade < 0 || grade > 100) { System.out.println("你設置的分數不合法"); return; } else { this.grade = grade; } } public void display() { super.display(); System.out.println("Name: " + name + ", Age: " + age + ", Grade: " + grade); } public static void notice(int age) { System.out.print("看電影了:"); Person.notice(age); System.out.println("\n"); } }
import miracle.java.basic.*; public class TestInheritance { public static void main(String[] args) { Student miracle = new Student("Miracle", 28); miracle.setGrade(85); miracle.display(); Student.notice(miracle.getAge()); Student miracleHe = new Student("Miracle He", 16); miracleHe.display(); Student.notice(miracleHe.getAge()); } }
現在我們將程序做簡單的改動:Person miracle = new Student("Miracle", 28);

package miracle.java.basic; public class Person { protected String name; protected int age; public int height = 150; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return this.name; } public void setName(String name) { if(name.length() < 2 || name.length() > 20) { System.out.println("你設置的名字不合法"); return; } else { this.name = name; } } public int getAge() { return this.age; } public void setAge(int age) { if(age < 0 || age > 100) { System.out.println("你設置的年齡不合法"); return; } else { this.age = age; } } public void display() { System.out.println("Name: " + name + ", Age: " + age); } }

package miracle.java.basic; public class Student extends Person { private int grade; public int height = 170; public Student() {} public Student(String name) { this(name, 0); } public Student(String name, int age) { super(name, age); } public int getGrade() { return this.grade; } public void setGrade(int grade) { if(grade < 0 || grade > 100) { System.out.println("你設置的分數不合法"); return; } else { this.grade = grade; } } public void display() { System.out.println("Name: " + name + ", Age: " + age + ", Grade: " + grade); } }
import miracle.java.basic.*; public class TestPolymorphic { public static void main(String[] args) { //miracle編譯時為Person,運行時為Student Person miracle = new Student("Miracle", 28); //miracle.setGrade(85); miracle.display();//方法具備多態性 System.out.println("Height: " + miracle.height);//150,字段不具備多態性 } }
從代碼中可以看出:當引用變量的編譯時類型和運行時類型不一致(父類 t = new 子類();)時,我們說表現出了對象的多態。而多態僅僅表現在調用重寫方法時,將調用子類中的方法,調用非重寫方法時,如果該方法在父類中將調用父類,如果在子類中將無法調用,而對象的字段不具多態性,即只能調用父類中對應的字段。回憶前面幾章講到的數據類型轉換,這里將子類轉化為父類(向上轉型)是成功的;相反向下轉型(父類轉化為子類)時,則不一定成功,必須強制類型轉換,但在轉換之前為了防止出現ClassCastException異常,建議使用instanceof運算符(類似於C#的is)判斷是否成功,如成功才執行強制轉換。
if(miracle instanceof Student) { Student stu = (Student)miracle; miracle.display(); }
提到繼承,就不得不提到組合這個概念。所謂組合,即將已有類以引用的方式嵌入到另一個類(整體類和部分類之分,遵從"整體類 has a 部分類"原則),以達到在整體類(類似子類)中復用部分類(類似父類)的功能。從類的關系角度來看,繼承是從多個子類中提取共有父類的過程;組合是從多個整體類中提取嵌入類的過程。

package miracle.java.basic; public class Person { protected String name; protected int age; public int height = 150; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return this.name; } public void setName(String name) { if(name.length() < 2 || name.length() > 20) { System.out.println("你設置的名字不合法"); return; } else { this.name = name; } } public int getAge() { return this.age; } public void setAge(int age) { if(age < 0 || age > 100) { System.out.println("你設置的年齡不合法"); return; } else { this.age = age; } } public void display() { System.out.println("Name: " + name + ", Age: " + age); } }

package miracle.java.basic; public class Club { private Person[] members; public Club() { this.members = new Person[]{}; } public Club(Person[] members) { this.members = members; } public void playGame() { for(Person p : members) { p.display(); if(p.getAge() >= 18) { System.out.println("可以上網"); } else { System.out.println("好好學習"); } } } }
import miracle.java.basic.*; public class TestComposition { public static void main(String[] args) { Club c = new Club(new Person[] { new Person("Miracle", 28), new Person("Miracle He", 16) }); c.playGame(); } }
可看出Club與Person之間沒有任何相似的關系,只是Club中由很多Person組成,因此是組合關系。
到現在為止,基本講完了面向對象的三大特性,在結束本篇講解之前,簡單談一下"初始化塊"的應用。所謂初始化塊,就是在構造器執行之前,對整個類(所有對象)的字段進行初始化的過程,通常會將多個構造器中相同的部分放到初始化塊中執行,可以把初始化塊看成是沒有形參的方法,只不過在構造器執行之前執行而已。與構造器執行順序一致,初始化塊也遵循從父類到子類依次執行的過程。與初始化塊對應的還有靜態初始化塊,主要完成類屬性的初始化,並且只在類加載時初始化一次。

package miracle.java.basic; public class Parent { static { a = 4; System.out.println("Parent的靜態初始化塊執行"); } { b = 4; System.out.println("Parent的初始化塊執行"); } public static int a = 2; public int b = 3; public Parent() { System.out.println("Parent的構造器執行"); } }

package miracle.java.basic; public class Child extends Parent { static { //a = 10; System.out.println("Child的靜態初始化塊執行"); } { b = 6; System.out.println("Child的初始化塊執行"); } public Child() { System.out.println("Child的構造器執行"); } }
import miracle.java.basic.*; public class TestInitBlock { public static void main(String[] args) { for(int i = 0; i < 2; i++) { Child c = new Child(); System.out.println("a: " + Parent.a + ", b: " + c.b); } } } /* 執行結果: * Parent的靜態初始化塊執行 * Child的靜態初始化塊執行 * Parent的初始化塊執行 * Parent的構造器執行 * Child的初始化塊執行 * Child的構造器執行 * a: 2, b: 6 * Parent的初始化塊執行 * Parent的構造器執行 * Child的初始化塊執行 * Child的構造器執行 * a: 2, b: 6 */
從運行結果來看:靜態初始化塊和初始化塊都先於構造器執行,並都遵從父類到子類的執行過程,但靜態初始化塊最先執行且僅執行一次,子類初始化塊在父類的初始化塊和構造器執行完畢之后,在子類構造器之前執行。最后給出完整版的Java類定義格式:
/*命名規則: *包名(全部小寫,以公司或項目組織的順序倒寫,中間以.分隔,如: miracle.java.basic) *類名(首字母大寫,多個單詞組合時每個單詞首字母大寫,單詞之間不加任何連接符號) *字段名、方法名(首字母小寫,多個單詞組合時第一個單詞首字母小寫,之后每個單詞首字母大寫,單詞之間不加任何連接符號) */ package 包名; import|import static 包名.類名|*;//包名.類名代表僅引入該包指定的類,包名.*代表引入該包所有的類或靜態成員 [public][final] class 類名 { static { //靜態初始化塊 } { //初始化塊 } [public|protected|private 類名() {}] //構造器 [public|protected|private][static|final] 類型 字段名 [=默認值];//字段列表 [public|protected|private][static|final|abstract] 返回值 方法名(參數) {} //方法列表 }
OK,到此為止,面向對象(上):封裝、繼承、多態就完全講解完畢,建議讀者把文章中代碼依次執行,領悟其中的要點。下一篇還將繼續面向對象(下):內部類、抽象類及接口。