上一篇中介紹完了java面向對象四大特徵中的封裝、繼承,本文繼續說剩下的2個特徵:抽象和多態。
抽象
當多個類中出現相同功能,但功能內容不一樣時,可以向上抽取功能定義,但不抽取功能內容,由各個子類去實現各自的功能主體。這樣的功能就是抽象方法,java中抽象由抽象類和抽象方法來體現。
抽象類和抽象方法都用abstract修飾,抽象方法只有方法聲明,沒有方法體,直接以;結束,且用abstract修飾。abstract關鍵字不能和final、private、static這3個關鍵字共存。
抽象類的特點- 抽象方法必須在抽象類中;
- 抽象方法和抽象類都必須被abstract關鍵在字修飾;
- 抽象類不可以用new創建對象,因爲調用抽象方法沒有意義,抽象類中可以有構造函數;
- 抽象類的方法要被使用,必須由子類複寫完所有的抽象方法後,建立子類對象調用;如果子類只複寫了部分抽象方法,那子類還是一個抽象類。
- 特殊情況:抽象類中可以不定義抽象方法,這樣做僅僅是不讓類建立對象。
abstract 關鍵字,和哪些關鍵字不能共存。
final:被final修飾的類不能有子類。而被abstract修飾的類一定是一個父類。
private: 抽象類中的私有的抽象方法,不被子類所知,就無法被複寫。而抽象方法出現的就是需要被複寫。
static:如果static可以修飾抽象方法,那麼連對象都省了,直接類名調用就可以了。可是抽象方法運行沒意義。
下面的代碼中演示了抽象類和抽象方法的使用:
/*
需求: 獲取一段程序運行的時間。
原理:獲取程序開始和結束的時間,並相減即可。
*/
abstract class GetTime{
public final void getTime(){
long start=System.currentTimeMillis();//獲取當前時間
runcode();
long end=System.currentTimeMillis();
System.out.println("程序運行 "+(end-start)+"毫秒");
}
public abstract void runcode();
}
class SubTime extends GetTime{
public void runcode(){
for(int i=0;i<400;i++){
System.out.println(i);
}
}
}
public class TemplateDemo{
public static void main(String[] args) {
SubTime st=new SubTime();
st.getTime();
}
}
這個程序同時也展示了一種程序設計模式:模板方法模式--在定義功能時,功能的一部分是確定的,但是有一部分是不確定的,而確定的部分在使用不確定的部分,那麼這時就將不確定的部分暴露出去,由該類的子類去完成。
上面的程序展示了模板方法模式,但是並不是說模板方法模式中暴露出去的部分必須是抽象的,抽象方法只是模板方法模板的一種實現方式。
當抽象類的方法都是抽象的,那麼該類可以通過接口的形式來表示,interface用於定義接口。
接口定義格式特點:
-
接口中常見定義:常量,抽象方法
-
接口中的成員都有固定修飾符
常量:public static final
方法:public abstract
即使在寫程序時接口中成員沒有使用上面的修飾符,成員的實際修飾符也是如此,系統會自動補上正確的修飾。
接口注意事項:
-
接口不能創建對象,因爲有抽象方法;
-
接口需要子類實現,子類中將接口中的抽象方法全部覆蓋後,子類纔可以實例化,否則子類是一個抽象類。
-
接口可以被子類多實現,彌補了java中不支持多繼承的缺憾,是多繼承的轉換形式。
多個接口中即使出現了一模一樣的函數,但是因爲都沒有函數體,所以函數怎麼實現完全由子類定義,所以函數調用時不會像多繼承時出現無法識別該調用哪個父類方法的問題,而是可以明確的運行子類方法;多實現時多個接口中也不能出現僅返回值不同的同名函數,這樣會導致子類實現接口後類中有多個返回值不同的同名函數。 -
一個類在繼承一個類的同時還可以實現多個接口
-
接口與接口之間可以繼承,而且可以多繼承。
接口多實現及接口繼承接口的例子:
interface A{
void methodA();
}
interface B{
void methodB();
}
interface C extends A,B //接口間可以多繼承,C接口中有methodA和methodB方法
{
void methodC();
}
interface D{
void methodD();
}
class E{
void methodE(){
System.out.println("Fu E");
}
}
class F extends E implements C,D//繼承一個類的同時實現2個接口, F類中共有5個方法
{
public void methodA(){
System.out.println("interface A");
}
public void methodB(){
System.out.println("interface B");
}
public void methodC(){
System.out.println("interface C");
}
public void methodD(){
System.out.println("interface D");
}
void methodE(){
System.out.println("Zi E");
}
}
public class Interfacedemo{
public static void main(String[] args) {
}
}
接口在設計上的特點:
-
接口是對外暴露的規則
-
接口是程序的擴展功能;
-
接口的出現降低了耦合度。
子父類繼承時,子父類時is a的所屬關係;子類實現接口時,子類與接口間的關係是like a關係,接口擴展了子類的功能,但接口功能可能並不是子類對象體系的必備功能,與子類並沒有直接的所屬關係。如下面這個例子:
interface Fly //Fly作爲一個擴展功能接口
{
void fly();
}
class Animal //動物類,動物都會吃
{
public void eat(){
System.out.println("動物都會吃東西");
}
}
class LaoYing extends Animal implements Fly{//LaoYing是Animal中的一種,實現Fly接口只是LaoYing的擴展功能
public void fly(){
System.out.println("老鷹在天空中高高的飛翔");//
}
public void eat(){
System.out.println("老鷹吃肉");
}
public void Zhuaxiaoji(){ //老鷹的特有功能
System.out.println("老鷹抓小雞");
}
}
子類繼承父類或實現接口,引發了java面向對象的最後一個特徵:多態多態
某一類事物的多種存在形態
多態的體現:父類的引用指向了自己的子類對象。或者說,父類的引用也可以接收自己的子類對象,如Animal a=new Cat()。
多態時的向下轉型:父類引用指向子類對象,如果想要調用子類的特有方法,需要強制將父類的引用,轉成子類類型,向下轉型;父類對象不能轉換子類類型。
多態的前提:必須是類與類之間有關係,要麼繼承,要麼實現,通常還有一個前提:存在覆蓋
多態的好處:多態的出現大大提高了程序的擴展性.
多態的弊端:提高了擴展性,但是隻能使用父類引用訪問父類中的成員。
多態中成員的特點(多態使用的注意事項)
多態中成員函數的特點:
在編譯時期:參閱引用型變量所屬的類中是否有調用的方法。如果有,編譯通過,如果沒有編譯失敗。
在運行期間:參閱對象所屬的類中是否有調用的方法
簡單總結就是:成員函數在多態調用時,編譯看左邊,運行看右邊。
在多態中,成員變量的特點:
無論編譯和運行,都參考左邊(引用型變量所屬的類)。
在多態中,靜態成員函數的特點:
無論編譯和運行,都參考左邊。
多態的一個基本演示及應用:
/*
電腦運行實例
電腦運行基於主板,電腦要能上網、視頻等還需要網卡、聲卡等設備運行,這些設備可作爲主板的擴展功能來實現,由主板來操作其啓動和終止
*/
interface PCI{ //主板管理網卡、聲卡等設備的統一接口,所有外接設備都需要遵守此接口規範
public void open();
public void close();
}
class MainBoard{
public void run(){
System.out.println("MainBoard run!");
}
public void usePCI(PCI p){ //函數調用時,PCI p=new NetCard(),接口型引用指向自己的子類對象
if(p!=null){
p.open();
p.close();
}
}
}
class NetCard implements PCI{
public void open(){
System.out.println("NetCard open");
}
public void close(){
System.out.println("NetCard close");
}
}
class SoundCard implements PCI{ //新來的外接設備只需實現PCI接口,就可以被主板操作,運行起來
public void open(){
System.out.println("SoundCard open");
}
public void close(){
System.out.println("SoundCard close");
}
}
public class DuoTaiDemo {
public static void main(String[] args) {
MainBoard mb=new MainBoard();
mb.run();
mb.usePCI(new NetCard());
mb.usePCI(new SoundCard());
}
}
下面再記錄與多態有關的2個比較有意思且容易把人繞暈的程序:
class Fu{
String s = "fu";
void show(){
System.out.println(s);
System.out.println(this.toString());/*f.show()執行時,此處打印的也是Zi對象,因爲代碼中沒有new Fu對象,內存中就沒有Fu對象,只有Zi對象*/
}
}
class Zi extends Fu{ //Zi類中沒有重寫show()方法
String s = "zi";
}
public class DuoTaiDemo2 {
public static void main(String[] args) {
Fu f = new Zi();
f.show();/*因爲子類中沒有複寫show()方法,在子類方法區中找不到該方法,從而調用Fu類中的show(),show()方法中使用的成員變量s就近取值,從而也是父類的。父類中的一個方法只有在父類中定義而在子類中沒有重寫的情況下,才能被父類類型的引用調用*/
Zi z = new Zi();
z.show();/*還是調用的父類的show()方法*/
}
}
運行結果是:
fu
Zi1@7f0b8d03
fu
<a target=_blank href="mailto:Zi1@4f57011e" target="_blank">Zi1@4f57011e</a>
另一個程序是:
public class DuoTaiDemo4{
public static void main(String[] args) {
Fu a = new Zi();
a.b();
}
}
class Fu {
public void a() {
System.out.println("fu---a");
}
public void b() {
System.out.println("fu---b");
a();
}
}
class Zi extends Fu{
public void a(){
System.out.println("zi----a");
}
public void b(){
System.out.println("zi----b");
super.b(); /*此處執行的是Fu類的b()函數,這裏明確指明是調用Fu類方法區的b()函數,而在父類的b()方法中再調用a()方法時,執行的是Zi類對象中的a()方法,因爲Zi類複寫了a()方法,此時自動通過this引用變量可以找到子類對象的a()方法*/
System.out.println(super.toString());/*此處打印的是子對象信息,DuoTaiDemo4類的主程序中就new了一個Zi對象,沒有new Fu對象,所以內存中只有一個Zi對象*/
}
}
運行結果是:
zi----b
fu---b
zi----a
Zi@7f0b8d03