一.抽象類 在某些情況下,父類知道其子類應該包含哪些方法,但是無法確定這些子類如何實現這些方法。這種有方法簽名但是沒有具體實現細節的方法就是抽象方法。有抽象方法的類只能被定義成抽象類,抽象方法和抽象類必須使用abstract修飾。抽象類里可以沒有抽象方法。 1.1 抽象類和抽象方法 抽象類和抽象方法 ...
一.抽象類
在某些情況下,父類知道其子類應該包含哪些方法,但是無法確定這些子類如何實現這些方法。這種有方法簽名但是沒有具體實現細節的方法就是抽象方法。有抽象方法的類只能被定義成抽象類,抽象方法和抽象類必須使用abstract修飾。抽象類里可以沒有抽象方法。
1.1 抽象類和抽象方法
抽象類和抽象方法的規則如下:
1.抽象類和抽象方法都必須使用abstract修飾符修飾,抽象方法不能有方法體
2.抽象類不能被實例化,無法使用new關鍵字來調用抽象類的構造器創建抽象類的實例。即使這個抽象類里不包含抽象方法。
3.抽象類可以包含成員變數,方法(普通方法、抽象方法),構造器,初始化塊,內部類(介面,枚舉類)5中成分。抽象類的構造器不能用於創建實例,主要是用於被子類調用。
4.含有抽象方法的類(直接定義了一個抽象方法;繼承了一個父類,沒有完全實現父類包含的抽象方法;或實現一個介面,但沒有完全實現介面包含的抽象方法)只能被定義成抽象類。
定義抽象方法只需在普通方法上加上abstract修飾符,並把普通方法的方法體全部去掉,併在方法後增加分號即可
定義抽象類只需在普通類上增加abstract修飾符即可。
抽象類不能用於創建實例,只能當作父類被其他子類繼承。
package com.company2; abstract class Shape{ private String color; //定義一個計算周長的方法 public abstract double calPerimeter(); //定義一個返回形狀的方法 public abstract String getType(); public Shape(String color) { this.color = color; } public Shape() { } public String getColor() { return color; } public void setColor(String color) { this.color = color; } } public class Triangle extends Shape{ private double a; private double b; private double c; public Triangle(String color, double a, double b, double c) { super(color); setSides(a,b,c); } public void setSides(double a, double b, double c) { if(a+b > c || a+c > b || b+c > a){ this.a = a; this.b = b; this.c = c; } System.out.println("三角形兩邊之和必須大於第三邊"); } @Override public double calPerimeter() { return a+b+c; } @Override public String getType() { return "三角形"; } }
當使用abstract修飾類時,表明這個類只能被繼承;當使用abstract修飾方法時,表明這個方法必須有子類提供實現(重寫)。而final修飾的方法不能被重寫,final修飾的類不能被繼承。因此final
和abstract永遠不能同時使用。static和abstract不能同時修飾某個方法,沒有類抽象方法的說法,但可以同時修飾內部類。abstract也不能修飾變數和構造器。
利用抽象類和抽象方法的優勢,可以更好地發揮多態的優勢,使得程式更加靈活。
1.2 抽象類的作用
從語義的角度來看,抽象類是從多個具體類中抽象出來的父類,它具有更高層次的抽象,它體現的是一種模板模式的設計。抽象類作為多個子類的通用模板,子類在抽象類的基礎上進行
拓展,改造,避免了子類設計的隨意性。
抽象類的普通方法可依賴與抽象方法,抽象方法則推遲到子類中提供實現
package com.company2; abstract class Speedmeter { private double turnRate;//轉速 public Speedmeter() { } //把返回車輪半徑的方法定義成抽象方法 public abstract double getRadius(); public void setTurnRate(double turnRate){ this.turnRate = turnRate; } public double getSpeed() { return Math.PI*2*getRadius()*turnRate; } } public class CarSpeedmeter extends Speedmeter{ @Override public double getRadius() { return 0.28; } public static void main(String[] args) { CarSpeedmeter csm = new CarSpeedmeter(); csm.setTurnRate(15); System.out.println(csm.getSpeed()); } }
模板模式在面向對象的軟體中很常用,其原理簡單,實現也簡單。下麵是使用模板模式的一些簡單規則:
1.抽象父類可以只定義需要使用的某些方法,把不能實現的部分抽象成抽象方法,留給其子類實現。
2.父類中可能包含需要調用其他系列方法的方法。這些被調用方法既可以由父類實現,也可以由子類實現。父類提供的方法只是定義了一個通用演算法,其實現也許並不完全由自身實現,必須
依賴於其子類的幫助。
二. Java 8改進的介面(interface)
2.1 介面的概念
介面是多個相似類中抽象出來的一組公共行為規範,介面不提供任何實現,它體現的是規範和實現分離的哲學。
規範和實現分離正是介面的好處,它讓軟體系統各組件之間面向介面耦合,可以為軟體系統提供很好的松耦合設計,從而降低個模塊間的耦合,為系統提供更好的可拓展性和可維護性。
介面可以有多個直接父介面,但介面只能繼承介面,不能繼承類
2.2 介面的定義規則
1.由於介面定義的是一種規範,用interface跟class區分,因此介面里不能包含構造器和初始化塊定義。介面里可以包含成員變數(只能是靜態常量),方法(只能是抽象實例方法,類方法或
預設方法), 內部類(內部接口,枚舉)定義。
2.因為介面定義的是多個類共同的公共行為規範。因此介面的所有成員(包括常量,方法,內部類)都是public訪問許可權。只能指定public修飾符,也可以省略。
3.介面定義的成員變數只能在定義時指定預設值,預設使用public static final修飾符修飾。可以省略不寫。
4.介面定義的方法只能是抽象方法,類方法和預設方法。類方法和預設方法都必須有方法實現(方法體)。
下麵定義一個介面
package com.company2; public interface Output { //定義的成員變數只能是常量,預設public static final修飾 int MAX_CACHE_LINE = 50; //介面里定義的普通方法只能是public的抽象方法,沒有方法體 void out(); void getData(String msg); //介面里定義的預設方法,需要使用default修飾,預設public修飾,有方法體 default void print(String... msgs) { for(String msg:msgs) { System.out.println(msg); } } default void test() { System.out.println("預設的test()方法"); } //介面里定義的類方法,需要使用static修飾,預設public修飾,有方法體 static String staticTest() { return "介面里的類方法"; } }
不同包下的另一個類訪問介面里的成員變數。如下
package com.company; import com.company2.Output; public class OutputFIeldTest { public static void main(String[] args) { System.out.println(Output.MAX_CACHE_LINE); System.out.println(Output.staticTest()); } }
2.3 介面的繼承
介面的繼承和類繼承不一樣,介面完全支持多繼承,即一個介面可以有多個直接父介面。和類繼承相似,子介面拓展某個父介面,將會獲得父介面里定義的所有抽象方法、常量
一個介面繼承多個父介面時,多個父介面排在extends關鍵字之後,多個父介面之間以逗號隔開。
2.4 使用介面
介面的主要用途
1.定義變數,也可用於強制類型轉換。介面聲明引用類型變數
2.調用介面中定義的常量
3.被其他類實現
類可以使用implements關鍵字實現一個或多個介面,這也是Java為單繼承靈活性不足所做的補充。實現介面與繼承父類相似,一樣可以獲得所實現介面里定義的常量,方法。
一個類可以繼承一個父類,並同時實現多個介面,implements部分必須在extends部分之後。
一個類實現了一個或多個介面之後,這個類必須完全實現這些介面里所定義的全部抽象方法(也就是重寫這些抽象方法);否則該類將保留從父介面那裡繼承得到的抽象方法,該類也必須
定義成抽象類。
一個類實現某個介面時,該類將會獲得介面中定義的常量,方法等。因此可以把實現介面理解為一種特殊的繼承,相當於實現類完全繼承了一個徹底抽象的類。
介面不能顯式繼承任何類,但所有介面類型的引用變數都可以直接賦給Object類型的引用變數。
三. 介面和抽象類
3.1 介面與抽象類的相似特征
1.介面和抽象類都不能被實例化,用於被其他類實現和繼承
2.介面和抽象類都包含抽象方法,實現介面和繼承抽象類的普通子類都必須實現這些抽象方法
3.2 介面與抽象類的設計目的差別
介面作為系統與外界交互的視窗,介面體現的是一種規範。對於介面的實現者而言,介面規定了實現者必須向外提供哪些服務(以方法的形式);對於介面的調用者而言,介面規定了調用者
可以調用哪些服務,以及如何調用這些服務(就是如何調用方法)。當一個程式中使用介面時,介面是多個模塊間的耦合標準;在多個應用程式之間使用介面時,介面是多個程式之間的通訊標準。
從某種程度上來看,介面類似於整個系統的總綱,它制定了各模塊應該遵循的標準,因此一個系統中的介面不應該經常改變。一但介面發生改變,其影響是輻射性的,導致系統中的大部分都需
要重寫。
抽象類作為系統中多個子類的共同父類,它所體現的是一種模板式設計。抽象類作為多個子類的抽象父類,可以被當成系統實現過程中的中間產品,這個中間產品已經實現了系統的部分功能(那
些已經提供實現的方法),但這個產品依然不能當成最終產品,必須有進一步的改善。
3.3 介面與抽象類的用法差別
1.介面里只能包含抽象方法,靜態方法,預設方法,不能為普通方法提供方法實現。抽象類則可以完全包含普通方法。
2.介面里只能定義靜態常量,不能定義普通成員變數。抽象類里即可以定義靜態常量,也可以定義普通成員變數。
3.介面里不包含構造器。抽象類里可以包含構造器,抽象類里的構造器並不是用於創建對象,而是讓其子類調用這些構造器來完成屬於抽象類的初始化操作。
4.介面里不能包含初始化塊。抽象類則可以完全包含初始化塊
5.一個類最多只能有一個直接父類,包括抽象類。但一個類可以直接實現多個介面,通過實現多個介面可以彌補Java單繼承的不足。
四. 面向介面編程
很多軟體架構設計理論都倡導面向介面編程來降低程式的耦合。下麵介紹兩種常用的場景來示範介面編程的優勢
4.1 簡單工廠模式
假設程式中有個Computer類需要組合一個輸出設備。有兩種選擇,讓Computer類組合一個Printer對象,或者讓Computer類組合一個Output。
假設讓Computer類組合一個Printer對象,假如有一天系統需要重構,需要BetterPrinter來代替Printer,這就需要打開Computer類源代碼進行修改,假如系統中有100個類組合了Printer,甚至1000個、
10000個...,這是多麼大的工作量啊
為了避免這個問題,工廠模式建議讓Computer類組合一個Output類型的對象,將Computer類和Printer類完全分離。Computer對象實際組合的是Printer對象還是BetterPrinter對象,對Computer而言
完全透明。當Printer對象切換到BetterPrinter對象時,系統完全不受影響。
package com.company2; public interface Output { //定義的成員變數只能是常量,預設public static final修飾 int MAX_CACHE_LINE = 50; //介面里定義的普通方法只能是public的抽象方法,沒有方法體 void out(); void getData(String msg); //介面里定義的預設方法,需要使用default修飾,預設public修飾,有方法體 default void print(String... msgs) { for(String msg:msgs) { System.out.println(msg); } } default void test() { System.out.println("預設的test()方法"); } //介面里定義的類方法,需要使用static修飾,預設public修飾,有方法體 static String staticTest() { return "介面里的類方法"; } }
Output介面定義了一系列列印行為
package com.company2; interface Product { int getProduceTime(); } public class Printer implements Output,Product { private String[] printData = new String[MAX_CACHE_LINE]; //用以記錄當前需列印的作業數 private int dataNum = 0; @Override public void out() { //只要還有作業就繼續列印 while (dataNum > 0) { System.out.println("告訴印表機正在列印" + printData[0]); //把作業隊列整體前移一位,並將剩下的作業數減1 System.arraycopy(printData, 1, printData, 0, --dataNum); } } @Override public void getData(String msg) { if(dataNum >= MAX_CACHE_LINE) { System.out.println("輸出隊列已滿,添加失敗"); } else{ //把列印數據添加到隊列里,已保存數據的數量加1 printData[dataNum++] = msg; } } @Override public int getProduceTime() { return 45; } }
Printer實現了Output介面
package com.company2; public class Computer { private Output out; public Computer(Output out) { this.out = out; } public void keyIn(String msg) { out.getData(msg); } public void print() { out.out(); } }
Computer類完全與Printer類分離,只是與Output介面耦合。Computer不再負責創建Outputer對象,系統提供一個Ouputer工廠來負責生產Output對象。
package com.company2; public class OutputFactory { public Output getOutput() { return new Printer(); } public static void main(String[] args) { OutputFactory of = new OutputFactory(); Computer c = new Computer(of.getOutput()); c.keyIn("輕量級Java EE企業應用實戰"); c.keyIn("瘋狂Java講義"); c.print(); } }
在該OutputFactory類中包含了一個getOutput()方法,返回Output實現類的實例,該方法負責創建Output實例
package com.company2; public class BetterPrinter implements Output { private String[] printData = new String[MAX_CACHE_LINE*2]; private int dataNum = 0; @Override public void out() { while(dataNum>0) { System.out.println("告訴印表機正在列印"+printData[0]); System.arraycopy(printData,1,printData,0,--dataNum); } } @Override public void getData(String msg) { if(dataNum >= MAX_CACHE_LINE * 2) { System.out.println("輸出隊列已滿,添加失敗"); } else{ printData[dataNum++] = msg; } } }
BetterPrinter類也實現了Output介面,因此也可當成Output對象使用,只要把OutputFactory工廠類的getOutput()方法中的下劃線部分改為如下代碼
return new BetterPrinter();
4.2 命令模式
假設一個方法需要遍歷某個數組的數組元素,但無法確定在遍曆數組元素時如何處理這些元素,需要在調用該方法時指定具體的處理行為。
對於這樣的一個需求,必須把處理行為作為參數傳入該方法,這個“處理行為”用編程來實現就是一段代碼。
可以考慮使用一個Command介面定義一個方法,用這個方法來封裝“處理行為”,但這個方法沒有方法體,因為現在還無法確定這個處理行為
public interface Command { void process(int[] target); }
需要創建一個數組的處理類,在這個處理類包含一個process方法,這個方法還無法確定處理數組的處理行為,所以定義該方法時使用了一個Command參數,這個Command參數負責
對數組的處理行為
public class ProcessArray { public void process(int[] target,Command cmd) { cmd.process(target); } }
通過Command介面,就實現了讓ProcessArray類和具體“處理行為”的分離,程式使用Command介面代表了對數組的處理行為。Command介面也沒提供真正的處理,只有等到需要
調用ProcessArray對象的process()方法時,才真正傳入一個Command對象,才確定對數組的處理行為。
下麵代碼示範了對數組的兩種處理方式
public class CommandTest { public static void main(String[] args) { ProcessArray pa = new ProcessArray(); int[] target = [3,-4,6,4]; //第一次處理數組,具體處理行為取決於PrintCommand pa.process(target,new PrintCommand()); //第二次處理數組,具體處理行為取決於AddCommand pa.process(target,new AddCommand()); } }
兩次不同的處理行為通過PrintCommand類和AddCommand類提供
package com.company2; public class PrintCommand implements Command{ public void process(int[] target) { for(int tmp:target) { System.out.println("迭代輸出數組的元素"+tmp); } } }
package com.company2; public class AddCommand implements Command{ public void process(int[] target) { int sum = 0; for(int tmp:target) { sum += tmp; } System.out.println("數組的元素總和為"+sum); } }