JAVA抽象類,接口,內部類詳解

一.內容

抽象類

當編寫一個類時,常常會為該類定義一些方法,這些方法用於描述這個類的行為。但在某些情況下隻需要定義出一些方法,而不需要具體的去實現這些行為。也就是說這些方法沒有方法體,隻是一些簽名而已,這樣的方法被稱為抽象方法,包含抽象方法的類被稱為抽象類。

抽象方法與抽象類

抽象方法與抽象類必須使用abstract關鍵字進行修飾,有抽象方法的類必須被定義成抽象類,抽象類裡面可以沒有抽象方法。

抽象類與抽象方法的規則如下:

  • 抽象類與抽象方法必須使用abstract關鍵字進行修飾,抽象方法不能有方法體。
  • 抽象類不能被實例化。即使抽象類不包含抽象方法,也不能被實例化
  • 抽象類可以包含field、方法、構造器、初始化塊、內部類5種成分。
  • 包含抽象方法的類,隻能被定義成抽象類。

語法格式:抽象方法

【修飾符】 abstract 返回值類型 methodName(形參列表);
//示例:
public abstract void runWay();//定義一個行進的方法

註意:抽象方法是沒有方法體,僅僅是一個方法聲明而已。所有Java要求其子類必須將父類中定義的抽象方法進行實現。這也就意味著子類需要先將該方法繼承過來,所以修飾符也就隻能是public或者protected。

示例代碼:抽象類定義

public abstract class Piece{}//定義一個棋子類

註意:抽象類不能實例化,隻能被繼承,抽象類中可以沒有抽象方法,即使抽象類中沒有抽象方法也不能被實例化。

抽象類的使用

抽象類不能創建實例,隻能當成父類來被繼承。抽象類可以看成是從多個具體類中抽象出來的父類,它具有更高層次的抽象。從多個具有相同特征的類中抽象出來的一個抽象類,以這個抽象類作為其子類的模板,從而避免子類設計的隨意性。

抽象類的體現就是一種模板模式的設計,抽象類作為多個子類的通用模板,子類在抽象類的基礎上進行擴展、改造,但子類總體上會保留抽象類的行為方式。

編寫一個抽象父類,父類提供瞭多個子類的通用方法,並把一個或多個方法留給其子類實現,這就是一種模板模式,模板模式也是十分常見的設計模式。

示例代碼:

//定義抽象父類Piece
public abstract class Piece{
    /**
    *定義棋子的行進方法
    */
    public abstract void runWay();
    /**
    *定義棋子進攻的方法
    */
    public void attack() { 
        System.out.println("吃掉下面的棋子");
    }
}
//Piece的子類Horse
public class Horse extends Piece{
    /**
    *實現父類中定義的抽象方法
    */
    public void runWay() {
        System.out.println("按照日字格行進!");
    }
}
//Piece的子類Cannon
public class Cannon extends Piece{
    /**
    *實現父類中定義的抽象方法
    */
    public void runWay() { 
        System.out.println("按照直線方式行進!");
    }
}

模板模式在面向對象的軟件中很常用,其原理簡單,實現也很簡單。使用模板模式有如下規則:抽象父類可以隻定義需要使用的方法,把不能實現的部分抽象成抽象方法留給子類去實現。

接口

抽象類是從多個類中抽象出來的模板,如果將這種抽象進行得更徹底,則可以提煉出一種更加特殊的“抽象類”——接口(interface)。接口裡不能包含普通方法,接口裡的所有方法都是抽象方法。接口裡面可以放:常量,抽象方法,默認方法,靜態方法,私有化方法

接口的概念

Java中的接口是一系列方法的聲明,是一些方法特征的集合,一個接口隻有方法的特征沒有方法的實現,因此這些方法可以在不同的地方被不同的類實現,而這些實現可以具有不同的行為(功能)。

接口的定義

接口和類定義不同,定義接口不在使用class關鍵字,而是使用interface關鍵字。

語法格式如下

【public】interface 接口名 extends 父接口1,父接口2{
    //零到多個常量定義
    //零到多個抽象方法定義
}

語法分析:

  • 修飾符可以是public或者protected,大多數是使用public,protected省略采用默認包權限訪問控制符。
  • 接口名應與類名采用相同的命名規則。
  • 一個接口可以有多個直接父接口,但接口隻能繼承接口,不能繼承類。

接口中的成員變量

接口定義的是一種規范,因此接口裡不能包含構造器和初始化塊定義。接口裡可以包含field、方法、內部類定義。因為接口沒有構造器與初始化塊,因此系統不能為field進行默認的初始化操作,隻能由程序編寫人員為field指定默認的值,所以field隻能是常量。又因為field隻能是常量,所以系統自動為這些field增加瞭static和final兩個修飾符。也就是說在接口中定義的Field不管是否使用瞭public static final修飾符,接口裡的Field總是默認使用public static final修飾符來進行修飾,不可更改。

示例代碼

interface DBobjectType{
    //對於在接口中定義的成員變量,用或不用public static final,意義都是相同的
    public static final int ROOT=0;
    public static final int DATABASE=1; 
    int TABLE=2; 
    int COLUMN=3;
    int INDEX=4;
}

接口中的方法

接口裡定義的方法都是抽象方法,因此系統會自動為方法增加public abstract修飾符。因此不管定義接口方法時是否使用瞭public abstract修飾符,系統都會默認方法使用public abstract修飾符來進行修飾。

示例代碼:

public interface DataConnection{
    /**
    *定義獲取數據庫連接的方法*/
    public abstract void getConnection();
    /**
    *定義關閉數據庫連接的方法,在接口中是否使用public abstract意義相同*/
    void close();
}

接口的繼承

接口的繼承與類的繼承不一樣,接口完全支持多繼承,即一個接口可以有多個直接父接口。和繼承相似,子接口擴展父接口,將會獲得父接口裡定義的所有抽象方法、field、內部類和枚舉定義。

一個接口繼承多個父接口時,多個父接口排在extends關鍵字之後,多個父接口之間使用英文逗號(,)進行分隔。

示例代碼:

public interface InterA{//定義接口A
    void a();
}
public interface InterB{//定義接口B
    void b();
}
public interface Inter extends InterA,InterB{//定義接口Inter繼承A、B
    voidc();
}

接口的實現/使用

接口不能用於創建實例,但接口可以用於聲明引用類型變量。當使用接口來聲明引用類型變量時,這個引用類型變量必須引用到其實現類的對象。除此之外,接口的主要用途就是被實現類進行實現。

一個類可以實現多個接口,繼承使用extends關鍵字,而實現則使用implements關鍵字。

單繼承多實現

示例代碼:

public interface InterA {
  void a();
}
public class InterAImpl implements InterA
{
    @Override
    public void a(){
        System.out.println("將接口InterA中定義的抽象方法進行實現!");
    }
}

實現接口與繼承類相似,一樣可以獲得所實現接口裡定義的常量field、抽象方法、內部類和枚舉類定義。讓類實現接口需要在類定義後面增加implements部分,當需要實現多個接口時,多個接口之間以英文逗號(,)隔開。一個類可以繼承一個父類並同時實現多個接口,implements部分必須放在extends部分之後。

示例代碼:

public interface DBobjectType {
    public static final int ROOT =0; 
    public static final int DATABASE =1;   
    int TABLE=2;
    int COLUMN=3;
    int INDEX=4;
}
public interface DataConnection{
    /**
    *定義獲取數據庫連接的方法
    */
    public abstract void getConnection();
    /**
    *定義關閉數據庫連接的方法,在接口中是否使用publicabstract意義相同
    */
    void close();
}
public class ConnectionImpl implements
    DBobjectType,DataConnection {
    @Override
    public void getConnection() {
        System.out.println("獲取一個連接對象!");
    }
    @Override
    public void close() {
        System.out.println("將連接進行關閉!");
    }
    public static void main(String[]args){
        ConnectionImpl impl=new ConnectionImpl();
        //直接使用從DBobjectType繼承過來的成員變量定義
        System.out.println("數據對象類型:"+ConnectionImpl.ROOT);
        impl.getConnection();
    }
}

一個類實現瞭一個或多個接口之後,這個類必須完全實現這些接口裡所定義的全部抽象方法,否則該類將保留從父接口那裡繼承到的抽象方法,該類也必須定義成抽象類。

接口與抽象類的差異

1、接口和抽象類都不能進行實例化,它們都位於繼承樹的頂端,用於被其他類實現和繼承。

2、接口和抽象類都可以包含抽象方法,實現接口或繼承抽象類的普通子類都必須實現這些抽象方法。

接口作為系統與外界交互的窗口,接口體現的是一種規范。對於接口的實現者而言,接口規定瞭實現者必須向外提供哪些服務。對於接口的調用者而言,接口規定瞭調用者可以調用哪些服務。當在一個程序中使用接口時,接口是多個模塊間的耦合標準,當在多個應用程序之間使用接口時,接口是多個程序之間的通信標準。

接口類似於系統的總綱,一旦接口發生變化,對於整個系統是輻射式的,所有實現這個接口的普通類都要進行改寫。

分析:JDBC編程是後面需要給大傢講到的數據庫編程,在此示例中不涉及到具體代碼,隻看結構圖。Java程序不可能為行業內使用個各種數據庫都提供一套連接方式。它隻會提供一套標準的接口,告訴數據庫生產廠商應該提供哪些實現。所以不同的數據庫生產廠商需要將Java提出的接口進行底層的實現,當我們需要進行JDBC編程時,隻需要將不同數據庫生產廠商提供的JAR包導入進來,按照接口的方式進行編程即可

抽象類則不一樣,抽象類作為系統中多個子類的共同父類,它所體現的是一種模版式設計。抽象類作為多個子類的抽象父類,可以被當成系統實現過程中的中間產品。這個中間產品已經實現瞭系統的部分功能,但這個類不能稱為最終產品,必須有更進一步的完善,這種完善可能有幾種不同的方式來實現。

接口與抽象類在用法上也存在如下差異:

  • 接口裡隻能包含抽象方法,不包含已經提供實現的方法,抽象類則完全可以包含普通方法。
  • 接口和抽象類裡都可以定義靜態方法。
  • 接口裡隻能定義靜態常量Field,不能定義普通的Field,抽象類裡則都可以。
  • 接口裡不包含構造器,抽象類裡可以包含構造器,抽象類裡的構造器並不是用來創建對象,而是讓其子類調用這些構造器完成屬於抽象類的初始化操作。
  • 接口裡不能包含初始化塊,但抽象類則完全可以包含初始化塊。
  • 一個類最多隻有一個父類,包括抽象類,但是一個類可以實現多個接口。

面向接口編程

接口體現的是一種規范和實現分離的設計模式,充分利用接口可以很好降低程序各模塊之間的耦合,從而提高系統的可擴展性和可維護性。

基於這種原則,軟件架構設計理論都倡導“面向接口”編程,而不是面向實現類編程,希望通過面向接口編程來降低程序的耦合。下面使用數據庫編程這種場景來示范面向接口編程的優勢。

//定義接口規范
public interface DataConnection{
    /**
    *定義獲取數據庫連接的方法*/
    public abstract void getConnection();
    /**
    *定義關閉數據庫連接的方法
    */
    public abstract void close();
}
//定義不同數據庫的實現類MySql
public class MySqlConnection implements DataConnection {
    @Override
    public void getConnection() {
        System.out.println("獲取MySql數據庫的連接。。。");
    }
    @Override
    public void close() {
        System.out.println("關閉MySql數據庫的連接。。。");
    }
}
//定義不同數據庫的實現類Oracle
public class OracleConnection implements DataConnection {
    @Override
    public void getConnection(){
        System.out.println("獲取Oracle數據庫的連接。。。");
    }
    @Override
    public void close() {
        System.out.println("關閉Oracle數據庫的連接。。。");
    }
}
//定義不同數據庫的實現類SqlServer
public class SqlServerConnection implements DataConnection {
    @Override
    public void getConnection() {
        System.out.println("獲取SqlServer數據庫的連接。。。");
    }
    @Override 
    public void close() {
        System.out.println("關閉SqlServer數據庫的連接。。。");
    }
}

內部類

在定義類的時候,我們一般把類定義成一個獨立的程序單元。但是在某些情況下,我們會把一個類放在另一個類的內部定義,這個定義在其他類內部的類就被稱為內部類,也可以稱為嵌套類。包含內部類的類也被稱為外部類,也可以稱為宿主類。Java從JDK1.1開始引入內部類,內部類的主要作用如下:

  • 內部類提供瞭更好的封裝,可以把內部類隱藏在外部類之內,不允許同一個包中其他類訪問該類。
  • 內部類成員可以直接訪問外部類的私有數據,因為內部類被當成外部類的成員,同一個類成員之間可以相互訪問。
  • 匿名內部類適合用於創建那些僅需要一次使用的類。

非靜態內部類

定義內部類非常簡單,隻要把一個類放在另一個類內部定義即可。此處的“內部類”包括類中的任何位置,甚至在方法中也可以定義內部類,在方法中定義的內部類叫做局部內部類。

通常情況下,內部類都被作為成員內部類定義,而不是作為局部內部類。成員內部類是一種與成員field、成員方法、構造方法和初始化塊相同級別的類成員。

成員內部類分為:靜態內部類和非靜態內部類兩種,使用static修飾的成員內部類就是靜態內部類,沒有使用static修飾的成員內部類就是非靜態內部類。

因為內部類作為其外部類的成員,所以可以使用任意訪問控制符:private、protected、public修飾的Field成員。

示例代碼:

public class DiningRoom {
    private String egg="雞蛋";
    class Cook { 
        public void makeFood() {
            //使用瞭外部類DiningRoom中定義的私有成員egg
            System.out.println("廚師使用"+egg+",做瞭一份炒雞蛋!");
        }
    }
    /**
    *DiningRoom類對外提供的做炒雞蛋的功能*實際該功能是由內部類Cook來執行的
    */
    public void fireEgg(){
        new Cook().makeFood();
    }
}

代碼優化

假設內部類Cook提供瞭多個方法,而外部類中定義的多個方法又多次用到瞭Cook中提供的方法,那麼類似上述示例中的調用方式,就會每次調用都會創建一個Cook的對象,用完即丟棄瞭,造成瞭程序上性能的降低。像這種情況我們就可以在父類DiningRoom中定義Cook的成員變量即可。

public class DiningRoom {
    private String egg="雞蛋";
    private Cook cook=new Cook();
    class Cook {
        public void makeFood() {
            //使用瞭外部類DiningRoom中定義的私有成員egg
            System.out.println("廚師使用"+egg+",做瞭一份炒雞蛋!");
        }
    }
    /**
    *DiningRoom類對外提供的做炒雞蛋的功能*實際該功能是由內部類Cook來執行的
    */
    public void fireEgg(){ 
        this.cook.makeFood();
    }
}

分析:通過代碼的改寫,那麼即使父類DiningRoom中的多個方法多次調用Cook類中定義的方法時,也隻會創建Cook的一個對象,而不是多個對象。

靜態內部類

使用static修飾符來修飾內部類就稱為靜態內部類,則這個內部類就屬於外部類本身,而不屬於外部類的某個對象。因此使用static修飾的內部類被稱為靜態內部類

靜態內部類可以包含靜態成員,也可以包含非靜態成員。根據靜態成員不能訪問非靜態成員的規則,靜態內部類不能訪問外部類的實例成員,隻能訪問外部類的類成員。即使是靜態內部類的實例方法也不能訪問外部類的實例成員,隻能訪問外部類的靜態成員。

示例代碼:

public class Factory {
    public static String noodle="面條";
    public static String dumplings="水餃";
    private String chicken="雞肉";
    public void madamFood(String food){
        System.out.println("廠長吃的是自己夫人做的飯!"+food);
    }
    public static void diningFood(String food){
        System.out.println("工人吃的是食堂做的飯!"+food);
    }
    static class DiningRoom {
        public static void eat(){
        	diningFood(Factory.noodle);
        }
        public void managerEat() {
            //編譯報錯,在靜態內部類中,即使是非靜態的成員
            //也不能訪問外部類的非靜態的成員
            madamFood("abc");
        }
    }
}

分析:在上述示例中,靜態內部類DiningRoom在進行編譯時報錯,提示對象的實例上調用瞭不在范圍內的數據信息。因為靜態內部類會跟隨外部類的靜態的信息同時存在,此時可以創建靜態內部類的實例對象,但是並不一定會創建外部類的實例對象,那麼去訪問外部類實例對象的成員就會出問題,因為對象都沒有,怎麼訪問對象上的成員呢?

局部內部類

如果把一個內部類定義在方法裡面定義,則這個內部類就是一個局部內部類。

示例代碼:

public void run() {
    //定義一個局部內部類,作用范圍更小,在方法外根本無法訪問
    class InnerTest {
        public int num2=5;
        public void run(){
            System.out.println(num2);
        }
    }
    InnerTest it=new InnerTest();
    it.run();
}

匿名內部類

匿名內部類的語法有些特別,創建匿名內部類時會立即創建一個該類的實例,這個類定義立即消失,匿名內部類不能重復使用。因此匿名內部類適合創建那種隻需要一次使用的類。

語法格式:

new 父類構造器|實現接口 (){
    //匿名內部類的類體部分
}

匿名內部類必須繼承一個父類,或實現一個接口,但最多隻能繼承一個父類,實現一個接口。

  • 匿名內部類不能是抽象類,因為系統在創建匿名內部類時,會立即創建匿名內部類的對象。
  • 匿名內部類不能定義構造器,因為匿名內部類沒有類名,也就無法定義構造器,但是匿名內部類可以定義實例初始化塊,通過初始化塊來完成初始化操作。

在Java的類庫中,有很多非常有用的工具類提供瞭大量的底層操作,可以讓程序開發人員能夠快速的進行軟件的業務邏輯開發,而不用去關註底層的實現。但是有些方法確實需要接收一些參數的,而這些參數都是接口類型,當程序開發人員調用此方法時,就必須要提供該接口的一個實現類,再去創建該實現類的對象才能去調用那些方法。

如果僅僅是為瞭調用某個方法,就為此去創建一個新的類就有點得不償失瞭。因此Java提供瞭匿名內部類的方式可以非常有效的解決此類問題。

示例代碼:

public class Test { 
    public static void main(String[]args) {
        Integer [] nums={1,3,10,21,14,5,27};
        //sort方法需要接收一個Comparator排序器對象,Comparator是接口類型
        Arrays.sort (nums,new Comparator <Integer>(){
            @Override 
            public int compare (Integer int1,Integer int2){ 
                if(int1>int2){
                    return-1;               
                }else {
                    return1;
                }
            }
        });
        System.out.println(Arrays.toString(nums));
    }
}

代碼分析:Comparator<T>是一個排序器接口,用於進行兩個數據之間的比較,數據類型需要通過<>這種方式在其中定義出來,compare就是接口中定義的方法,如果大於返回1,小於返回1,等於返回0。就是普通的升序排序,而如果反過來就是降序排序。

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: