Java 抽象類、接口、內部類

抽象類

當編寫一個類時,經常會爲該類定義一些方法,這些方法用以描述該類的行爲方式,那麼這些方法都有具體的方法體。但在某些狀況下,某個父類只是知道其子類應該包含怎樣的方法,但沒法準確地知道這些子類如何實現這些方法。使用抽象方法便可知足該要求:抽象方法是隻有方法簽名,沒有方法實現的方法java

抽象方法和抽象類

抽象方法和抽象類必須使用abstract修飾符來定義,有抽象方法的類只能被定義成抽象類,抽象類裏能夠沒有抽象方法算法

抽象方法和抽象類的規則以下:編程

  • 抽象類必須使用abstract修飾符來修飾,抽象方法也必須使用abstract修飾符來修飾,抽象方法不能有方法體設計模式

  • 抽象類不能被實例化,沒法使用new關鍵字來調用抽象類的構造器建立抽象類的實例。即便抽象類裏不包含抽象方法,這個抽象類也不能建立實例數組

  • 抽象類能夠包含成員變量、方法(普通方法和抽象方法)、構造器、初始化塊、內部類(接口、枚舉)5種成分。抽象類的構造器不能用於建立實例,主要是用於被其子類調用app

  • 含有抽象方法的類(包括直接定義了一個抽象方法;或繼承了一個抽象父類,但沒有徹底實現父類包含的抽象方法;或實現了一個接口,但沒有徹底實現接口包含的抽象方法三種狀況)只能被定義成抽象類this

抽象類與空方法體的方法:public abstract void test(); public void test(){};設計

抽象類不能用於建立實例,只能看成父類被其餘子類繼承code

當使用abstract修飾類時,代表這個類只能被繼承;當使用abstract修飾方法時,代表這個方法必須由子類提供實現(即重寫)。而final修飾的類不能被繼承,final修飾的方法不能被重寫。所以final和abstract永遠不能同時使用對象

abstract不能用於修飾成員變量,不能用於修飾局部變量,即沒有抽象變量、沒有抽象成員變量等說法;abstract也不能用於修飾構造器,沒有抽象構造器,抽象類裏定義的構造器只能是普通構造器

當使用static修飾一個方法時,代表這個方法屬於該類自己,即經過類就可調用該方法,但若是該方法被定義成抽象方法,則將致使經過該類來調用該方法時出現錯誤(調用了一個沒有方法體的方法確定會引發錯誤)。所以static和abstract不能同時修飾某個方法,即沒有所謂的類抽象方法。但static和abstract能夠同時修飾內部類

abstract關鍵字修飾的方法必須被其子類重寫纔有意義,不然這個方法將永遠不會有方法體,所以abstract方法不能定義爲private訪問權限,即private和abstract不能同時修飾方法

抽象類的方法

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

模板模式在面向對象的軟件中很經常使用,其原理簡單,實現也很簡單。下面是使用模板模式的一些簡單規則:

  • 抽象父類能夠只定義須要使用的某些方法,把不能實現的部分抽象成抽象方法,留給其子類去實現

  • 父類中可能包含須要調用其餘系列方法的方法,這些被調用方法既能夠由父類實現,也能夠由其子類實現。父類裏提供的方法只是定義了一個通用算法,其實現也許並不徹底由自身實現,而必須依賴於其子類的輔助

Java8改進的接口

抽象類是從多個類中抽象出來的模板,若是將這種抽象進行得更完全,則能夠提煉出一種更加特殊的「抽象類」——接口(interface),接口裏不能包含普通方法,接口裏的全部方法都是抽象方法。Java8對接口進行了改進,容許在接口中定義默認方法,默認方法能夠提供方法實現

接口的概念

接口定義的是多個類共同的公共行爲規範,這些行爲是與外部交流的通道,這意味着接口裏一般是定義一組公用方法。

Java8中接口的定義

[修飾符]    interface    接口名    extends    父接口1,父接口2...
{
    零個到多個常量定義...
    零個到多個抽象方法定義...
    零個到多個內部類、接口、枚舉定義...
    零個到多個默認方法或類方法定義...
}
  • 修飾符能夠是public或者省略,若是省略了public訪問控制符,則默認採用包權限訪問控制符,即只有在相同包結構下才能夠訪問該接口

  • 接口名應與類名採用相同的命名規則,即若是僅從語法角度來看,接口名只要是合法的標識符便可;若是要遵照Java可讀性規範,則接口名應由多個有意義的單詞連綴而成,每一個單詞首字母大寫,單詞與單詞之間無須任何分隔符。接口名一般可以使用形容詞。

  • 一個接口能夠有多個直接父接口,但接口只能繼承接口,不能繼承類。

因爲接口定義的是一種規範,所以接口裏不能包含構造器和初始化塊定義。接口裏能夠包含成員變量(只能是靜態常量)、方法(只能是抽象實例方法、類方法或默認方法)、內部類(包括內部接口、枚舉)定義

接口裏定義的是多個類共同的公共行爲規範,所以接口裏的全部成員,包括常量、方法、內部類和內部枚舉都是public訪問權限。定義接口成員時,能夠省略訪問控制修飾符,若是指定訪問控制修飾符,則只能使用public訪問控制修飾符。

對於接口裏定義的靜態常量而言,它們是接口相關的,所以系統會自動爲這些成員變量增長static和final兩個修飾符。也就是說,在接口中定義成員變量時,無論是否使用public static final修飾符,接口裏的成員變量老是使用這三個修飾符來修飾。並且接口裏沒有構造器和初始化塊,所以接口裏定義的成員變量只能在定義時指定默認值

接口裏定義的內部類、內部接口、內部枚舉默認都採用public static兩個修飾符,無論定義時是否指定這兩個修飾符,系統都會自動使用public static對它們進行修飾

接口裏定義的方法只能是抽象方法、類方法或默認方法,所以若是不是定義默認方法,系統將自動爲普通方法增長abstract修飾符;定義接口裏的普通方法時無論是否使用public abstract修飾符,接口裏的普通方法老是public abstract來修飾。接口裏的普通方法不能有方法實現(方法體);但類方法、默認(default)方法都必須有方法實現(方法體)

public interface Output 
{
    //接口裏定義的成員變量只能是常量
    int MAX_CACHE_LINE = 50;
    //接口裏定義的普通方法只能是public的抽象方法
    void out();
    void getData(String msg);
    //在接口裏定義默認方法,須要使用default修飾
    default void print(String... msgs)
    {
        for (String msg : msgs) {
            System.out.println(msg);
        }
    }
    //在接口中定義默認方法,須要使用default修飾
    default void test()
    {
        System.out.println("默認的test()方法");
    }
    //在接口裏定義類方法,須要使用static修飾
    static String staticTest()
    {
        return "接口裏的類方法";
    }
}

Java8容許在接口中定義類方法,類方法必須使用static修飾,該方法不能使用default修飾,不管程序是否指定,類方法老是使用public修飾——若是開發者沒有指定public,系統會自動爲類方法添加public修飾符。類方法能夠直接使用接口來調用。

接口的繼承

接口的繼承和類繼承不同,接口徹底支持多繼承,即一個接口能夠有多個直接父接口。和類繼承類似,子接口擴展某個父接口,將會得到父接口裏定義的全部抽象方法、常量。
一個接口繼承多個父接口時,多個父接口排在extends關鍵字以後,多個父接口之間以英文逗號(,)隔開。

使用接口

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

  • 定義變量,也可用於進行強制類型轉換

  • 調用接口中定義的常量

  • 被其餘類實現
    一個類能夠實現一個或多個接口,繼承使用extends關鍵字,實現則使用implements關鍵字。

[修飾符]    class    類名    extends    父類    implements    接口1,接口2
{
    類體部分
}

一個類實現了一個或多個接口以後,這個類必須徹底實現這些接口裏所定義的所有抽象方法(也就是重寫這些抽象方法);不然,該類將保留從父接口那裏繼承到的抽象方法,該類也必須定義成抽象類。
一個類實現某個接口時,該類將會得到接口中定義的常量(成員變量)、方法等,所以能夠把實現接口理解爲一種特殊的繼承,至關於實現類繼承了一個完全抽象的類(至關於除了默認方法外,全部方法都是抽象方法的類)。

//定義一個Product接口
interface Product
{
    int getProduceTime();
}
//讓Printer類實現Output和Product接口
public class Printer implements Output, Product
{
    private String[] printData = new String[MAX_CACHE_LINE];
    //用以記錄當前需打印的做業數
    private int dataNum = 0;
    public void out() 
    {
        //只要有做業,就繼續打印
        while(dataNum >0)
        {
            System.out.println("打印機打印:"+printData[0]);
            //把做業隊列總體前移一位,並將剩下的做業數減1
            System.arraycopy(printData, 1, printData, 0, --dataNum);
        }
    }
    public void getData(String msg)
    {
        if (dataNum >= MAX_CACHE_LINE) 
        {
            System.out.println("輸出隊列已滿,添加失敗");
        }
        else {
            //把打印數據添加到隊列裏,已保存數據的數量加1
            printData[dataNum++] = msg;
        }
    }
    public int getProduceTime() 
    {
        return 45;
    }
    public static void main(String[] args) 
    {
        //建立一個Printer對象,當成Output使用
        Output o = new Printer();
        o.getData("Pringles品客薯片");
        o.getData("酸乳酪洋蔥味薯片");
        o.out();
        o.getData("樂天熊仔餅");
        o.getData("小熊餅");    
        o.out();
        //調用Output接口中定義的默認方法
        o.print("大天狗","妖刀姬","一目連");
        o.test();
        //建立一個Printer對象,當成Product使用
        Product p = new Printer();
        System.out.println(p.getProduceTime());
        //全部接口類型的引用變量均可直接賦給Object類型的變量
        Object object = p;
    }
}

實現接口方法時,必須使用public訪問控制修飾符,由於接口裏的方法都是public的,而子類(至關於實現類)重寫父類方法時訪問權限只能更大或者相等,因此實現類實現接口裏的方法時只能使用public訪問權限

接口和抽象類

接口和抽象類很像,它們都具備以下特徵。

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

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

接口做爲系統與外界交互的窗口,接口體現的是一種規範
抽象類做爲系統中多個子類的共同父類,它所體現的是一種模板式設計

接口和抽象類在用法上的差異:

  • 接口裏只能包含抽象方法和默認方法,不能爲普通方法提供方法實現;抽象類則徹底能夠包含普通方法

  • 接口裏不能定義靜態方法;抽象類裏能夠定義靜態方法

  • 接口裏只能定義靜態常量,不能定義普通成員變量;抽象類裏則既能夠定義普通成員變量,也能夠定義靜態常量

  • 接口裏不包含構造器;抽象類裏能夠包含構造器,抽象類裏的構造器並非用於建立對象,而是讓其子類調用這些構造器來完成屬於抽象類的初始化操做

  • 接口裏不能包含初始化塊,但抽象類則徹底包含初始化塊

  • 一個類最多隻能有一個直接父類,包括抽象類;但一個類能夠直接實現多個接口,經過實現多個接口能夠彌補Java單繼承的不足。

面向接口編程

接口體現的是一種規範和實現分離的設計哲學,充分利用接口能夠極好地下降程序各模塊之間的耦合,從而提升系統的可擴展性和可維護性

1.簡單工廠模式

所謂設計模式,就是對常常出現的軟件設計問題的成熟解決方案。

Computer類組合一個Output類型的對象,將Computer類與Printer類徹底分離。Computer對象實際組合的是Printer對象仍是BetterPrinter對象,對Computer而言徹底透明。當Printer對象切換到BetterPrinter對象時,系統徹底不受影響

public class Computer 
{
    private Output output;
    public Computer(Output output)
    {
        this.output = output;
    }
    // 定義一個模擬獲取字符串輸入的方法
    public void keyIn(String msg) 
    {
        output.getData(msg);
    }
    //定義一個模擬打印的方法
    public void print() 
    {
        output.out();
    }
}

Computer類已經徹底與Printer類分離,只是與Output接口耦合。Computer再也不負責建立Output對象,系統提供一個Output工廠來負責生成Output對象

public class OutputFactory 
{
    public Output getOutput() 
    {
        return new Printer();
    }
    public static void main(String[] args)
    {
        OutputFactory outputFactory = new OutputFactory();
        Computer computer = new Computer(outputFactory.getOutput());
        computer.keyIn("眼前的黑是什麼黑,你說的白是什麼白");
        computer.keyIn("人們說的天空藍,是我記憶中那團白雲背後的藍天");
        computer.print();
    }
}

在該OutputFactory類中包含了一個getOutput()方法,該方法返回一個Output實現類的實例,該方法負責建立Output實例,具體建立哪個實現類的對象由該方法決定(具體由該方法中的粗體部分控制,固然也能夠增長更復雜的控制邏輯)。若是系統須要將Printer改成BetterPrinter實現類,只需讓BetterPrinter實現Output接口,並改變OutputFactory類中的getOutput()方法便可。

public class BetterPrinter implements Output
{
    private String[] printData = new String[MAX_CACHE_LINE];
    //用以記錄當前需打印的做業數
    private int dataNum = 0;
    public void out() 
    {
        //只要有做業,就繼續打印
        while (dataNum > 0) 
        {
            System.out.println("高速打印機正在打印:"+printData[0]);
            //把做業隊列總體前移一位,並將剩下的做業數減1
            System.arraycopy(printData, 1, printData, 0, --dataNum);
        }
    }
    public void getData(String msg)
    {
        if (dataNum >= MAX_CACHE_LINE * 2) 
        {
            System.out.println("輸出隊列已滿,添加失敗");
        }
        else {
            //把打印數據添加到隊列裏,已保存數據的數量加1
            printData[dataNum++] = msg;
        }
    }
}

上面的BetterPrinter類也實現了Output接口,所以也可當成Output對象使用,因而只要把OutputFactory工廠類的getOutput()方法中部分改成以下代碼:

return new BetterPrinter();

經過這種方式,便可把全部生成Output對象的邏輯集中在OutputFactory工廠類中管理,而全部須要使用Output對象的類只需與Output接口耦合,而不是與具體的實現類耦合。即便系統中有不少類使用了Printer對象;只需OutputFactory類的getOutput()方法生成的Output對象是BetterPrinter對象,則它們所有都會改成使用BetterPrinter對象,而全部程序無須修改,只須要修改OutputFactory工廠類的getOutput()方法實現便可

2.命令模式

某個方法須要完成某一個行爲,但這個行爲的具體實現沒法肯定,必須等到執行該方法時才能夠肯定。具體一點:假設有個方法須要遍歷某個數組元素,但沒法肯定在遍歷數組元素時如何處理這些元素,須要在調用該方法時指定具體的處理行爲。

使用一個Command接口來定義一個方法,用這個方法來封裝「處理行爲」,但這個方法沒有方法體——由於如今還沒法肯定這個處理行爲。

public interface Command 
{
    //接口裏定義的process方法用於封裝「處理行爲」
    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 PrinterCommand());
        System.out.println("-------");
        pa.process(target, new AddCommand());
    }
}
public class PrinterCommand implements Command 
{
    public void process(int[] target) 
    {
        for(int tmp :target)
        {
            System.out.println("迭代輸出目標數組的元素:"+tmp);
        }
    }
}
public class AddCommand implements Command 
{
    public void process(int[] target) 
    {
        int sum = 0;
        for (int tmp : target) 
        {
            sum += tmp;
        }
        System.out.println("數組元素的總和是"+sum);
    }
}

內部類

一個類放在另外一個類的內部定義,這個定義在其餘類內部的類就被稱爲內部類,包含內部類的類也被稱爲外部類。內部類主要有以下做用:

  • 內部類提供了更好的封裝,能夠把內部類隱藏在外部類以內,不容許同一個包中的其餘類訪問該類。

  • 內部類成員能夠直接訪問外部類的私有數據,由於內部類被當成其外部類成員,同一個類的成員之間能夠互相訪問。但外部類不能訪問內部類的實現細節,例如內部類的成員變量。

  • 匿名內部類適合用於建立那些僅須要一次使用的類。

內部類與外部類的區別:

  • 內部類比外部類能夠多使用三個修飾符:private、protected、static——外部類不可使用這三個修飾符。

  • 非靜態內部類不能擁有靜態成員。

非靜態內部類

定義內部類很是簡單,只要把一個類放在另外一個類的內部定義便可。這次的「類內部」包括類中的任何位置,甚至在方法中也能夠定義內部類(方法裏定義的內部類被稱爲局部內部類)。內部類定義語法格式以下:

public class OuterClass
{
    //此處能夠定義內部類
}

大部分時候,內部類都被做爲成員內部類定義,而不是做爲局部內部類。成員內部類是一種與成員變量、方法、構造器和初始化塊類似的類成員:局部內部類和匿名內部類則不是類成員。

成員內部類分爲兩種:靜態內部類和非靜態內部類,使用static修飾的成員內部類

外部類的上一級程序單元是包,因此它只有2個做用域:同一包內和任何位置。所以只需2種訪問權限:包訪問和公開訪問權限,正好對應省略訪問控制符和public訪問控制符。省略訪問控制符是包訪問權限,即同一包中的其餘類能夠訪問省略訪問控制符的成員。所以若是一個外部類不使用任何訪問控制符,則只能被同一個包中其餘類訪問。而內部類的上一級程序單元是外部類,他就具備4個做用域:同一個類、同一個包、父子類和任何位置,所以可使用4種訪問控制權限。

成員內部類(包括靜態內部類、非靜態內部類)的class文件老是這種形式:OuterClass$InnerClass.class

在非靜態內部類裏能夠直接訪問外部類的private成員。由於在非靜態內部類對象裏,保存了一個它所寄生的外部類對象的引用(當調用非靜態內部類的實例方法時,必須有一個非靜態內部類實例,非靜態內部類實例必須寄生在外部類實例裏)

當在非靜態內部類的方法訪問某個變量時,系統優秀在該方法內查找是否存在該名字的局部變量,若是存在就使用該變量;若是不存在,則到該方法所在的內部類中查找是否存在該名字的成員變量,若是存在則使用該成員變量;若是不存在,則在該內部類所在的外部類中查找是否存在該名字的成員變量,若是存在則使用該成員變量;若是依然不存在,系統將出現編譯錯誤;提示找不到該變量。
所以,若是外部類成員變量、內部類成員變量與內部類裏方法的局部變量同名,則可經過使用this、外部類類名.this做爲限定來區分。

public class DiscernVariable 
{
    private String prop = "外部類的實例變量";
    private class InClass
    {
        private String prop = "內部類的實例變量";
        public void info()
        {
            String prop = "局部變量";
            // 經過外部類類名.this.varName 訪問外部類實例變量
            System.out.println("外部類的實例變量值:"+DiscernVariable.this.prop);
            // 經過this.varName 訪問內部類實例變量
            System.out.println("內部類的實例變量值:"+this.prop);
            // 直接訪問局部變量
            System.out.println("局部變量值:"+prop);
        }
    }
    public void test() 
    {
        InClass inClass = new InClass();
        inClass.info();
    }        
    public static void main(String[] args) 
    {
        new DiscernVariable().test();
    }
}

非靜態內部類的成員能夠訪問外部類的private成員,但反過來就不成立了。非靜態內部類的成員只在非靜態內部類範圍內是可知的,並不能被外部類直接使用。若是外部類須要訪問非靜態內部類的成員,則必須顯式建立非靜態內部類對象來調用訪問其實例成員。

public class Outer 
{
    private int outProp = 9;
    class Inter
    {
        private int inProp = 5;
        public void accessOuterProp() 
        {
            //非靜態內部類能夠直接訪問外部類的private成員變量
            System.out.println("外部類的outProp值:"+outProp);
        }
    }
    public void accessInterProp() 
    {
        //外部類不能直接訪問非靜態內部類的實例變量
        //下面代碼出現編譯錯誤
        //System.out.println("內部類的inProp值:"+inProp);
        //如需訪問內部類的實例變量,必須顯式建立內部類對象
        System.out.println("內部類的inProp值:"+new Inter().inProp);
    }
        public static void main(String[] args) 
    {
        //執行下面代碼,只建立了外部類對象,還未建立內部類對象
        Outer outer = new Outer();
        outer.accessInterProp();
    }
}

非靜態內部類對象必須寄生在外部類對象裏,而外部類對象則沒必要必定有非靜態內部類對象寄生其中。簡單地講,若是存在一個非靜態內部類對象,則必定存在一個被他寄生的外部類對象。但外部類對象存在時,外部類對象裏不必定寄生了非靜態內部類對象。所以外部類對象訪問非靜態內部類成員時,可能非靜態普通內部類對象根本不存在!而非靜態內部類對象訪問外部類成員時,外部類對象必定存在。

根據靜態成員不能訪問非靜態成員的規則,外部類的靜態成員、靜態代碼塊不能訪問非靜態內部類,包括不能使用非靜態內部類定義變量、建立實例等。總之,不容許在外部類的靜態成員中直接使用非靜態內部類。
Java不容許在非靜態內部類定義靜態成員。非靜態內部類裏不能有靜態方法、靜態成員變量、靜態初始化塊。

靜態內部類

若是使用static來修飾一個內部類,則這個內部類就屬於外部類自己,而不屬於外部類的某個對象。所以使用static修飾的內部類被稱爲類內部類,有的地方也稱爲靜態內部類。

static關鍵字的做用是把類的成員變成類相關,而不是實例相關,即static修飾的成員屬於整個類,而不屬於單個對象。外部類的上一級程序單元是包,因此不可以使用static修飾;而內部類的上一級程序單元是外部類,使用static修飾能夠將內部類變成外部類相關,而不是外部類實例相關。所以static關鍵字不可修飾外部類,但可修飾內部類。

靜態內部類是外部類的類相關的,而不是外部類的對象相關的。也就是說,靜態內部類對象不是寄生在外部類的實例中,而是寄生在外部類的類自己中。當靜態內部類對象存在時,並不存在一個被它寄生的外部類對象,靜態內部類對象只持有外部類的類引用,沒有持有外部類對象的引用。若是容許靜態內部類的實例方法訪問外部類的實例成員,但找不到被寄生的外部類對象,這將引發錯誤。

靜態內部類是外部類的一個靜態成員,所以外部類的全部方法、全部初始化塊中可使用靜態內部類來定義變量、建立對象等。
外部類依然不能直接訪問靜態內部類的成員,但可使用靜態內部類的類名做爲調用者來訪問靜態內部類的類成員,也可使用靜態內部類對象做爲調用者來訪問靜態內部類的實例成員。

使用內部類

1.在外部類使用內部類

能夠直接經過內部類類名來定義變量,經過new調用內部類構造器來建立實例。
區別:不要在外部類的靜態成員(包括靜態方法和靜態初始化塊)中使用非靜態內部類,由於靜態成員不能訪問非靜態成員。

2.在外部類之外使用內部類

但願在外部類之外的地方訪問內部類(靜態、非靜態),則內部類不能使用private訪問控制權限,private修飾的內部類只能在外部類內部使用。
▲ 省略訪問控制符的內部類,只能被與外部類處於同一個包中的其餘類訪問。
▲ 使用protected修飾的內部類,可被與外部類處於同一個包中的其餘類和外部類的子類所訪問。
▲ 使用public修飾的內部類,能夠在任何地方被訪問。

在外部類之外的地方定義內部類(包括靜態和非靜態)變量的語法格式以下:
OuterClass.InnerClass varName

在外部類之外的地方建立非靜態內部類實例的語法:
OuterInstance.new InnerConstructor()
class Out
{
    //定義一個內部類,不使用訪問控制符
    //即只與同一個包中的其餘類可訪問該內部類
    class In
    {
        public In(String msg)
        {
            System.out.println(msg);
        }
    }
}
public class CreateInnerInstance 
{
    public static void main(String[] args) 
    {
        Out.In in = new Out().new In("瞅啥瞅");
        /*
         上面代碼可改成以下三行代碼
         使用OuterClass.InerClass的形式定義內部類變量
         Out.In in;
         建立外部類實例,非靜態內部類實例將寄生在該實例中
         Out out = new Out();
         經過外部類實例和new來調用內部類構造器建立非靜態內部類實例
         in = out.new In("瞅啥瞅");
         */
    }
}

定義一個子類繼承Out類的非靜態內部類In類

public class SubClass2 extends Out.In
{
    //顯式定義SubClass2的構造器
    public SubClass2(Out out)
    {
        //經過傳入的Out對象顯式調用In的構造器
        out.super("瞅你咋地");
    }
}

非靜態內部類In類的構造器必須使用外部類對象來調用,代碼中super表明調用In類的構造器,而out則表明外部類對象(上面的Out、In兩個類直接來自前一個CreateInnerInsatence.java)。能夠看出若是須要建立SubClass2對象時,必須先建立一個Out對象。由於SubClass2是非靜態內部類In類的子類,非靜態內部類In對象裏必須與一個對Out對象的引用,其子類SubClass2對象裏也應該持有對Out對象的引用。當建立SubClass2對象時傳給該構造器的Out對象,就是SubClass2對象裏Out對象引用所指向的對象。

非靜態內部類In對象和SubClass2對象都必須持有指向Outer對象的引用,區別是建立兩種對象時傳入Out對象的方式不一樣:當建立非靜態內部類In類的對象時,必須經過Outer對象來調用new關鍵字;當建立SubClass2類的對象時,必須使用Outer對象做爲調用者來調用In類的構造器。

3.在外部類之外使用靜態內部類

在外部類之外的地方建立靜態內部類實例的語法:
new OutClass.InnerConstructor()

class StaticOut
{
    //定義一個靜態內部類,不使用訪問控制符
    //即同一個包中的其餘類可訪問該內部類
    static class StaticIn
    {
        public StaticIn()
        {
            System.out.println("靜態內部類的構造器");
        }
    }
}

public class CreateStaticInnerInstance 
{
    public static void main(String[] args) 
    {
        StaticOut.StaticIn in = new StaticOut.StaticIn();
        /*
         上面代碼可改成以下兩行代碼
         使用OuterClass.InnerClass的形式定義內部類變量
         StaticOut.StaticIn in;
         經過new來調用內部構造器建立靜態內部類實例
         in = new StaticOut.StaticIn();
         */
    }
}

無論是靜態內部類仍是非靜態內部類,它們聲明變量的語法徹底同樣。區別只是在建立內部類對象時,靜態內部類只需使用外部類便可調用構造器,而非靜態內部類必須使用外部類對象來調用構造器。

使用靜態內部類比使用非靜態內部類要簡單不少,只要把外部類當成靜態內部類的包空間便可。所以當程序須要使用內部類時,應該優先考慮使用靜態內部類。

局部內部類

若是把一個內部類放在方法裏定義,則這個內部類就是一個局部內部類,局部內部類僅在該方法裏有效。因爲局部內部類不能在外部類的方法之外的地方使用,所以局部內部類也不能使用訪問控制符和static修飾符修飾。

對於局部成員而言,無論是局部變量仍是局部內部類,它們的上一級程序單元都是方法,而不是類,使用static修飾它們沒有任何意義。所以,全部的局部成員都不能使用static修飾。

Java8改進的匿名內部類

匿名內部類適合建立只須要一次使用的類。建立匿名內部類時會當即建立一個該類的實例,這個類定義當即消失,匿名內部類不能重複使用。

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

關於匿名內部類規則

  • 匿名內部類不能是抽象類,由於系統在建立匿名內部類時,會當即建立匿名內部類的對象。所以不容許將匿名內部類定義成抽象類

  • 匿名內部類不能定義構造器。因爲匿名內部類沒有類名,因此沒法定義構造器,但匿名內部類能夠定義初始化塊,能夠經過實例初始化塊來完成構造器須要完成的事情。

最經常使用的建立匿名內部類的方式是須要建立某個接口類型的對象

interface Product
{
    public double getPrice();
    public String getName();
}
public class AnonymousTest 
{
    public void test(Product p) 
    {
        System.out.println("購買了一個"+p.getName()
                +",花掉了"+p.getPrice());
    }
    public static void main(String[] args) 
    {
        AnonymousTest ta = new AnonymousTest();
        //調用test()方法時,須要傳入一個Product參數
        //此處傳入其匿名實現類的實例
        ta.test(new Product() 
        {
            public double getPrice() 
            {
                return 567.8;
            }
            public String getName() 
            {
                return "AGP顯卡";
            }
        });
    }
}

上面程序中的AnonymousTest類定義了一個test()方法,該方法須要一個Product對象做爲參數,但Product是接口,沒法直接建立對象,所以這次建立一個Product接口實現類的對象傳入該方法

若是這個Product接口實現類只需使用一次,則採用上述方式,定義匿名內部類

當經過實現接口來建立匿名內部類時,匿名內部類也不能顯式建立構造器,所以匿名內部類只有一個隱式的無參數構造器,故new接口名後的括號裏不能傳入參數值

若是經過繼承父類來建立匿名內部類時,匿名內部類將擁有和父類類似的構造器,這次的類似指的是擁有相同的形參列表

當建立匿名內部類時,必須實現接口或抽象父類裏的全部抽象方法

abstract class Device
{
    private String name;
    public abstract double getPrice();
    public Device(){};
    public Device(String name) 
    {
        this.name =name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class AnonymousInner 
{
    public void test(Device d) 
    {
        System.out.println("購買了一個"+d.getName()+",花掉了"+d.getPrice());
    }
    public static void main(String[] args) 
    {
        AnonymousInner ai = new AnonymousInner();
        //調用有參數的構造器建立Device匿名內部類的對象
        ai.test(new Device("電子示波器") 
        {
            public double getPrice() {
                return 67.8;
            }
        });
    //調用無參數的構造器建立Device匿名內部類的對象
    Device d = new Device() 
    {
        //初始化塊
        {
            System.out.println("匿名內部類的初始化塊...");
        }
        //實現抽象方法
        public double getPrice() {
            return 56.2;
        }
        public String getName() 
        {
            return "鍵盤";
        }
    };
    ai.test(d);
    }
}

當建立Device爲父類的匿名內部類時,既能夠傳入參數,表明父類帶參數的構造器;也能夠不傳入參數,表明調用父類無參數的構造器。

interface A
{
    void test();
}
public class ATest 
{
    public static void main(String[] args) 
    {
        int age = 8;
        A a = new A() 
        {
            public void test() 
            {
                //在Java 8之前下面語句將提示錯誤:age必須使用final修飾
                //從Java 8開始,匿名內部類、局部內部類容許訪問非final局部變量
                System.out.println(age);
            }
        };
        a.test();
    }
}

若是局部變量被匿名內部類訪問,那麼局部變量至關於自動使用了final修飾。

默認方法

Java 8 新增了接口的默認方法。簡單說,默認方法就是接口能夠有實現方法,並且不須要實現類去實現其方法,只需在方法名前面加個default關鍵字便可實現默認方法。

默認方法語法格式以下:

public interface PPAP 
{
    default void print()
    {
        System.out.println("I have an apple, I have a pen~");
    }
}
相關文章
相關標籤/搜索