一、方法的重寫。
1、重寫只能出現在繼承關係之中。當一個類繼承它的父類方法時,都有機會重寫該父類的方法。一個特例是父類的方法被標識為final。重寫的主要優點是能夠定義某個子類型特有的行為。
class Animal {
public void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
}
2、對於從父類繼承來的抽象方法,要麼在子類用重寫的方式設計該方法,要麼把子類也標識為抽象的。所以抽象方法可以說是必須要被重寫的方法。
3、重寫的意義。
重寫方法可以實現多態,用父類的引用來操縱子類對象,但是在實際運行中對象將運行其自己特有的方法。
public class Test {
public static void main (String[] args) {
Animal h = new Horse();
h.eat();
}
}
class Animal {
public void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
public void buck(){
}
}
一個原則是:使用了什麼引用,編譯器就會只調用引用類所擁有的方法。如果調用子類特有的方法,如上例的h.buck(); 編譯器會抱怨的。也就是說,編譯器只看參考型別,而不是物件類型。
4、重寫方法的規則。
若想實現一個合格重寫方法,而不是重載,那麼必須同時滿足下面的要求!
A、重寫規則之一:重寫方法不能比被重寫方法限制有更嚴格的存取層級。
(但是可以更廣泛,比如父類方法是包存取權限,子類的重寫方法是public存取權限。)
比如:Object類有個toString()方法,開始重寫這個方法的時候我們總容易忘記public修飾符,編譯器當然不會放過任何教訓我們的機會。出錯的原因就是:沒有加任何存取修飾詞的方法具有包存取權限,包存取權限比public當然要嚴格了,所以編譯器會報錯的。
B、重寫規則之二:參數列表必須與被重寫方法的相同。
重寫有個孿生的弟弟叫重載,也就是後面要出場的。如果子類方法的參數與父類對應的方法不同,那麼就是你認錯人了,那是重載,不是重寫。
C、重寫規則之三:傳回型別必須與被重寫方法的傳回型別相同。
父類方法A:void eat(){} 子類方法B:int eat(){} 兩者雖然參數相同,可是傳回型別不同,所以不是重寫。
父類方法A:int eat(){} 子類方法B:long eat(){} 傳回型別雖然相容父類,但是不同就是不同,所以不是重寫。
D、重寫規則之四:重寫方法不能拋出新的異常或者比被重寫方法聲明的檢查異常更廣的檢查異常。但是可以拋出更少,更有限或者不拋出異常。
import java.io.*;
public class Test {
public static void main (String[] args) {
Animal h = new Horse();
try {
h.eat();
}
catch (Exception e) {
}
}
}
class Animal {
public void eat() throws Exception{
System.out.println ("Animal is eating.");
throw new Exception();
}
}
class Horse extends Animal{
public void eat() throws IOException{
System.out.println ("Horse is eating.");
throw new IOException();
}
}
這個例子中,父類拋出了檢查異常Exception,子類拋出的IOException是Exception的子類,也即是比被重寫的方法拋出了更有限的異常,這是可以的。如果反過來,父類拋出IOException,子類拋出更為寬泛的Exception,那麼不會通過編譯的。
注意:這種限制只是針對檢查異常,至於運行時異常RuntimeException及其子類不再這個限制之中。
E、重寫規則之五:不能重寫被標識為final的方法。
F、重寫規則之六:如果一個方法不能被繼承,則不能重寫它。
比較典型的就是父類的private方法。下例會產生一個有趣的現象。
public class Test {
public static void main (String[] args) {
//Animal h = new Horse();
Horse h = new Horse();
h.eat();
}
}
class Animal {
private void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
}
這段代碼是能通過編譯的。表面上看來違反了第六條規則,但實際上那是一點巧合。Animal類的eat()方法不能被繼承,因此Horse類中的eat()方法是一個全新的方法,不是重寫也不是重載,只是一個只屬於Horse類的全新的方法!這點讓很多人迷惑了,但是也不是那麼難以理解。
main()方法如果是這樣:
Animal h = new Horse();
//Horse h = new Horse();
h.eat();
編譯器會報錯,為什麼呢?Horse類的eat()方法是public的啊!應該可以調用啊!請牢記,多態只看父類引用的方法,而不看子類對象的方法!
二、方法的重載。
重載是有好的,它不要求你在調用一個方法之前轉換資料類型,它會自動地尋找匹配的方法。方法的重載是在編譯時間刻就決定調用哪個方法了,和重寫不同。最最常用的地方就是構造器的重載。
1、基礎資料型別 (Elementary Data Type)參數的重載。
public class Test {
static void method(byte b){
System.out.println ("method:byte");
}
static void method(short s){
System.out.println ("method:short");
}
static void method(int i){
System.out.println ("method:int");
}
static void method(float f){
System.out.println ("method:float");
}
static void method(double d){
System.out.println ("method:double");
}
public static void main (String[] args) {
method((byte)1);
method('c');
method(1);
method(1L);
method(1.1);
method(1.1f);
}
}
輸出結果:
method:byte
method:int
method:int
method:float
method:double
method:float
可以看出:首先要尋找的是資料類型正好匹配方法。如果找不到,那麼就提升為表達能力更強的資料類型,如上例沒有正好容納long的整數類型,那麼就轉換為float類型的。如果通過提升也不能找到合適的相容類型,那麼編譯器就會報錯。反正是不會自動轉換為較小的資料類型的,必須自己強制轉換,自己來承擔轉變後果。
char類型比較特殊,如果找不到正好匹配的類型,它會轉化為int而不是short,雖然char是16位的。
2、重載方法的規則。
A、被重載的方法必須改變參數列表。
參數必須不同,這是最重要的!不同有兩個方面,參數的個數,參數的類型,參數的順序。
B、被重載的方法與傳回型別無關。
也就是說,不能通過傳回型別來區分重載方法。
C、被重載的方法可以改變存取修飾詞。
沒有重寫方法那樣嚴格的限制。
D、被重載的方法可以聲明新的或者更廣的檢查異常。
沒有重寫方法那樣嚴格的限制。
E、方法能夠在一個類中或者在一個子類中被重載。
3、帶對象引用參數的方法重載。
class Animal {}
class Horse extends Animal{}
public class Test {
static void method(Animal a){
System.out.println ("Animal is called.");
}
static void method(Horse h){
System.out.println ("Horse is called.");
}
public static void main (String[] args) {
Animal a = new Animal();
Horse h = new Horse();
Animal ah = new Horse();
method(a);
method(h);
method(ah);
}
}
輸出結果是:
Animal is called.
Horse is called.
Animal is called.
前兩個輸出沒有任何問題。第三個方法為什麼不是輸出“Horse is called.”呢?還是那句老話,要看參考型別而不是物件類型,方法重載是在編譯時間刻就決定的了,參考型別決定了調用哪個版本的重載方法。
4、重載和重寫方法區別的小結。
如果能徹底弄明白下面的例子,說明你對重載和重寫非常瞭解了,可以結束這節的複習了。
class Animal {
public void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal{
public void eat(){
System.out.println ("Horse is eating.");
}
public void eat(String food){
System.out.println ("Horse is eating " + food);
}
}
public class Test {
public static void main (String[] args) {
Animal a = new Animal();
Horse h = new Horse();
Animal ah = new Horse();
a.eat();
h.eat();
h.eat("apple");
ah.eat();
//a.eat("apple");
//ah.eat("apple");
}
}
四個輸出分別是什嗎?被注釋的兩條語句為什麼不能通過編譯?
第一條:a.eat(); 普通的方法調用,沒有多態,沒什麼技術含量。調用了Animal類的eat()方法,輸出:Animal is eating.
第二條:h.eat(); 普通的方法調用,也沒什麼技術含量。調用了Horse類的eat()方法,輸出:Horse is eating.
第三條:h.eat("apple"); 重載。Horse類的兩個eat()方法重載。調用了Horse類的eat(String food)方法,輸出:Horse is eating apple
第四條:ah.eat(); 多態。前面有例子了,不難理解。輸出:Horse is eating.
第五條:a.eat("apple"); 低級的錯誤,Animal類中沒有eat(String food)方法。因此不能通過編譯。
第六條:ah.eat("apple"); 關鍵點就在這裡。解決的方法還是那句老話,不能看物件類型,要看參考型別。Animal類中沒有eat(String food)方法。因此不能通過編譯。
小結一下:多態不決定調用哪個重載版本;多態只有在決定哪個重寫版本時才起作用。
重載對應編譯時間,重寫對應運行時。夠簡潔的了吧!
三、構造方法。
構造方法是一種特殊的方法,沒有構造方法就不能建立一個新對象。實際上,不僅要調用對象實際類型的構造方法,還要調用其父類的構造方法,向上追溯,直到Object類。構造方法不必顯式地調用,當使用new關鍵字時,相應的構造方法會自動被調用。
1、構造方法的規則。
A、構造方法能使用任何存取修飾詞。包括private,事實上java類庫有很多都是這樣的,設計者不希望使用者建立該類的對象。
B、構造方法的名稱必須與類名相同。這樣使得構造方法與眾不同,如果我們遵守sun的編碼規範,似乎只有構造方法的首字母是大寫的。
C、構造方法不能有傳回型別。
反過來說,有傳回型別的不是構造方法
public class Test {
int Test(){
return 1;
}
}
這個方法是什麼東西?一個冒充李逵的李鬼而已,int Test()和其他任何普通方法沒什麼兩樣,就是普通的方法!只不過看起來很噁心,類似噁心的東西在考試卷子裡比較多。
D、如果不在類中建立自己的構造方法,編譯器會自動產生預設的不帶參數的建構函式。
這點很容易驗證!寫一個這樣簡單的類,編譯。
class Test {
}
對產生的Test.class檔案反編譯:javap Test,可以看到:
D:"JavaCode"bin>javap Test
Compiled from "Test.java"
class Test extends java.lang.Object{
Test();
}
看到編譯器自動添加的預設建構函式了吧!
E、如果只建立了帶參數的構造方法,那麼編譯器不會自動添加無參的構造方法的!
F、在每個構造方法中,如果使用了重載建構函式this()方法,或者父類的構造方法super()方法,那麼this()方法或者super()方法必須放在第一行。而且這兩個方法只能選擇一個,因此它們之間沒有順序問題。
G、除了編譯器產生的構造方法,而且沒有顯式地調用super()方法,那麼編譯器會插入一個super()無參調用。
H、抽象類別有構造方法。
四、靜態方法的重載與重寫(覆蓋)。
1、靜態方法是不能被覆蓋的。可以分兩種情況討論:
A、子類的非靜態方法“覆蓋”父類的靜態方法。
這種情況下,是不能通過編譯的。
class Father{
static void print(){
System.out.println ("in father method");
}
}
class Child extends Father{
void print(){
System.out.println ("in child method");
}
}
static方法表示該方法不關聯具體的類的對象,可以通過類名直接調用,也就是編譯的前期就綁定了,不存在後期動態綁定,也就是不能實現多態。子類的非靜態方法是與具體的對象綁定的,兩者有著不同的含義。
B、子類的靜態方法“覆蓋”父類靜態方法。
這個覆蓋依然是帶引號的。事實上把上面那個例子Child類的print方法前面加上static修飾符,確實能通過編譯!但是不要以為這就是多態!多態的特點是動態綁定,看下面的例子:
class Father{
static void print(){
System.out.println ("in father method");
}
}
class Child extends Father{
static void print(){
System.out.println ("in child method");
}
}
class Test{
public static void main (String[] args) {
Father f =new Child();
f.print();
}
}
輸出結果是:in father method
從這個結果可以看出,並沒有實現多態。
但是這種形式很迷惑人,貌似多態,實際編程中千萬不要這樣搞,會把大家搞懵的!
它不符合覆蓋表現出來的特性,不應該算是覆蓋!
總而言之,靜態方法不能被覆蓋。
2、靜態方法可以和非靜態方法一樣被重載。
這樣的例子太多了,我不想寫常式了。看看java類庫中很多這樣的例子。
如java.util.Arrays類的一堆重載的binarySearch方法。
在這裡提一下是因為查資料時看到這樣的話“sun的SL275課程說,靜態方法只能控制靜態變數(他們本身沒有),靜態方法不能被重載和覆蓋……”
大家不要相信啊!可以重載的。而且靜態與非靜態方法可以重載。
從重載的機制很容易就理解了,重載是在編譯時間刻就決定的了,非靜態方法都可以,靜態方法怎麼可能不會呢?