在《Java中的抽象方法和接口》中,介紹了抽象方法與接口,以及做了簡單的比較。
這里我想詳細探討下抽象類。
一、抽象類的定義
被關鍵字“abstract”修飾的類,為抽象類。(而且,abxtract只能修飾類和方法)
下面顯示了一個最簡單的空抽象類
public abstract class AbstractClass { public static void main(String[] args) { AbstractClass abstractClass=new AbstractClass(); } }
當對這個空的抽象類進行實例化時,編譯器會報錯:
'AbstractClass' is abstract; cannot be instantiated'
現在對這個抽象類進行擴展,添加屬性和方法:
public abstract class AbstractClass { public int constInt = 5; /*重載method()*/ public void method() { } //沒有編譯錯誤 public abstract void method(int a); public static void main(String[] args) { AbstractClass abstractClass=new AbstractClass() { @Override public void method(int a) { System.out.println("實例化抽象類"); } };
System.out.println(abstractClass.constInt); abstractClass.method(5); } } //運行結果 /* 5 實例化抽象類 */
在這個抽象類中我添加了一個實例屬性,一個抽象方法,以及該抽象方法的重載實例方法,這些都是合法的。
在main方法中,直接對抽象類通過new操作符進行實例化,出乎意料的是,IDE直接提示了這種操作——這里生成了一個匿名內部類(Anonymous Inner)。
下面是關於匿名內部類的知識點:
- 一種特殊的局部內部類,沒有類名,沒有class關鍵字,也不能使用extends和implements等關鍵字修飾。
- 匿名內部類不能是抽象類(即不能擁有抽象方法),必須實現它的抽象父類或者接口的所有抽象方法。
- 因為沒有類名,所以匿名內部類只能被使用一次,通常用來簡化代碼編寫。
- 使用匿名內部類的前提條件:繼承一個父類或者實現一個接口。
第四點和第一點略有矛盾,其實就是使用的時候通過new操作符指定要繼承的父類或要實現的接口(事實上,new是直接調用某個構造函數。new真的是個牛逼的操作符啊),然后再通過直接定義類體(類體中實現某些方法),構建新類的實例。
所以,在我上面的代碼中,生成了一個繼承AbstractClass的新匿名內部類的實例,這個類中實現了父類的抽象method方法,獲得的實例我們賦給了abstractClass,並通過實例調用了新的method(int 5)方法。
二、抽象類與抽象方法
抽象方法只有方法聲明,沒有方法體的方法。它將由子類(可能是抽象類,也可能是非抽象類)進行實現。
通過上面空的抽象類的方法可知,擁有一個抽象方法並不是構建一個抽象類充分必要條件。
那么,一個有抽象方法的普通類是合法的嗎?大概率是不合法的,因為如果這樣的設計是合法的又有什么意義呢?
實際上,如果我們在一個非抽象類中定義一個抽象方法,IDE會提示:
“Abstract method in non-abstract class”
而如果我們一定要運行如下所示的一段錯誤代碼:
public class AbstractTest { public abstract void test(); public static void main(String[] args) { } }
編譯器的報錯信息為:
Error:java: AbstractTest不是抽象的, 並且未覆蓋biguo.classConstruct.AbstractTest中的抽象方法test()
所以抽象方法只能存在於抽象類中。
三、抽象方法可以是static的嗎?
在進行下面的測試時,突然想到一個很有意思的問題,抽象類中的抽象方法可以是靜態的嗎?
先下結論:NO,這是的修飾符組合是不合法的——Error: java: 非法的修飾符組合: abstract和static
public abstract class AbstractTest { //非法的修飾符組合 public static abstract void test(); public static void main(String[] args) { } }
static成員方法意味着,不需要實例化可以使用(在類的內部或者通過類訪問)。但是呢,也可以通過實例進行訪問,這樣做不會報錯,但會得到IDE的警告,因為違反了static的設計語義;
abstract方法意味着沒有方法體(區別下“有方法體,但方法體為空”),即只是一個方法聲明,需要被子類去實現。
我們先要清楚,抽象類中是可以擁有static方法的,比如,我們把main方法放在一個抽象類中,程序是可以由此運行的。
既然這樣,一個“static abstract”組合的方法,對這個抽象類完全沒有存在的意義了!!因為它沒有方法體,無法通過類來使用。
在StackOverFlow上有探討,說完全可以允許這樣的“static abstract”方法,因為在非抽象子類中,實現這個抽象方法后的子方法仍然是static,是子類的類方法。
這樣的說法有一點點意義,但是它仍然無法解決的是,“static abstract”方法對父類中“static”語義的違背
static方法可以被子類重寫嗎?
答案是:static方法不能被子類重寫。(涉及到重寫的定義)
但是!我們確實又可以在子類中重新定義一個與父類static方法一模一樣的方法,如下的test()方法。
package biguo.classConstruct; public class AbstractTest { public static void test(){ System.out.println("This is AbstractTest's static test!"); } public static void print(){ System.out.println("This is AbstractTest's static print!"); } public static void main(String[] args) { AbstractTest.test(); AbstractTestSon.test(); AbstractTestSon.print(); System.out.println(); AbstractTestSon abstractTestSon=new AbstractTestSon(); abstractTestSon.print(); abstractTestSon.test(); } } class AbstractTestSon extends AbstractTest{ public static void test(){ System.out.println("This is AbstractTest-Son's static test!"); } } //輸出 /* This is AbstractTest's static test! This is AbstractTest-Son's static test! This is AbstractTest's static print! This is AbstractTest's static print! This is AbstractTest-Son's static test! */
通過輸出結果可以看到:父類中未被子類重寫的static方法是可以被子類及其對象訪問到的,但是被子類重寫過的方法,則子類及其對象只能調用自己的方法了。
為什么這種情況不能叫做子類“重寫”了父類的方法呢,而是叫”方法隱藏(method hidding)“?
在www.geeksforgeeks.org的這篇文章中,對此做了解釋:
因為,方法重寫(Overriding)是OOP語言的一種特性,在Java中,它是實現“運行時多態”一種方式!!而父類子類的相同簽名的同名方法的情況,there won’t be any run-time polymorphism。
還篇文章(”5 Java concepts explained: Overloading, overriding, shadowing, hiding, and obscuring“),解釋了這些概念的差別,但是卻沒有提到上述的method hidding情況。(所以我以為隱藏只是繼承關系的類中變量之間的行為)。
四、抽象類的繼承
從抽象父類派生的子類如果不能實現所有的抽象方法,它也必須聲明為抽象的。
抽象類可以定義構造方法,且能被子類在構造方法中調用。
一個非抽象類的子類,可以聲明為抽象類。
五、final與abstract的矛盾
final關鍵字可以修飾類、方法、變量。
final修飾的類不能被派生;final修飾的方法,禁止子類重寫。
所以我們可以看出,final和abstract就是冰火不容的~