摘要:即使抽象類里不包含抽象方法,這個抽象類也不能創(chuàng)建實例抽象類可以包含成員變量方法普通方法和抽象方法構(gòu)造器初始化塊內(nèi)部類接口枚舉種成分。
抽象類
當(dāng)編寫一個類時,常常會為該類定義一些方法,這些方法用以描述該類的行為方式,那么這些方法都有具體的方法體。但在某些情況下,某個父類只是知道其子類應(yīng)該包含怎樣的方法,但無法準確地知道這些子類如何實現(xiàn)這些方法。使用抽象方法即可滿足該要求:抽象方法是只有方法簽名,沒有方法實現(xiàn)的方法
抽象方法和抽象類抽象方法和抽象類必須使用abstract修飾符來定義,有抽象方法的類只能被定義成抽象類,抽象類里可以沒有抽象方法
抽象方法和抽象類的規(guī)則如下:
抽象類必須使用abstract修飾符來修飾,抽象方法也必須使用abstract修飾符來修飾,抽象方法不能有方法體
抽象類不能被實例化,無法使用new關(guān)鍵字來調(diào)用抽象類的構(gòu)造器創(chuàng)建抽象類的實例。即使抽象類里不包含抽象方法,這個抽象類也不能創(chuàng)建實例
抽象類可以包含成員變量、方法(普通方法和抽象方法)、構(gòu)造器、初始化塊、內(nèi)部類(接口、枚舉)5種成分。抽象類的構(gòu)造器不能用于創(chuàng)建實例,主要是用于被其子類調(diào)用
含有抽象方法的類(包括直接定義了一個抽象方法;或繼承了一個抽象父類,但沒有完全實現(xiàn)父類包含的抽象方法;或?qū)崿F(xiàn)了一個接口,但沒有完全實現(xiàn)接口包含的抽象方法三種情況)只能被定義成抽象類
抽象類與空方法體的方法:public abstract void test(); public void test(){};
抽象類不能用于創(chuàng)建實例,只能當(dāng)作父類被其他子類繼承
當(dāng)使用abstract修飾類時,表明這個類只能被繼承;當(dāng)使用abstract修飾方法時,表明這個方法必須由子類提供實現(xiàn)(即重寫)。而final修飾的類不能被繼承,final修飾的方法不能被重寫。因此final和abstract永遠不能同時使用
abstract不能用于修飾成員變量,不能用于修飾局部變量,即沒有抽象變量、沒有抽象成員變量等說法;abstract也不能用于修飾構(gòu)造器,沒有抽象構(gòu)造器,抽象類里定義的構(gòu)造器只能是普通構(gòu)造器
當(dāng)使用static修飾一個方法時,表明這個方法屬于該類本身,即通過類就可調(diào)用該方法,但如果該方法被定義成抽象方法,則將導(dǎo)致通過該類來調(diào)用該方法時出現(xiàn)錯誤(調(diào)用了一個沒有方法體的方法肯定會引起錯誤)。因此static和abstract不能同時修飾某個方法,即沒有所謂的類抽象方法。但static和abstract可以同時修飾內(nèi)部類
abstract關(guān)鍵字修飾的方法必須被其子類重寫才有意義,否則這個方法將永遠不會有方法體,因此abstract方法不能定義為private訪問權(quán)限,即private和abstract不能同時修飾方法
抽象類的方法抽象類體現(xiàn)的就是一種模板模式的設(shè)計,抽象類作為多個子類的通用模板,子類在抽象類的基礎(chǔ)上進行擴展、改造,但子類總體上會大致保留抽象類的行為方式。
模板模式在面向?qū)ο蟮能浖泻艹S茫湓砗唵危瑢崿F(xiàn)也很簡單。下面是使用模板模式的一些簡單規(guī)則:
抽象父類可以只定義需要使用的某些方法,把不能實現(xiàn)的部分抽象成抽象方法,留給其子類去實現(xiàn)
父類中可能包含需要調(diào)用其他系列方法的方法,這些被調(diào)用方法既可以由父類實現(xiàn),也可以由其子類實現(xiàn)。父類里提供的方法只是定義了一個通用算法,其實現(xiàn)也許并不完全由自身實現(xiàn),而必須依賴于其子類的輔助
Java8改進的接口抽象類是從多個類中抽象出來的模板,如果將這種抽象進行得更徹底,則可以提煉出一種更加特殊的“抽象類”——接口(interface),接口里不能包含普通方法,接口里的所有方法都是抽象方法。Java8對接口進行了改進,允許在接口中定義默認方法,默認方法可以提供方法實現(xiàn)
接口的概念接口定義的是多個類共同的公共行為規(guī)范,這些行為是與外部交流的通道,這意味著接口里通常是定義一組公用方法。
Java8中接口的定義[修飾符] interface 接口名 extends 父接口1,父接口2... { 零個到多個常量定義... 零個到多個抽象方法定義... 零個到多個內(nèi)部類、接口、枚舉定義... 零個到多個默認方法或類方法定義... }
修飾符可以是public或者省略,如果省略了public訪問控制符,則默認采用包權(quán)限訪問控制符,即只有在相同包結(jié)構(gòu)下才可以訪問該接口
接口名應(yīng)與類名采用相同的命名規(guī)則,即如果僅從語法角度來看,接口名只要是合法的標識符即可;如果要遵守Java可讀性規(guī)范,則接口名應(yīng)由多個有意義的單詞連綴而成,每個單詞首字母大寫,單詞與單詞之間無須任何分隔符。接口名通常能夠使用形容詞。
一個接口可以有多個直接父接口,但接口只能繼承接口,不能繼承類。
由于接口定義的是一種規(guī)范,因此接口里不能包含構(gòu)造器和初始化塊定義。接口里可以包含成員變量(只能是靜態(tài)常量)、方法(只能是抽象實例方法、類方法或默認方法)、內(nèi)部類(包括內(nèi)部接口、枚舉)定義
接口里定義的是多個類共同的公共行為規(guī)范,因此接口里的所有成員,包括常量、方法、內(nèi)部類和內(nèi)部枚舉都是public訪問權(quán)限。定義接口成員時,可以省略訪問控制修飾符,如果指定訪問控制修飾符,則只能使用public訪問控制修飾符。
對于接口里定義的靜態(tài)常量而言,它們是接口相關(guān)的,因此系統(tǒng)會自動為這些成員變量增加static和final兩個修飾符。也就是說,在接口中定義成員變量時,不管是否使用public static final修飾符,接口里的成員變量總是使用這三個修飾符來修飾。而且接口里沒有構(gòu)造器和初始化塊,因此接口里定義的成員變量只能在定義時指定默認值
接口里定義的內(nèi)部類、內(nèi)部接口、內(nèi)部枚舉默認都采用public static兩個修飾符,不管定義時是否指定這兩個修飾符,系統(tǒng)都會自動使用public static對它們進行修飾
接口里定義的方法只能是抽象方法、類方法或默認方法,因此如果不是定義默認方法,系統(tǒng)將自動為普通方法增加abstract修飾符;定義接口里的普通方法時不管是否使用public abstract修飾符,接口里的普通方法總是public abstract來修飾。接口里的普通方法不能有方法實現(xiàn)(方法體);但類方法、默認(default)方法都必須有方法實現(xiàn)(方法體)
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修飾——如果開發(fā)者沒有指定public,系統(tǒng)會自動為類方法添加public修飾符。類方法可以直接使用接口來調(diào)用。
接口的繼承接口的繼承和類繼承不一樣,接口完全支持多繼承,即一個接口可以有多個直接父接口。和類繼承相似,子接口擴展某個父接口,將會獲得父接口里定義的所有抽象方法、常量。
一個接口繼承多個父接口時,多個父接口排在extends關(guān)鍵字之后,多個父接口之間以英文逗號(,)隔開。
接口不能用于創(chuàng)建實例,但接口可以用于聲明引用類型變量。當(dāng)使用接口來聲明引用類型變量時,這個引用類型變量必須引用到其實現(xiàn)類的對象。除此之外,接口的主要用途就是被實現(xiàn)類實現(xiàn)。
定義變量,也可用于進行強制類型轉(zhuǎn)換
調(diào)用接口中定義的常量
被其他類實現(xiàn)
一個類可以實現(xiàn)一個或多個接口,繼承使用extends關(guān)鍵字,實現(xiàn)則使用implements關(guān)鍵字。
[修飾符] class 類名 extends 父類 implements 接口1,接口2 { 類體部分 }
一個類實現(xiàn)了一個或多個接口之后,這個類必須完全實現(xiàn)這些接口里所定義的全部抽象方法(也就是重寫這些抽象方法);否則,該類將保留從父接口那里繼承到的抽象方法,該類也必須定義成抽象類。
一個類實現(xiàn)某個接口時,該類將會獲得接口中定義的常量(成員變量)、方法等,因此可以把實現(xiàn)接口理解為一種特殊的繼承,相當(dāng)于實現(xiàn)類繼承了一個徹底抽象的類(相當(dāng)于除了默認方法外,所有方法都是抽象方法的類)。
//定義一個Product接口 interface Product { int getProduceTime(); } //讓Printer類實現(xiàn)Output和Product接口 public class Printer implements Output, Product { private String[] printData = new String[MAX_CACHE_LINE]; //用以記錄當(dāng)前需打印的作業(yè)數(shù) private int dataNum = 0; public void out() { //只要有作業(yè),就繼續(xù)打印 while(dataNum >0) { System.out.println("打印機打印:"+printData[0]); //把作業(yè)隊列整體前移一位,并將剩下的作業(yè)數(shù)減1 System.arraycopy(printData, 1, printData, 0, --dataNum); } } public void getData(String msg) { if (dataNum >= MAX_CACHE_LINE) { System.out.println("輸出隊列已滿,添加失敗"); } else { //把打印數(shù)據(jù)添加到隊列里,已保存數(shù)據(jù)的數(shù)量加1 printData[dataNum++] = msg; } } public int getProduceTime() { return 45; } public static void main(String[] args) { //創(chuàng)建一個Printer對象,當(dāng)成Output使用 Output o = new Printer(); o.getData("Pringles品客薯片"); o.getData("酸乳酪洋蔥味薯片"); o.out(); o.getData("樂天熊仔餅"); o.getData("小熊餅"); o.out(); //調(diào)用Output接口中定義的默認方法 o.print("大天狗","妖刀姬","一目連"); o.test(); //創(chuàng)建一個Printer對象,當(dāng)成Product使用 Product p = new Printer(); System.out.println(p.getProduceTime()); //所有接口類型的引用變量都可直接賦給Object類型的變量 Object object = p; } }
實現(xiàn)接口方法時,必須使用public訪問控制修飾符,因為接口里的方法都是public的,而子類(相當(dāng)于實現(xiàn)類)重寫父類方法時訪問權(quán)限只能更大或者相等,所以實現(xiàn)類實現(xiàn)接口里的方法時只能使用public訪問權(quán)限
接口和抽象類接口和抽象類很像,它們都具有如下特征。
接口和抽象類都不能被實例化,它們都位于繼承樹的頂端,用于被其他類實現(xiàn)和繼承。
接口和抽象類都可以包含抽象方法,實現(xiàn)接口或繼承抽象類的普通子類都必須實現(xiàn)這些抽象方法。
接口作為系統(tǒng)與外界交互的窗口,接口體現(xiàn)的是一種規(guī)范
抽象類作為系統(tǒng)中多個子類的共同父類,它所體現(xiàn)的是一種模板式設(shè)計
接口和抽象類在用法上的差別:
接口里只能包含抽象方法和默認方法,不能為普通方法提供方法實現(xiàn);抽象類則完全可以包含普通方法
接口里不能定義靜態(tài)方法;抽象類里可以定義靜態(tài)方法
接口里只能定義靜態(tài)常量,不能定義普通成員變量;抽象類里則既可以定義普通成員變量,也可以定義靜態(tài)常量
接口里不包含構(gòu)造器;抽象類里可以包含構(gòu)造器,抽象類里的構(gòu)造器并不是用于創(chuàng)建對象,而是讓其子類調(diào)用這些構(gòu)造器來完成屬于抽象類的初始化操作
接口里不能包含初始化塊,但抽象類則完全包含初始化塊
一個類最多只能有一個直接父類,包括抽象類;但一個類可以直接實現(xiàn)多個接口,通過實現(xiàn)多個接口可以彌補Java單繼承的不足。
面向接口編程接口體現(xiàn)的是一種規(guī)范和實現(xiàn)分離的設(shè)計哲學(xué),充分利用接口可以極好地降低程序各模塊之間的耦合,從而提高系統(tǒng)的可擴展性和可維護性
1.簡單工廠模式
所謂設(shè)計模式,就是對經(jīng)常出現(xiàn)的軟件設(shè)計問題的成熟解決方案。
Computer類組合一個Output類型的對象,將Computer類與Printer類完全分離。Computer對象實際組合的是Printer對象還是BetterPrinter對象,對Computer而言完全透明。當(dāng)Printer對象切換到BetterPrinter對象時,系統(tǒng)完全不受影響
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類已經(jīng)完全與Printer類分離,只是與Output接口耦合。Computer不再負責(zé)創(chuàng)建Output對象,系統(tǒng)提供一個Output工廠來負責(zé)生成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實現(xiàn)類的實例,該方法負責(zé)創(chuàng)建Output實例,具體創(chuàng)建哪一個實現(xiàn)類的對象由該方法決定(具體由該方法中的粗體部分控制,當(dāng)然也可以增加更復(fù)雜的控制邏輯)。如果系統(tǒng)需要將Printer改為BetterPrinter實現(xiàn)類,只需讓BetterPrinter實現(xiàn)Output接口,并改變OutputFactory類中的getOutput()方法即可。
public class BetterPrinter implements Output { private String[] printData = new String[MAX_CACHE_LINE]; //用以記錄當(dāng)前需打印的作業(yè)數(shù) private int dataNum = 0; public void out() { //只要有作業(yè),就繼續(xù)打印 while (dataNum > 0) { System.out.println("高速打印機正在打印:"+printData[0]); //把作業(yè)隊列整體前移一位,并將剩下的作業(yè)數(shù)減1 System.arraycopy(printData, 1, printData, 0, --dataNum); } } public void getData(String msg) { if (dataNum >= MAX_CACHE_LINE * 2) { System.out.println("輸出隊列已滿,添加失敗"); } else { //把打印數(shù)據(jù)添加到隊列里,已保存數(shù)據(jù)的數(shù)量加1 printData[dataNum++] = msg; } } }
上面的BetterPrinter類也實現(xiàn)了Output接口,因此也可當(dāng)成Output對象使用,于是只要把OutputFactory工廠類的getOutput()方法中部分改為如下代碼:
return new BetterPrinter();
通過這種方式,即可把所有生成Output對象的邏輯集中在OutputFactory工廠類中管理,而所有需要使用Output對象的類只需與Output接口耦合,而不是與具體的實現(xiàn)類耦合。即使系統(tǒng)中有很多類使用了Printer對象;只需OutputFactory類的getOutput()方法生成的Output對象是BetterPrinter對象,則它們?nèi)慷紩臑槭褂肂etterPrinter對象,而所有程序無須修改,只需要修改OutputFactory工廠類的getOutput()方法實現(xiàn)即可
2.命令模式
某個方法需要完成某一個行為,但這個行為的具體實現(xiàn)無法確定,必須等到執(zhí)行該方法時才可以確定。具體一點:假設(shè)有個方法需要遍歷某個數(shù)組元素,但無法確定在遍歷數(shù)組元素時如何處理這些元素,需要在調(diào)用該方法時指定具體的處理行為。
使用一個Command接口來定義一個方法,用這個方法來封裝“處理行為”,但這個方法沒有方法體——因為現(xiàn)在還無法確定這個處理行為。
public interface Command { //接口里定義的process方法用于封裝“處理行為” void process(int[] target); }
下面是需要處理數(shù)組的處理類,在這個處理中包含了一個process()方法,這個方法無法確定處理數(shù)組的處理行為,所以定義該方法時使用了一個Command參數(shù),這個Command參數(shù)負責(zé)對數(shù)組的處理行為。
public class ProcessArray { public void process(int[] target, Command cmd) { cmd.process(target); } }
通過一個Command接口,就實現(xiàn)了讓ProcessArray類和具體“處理行為”的分離,程序使用Command接口代表了對數(shù)組的處理行為。Command接口也沒有提供真正的處理,只要等到調(diào)用ProcessArray對象的process()方法時,才真正傳入一個Command對象,才確定對數(shù)組的處理行為。
public class CommandTest { public static void main(String[] args) { ProcessArray pa = new ProcessArray(); int[] target = {3, -4, 6, 4}; //第一次處理數(shù)組,具體處理行為取決于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("迭代輸出目標數(shù)組的元素:"+tmp); } } }
public class AddCommand implements Command { public void process(int[] target) { int sum = 0; for (int tmp : target) { sum += tmp; } System.out.println("數(shù)組元素的總和是"+sum); } }內(nèi)部類
一個類放在另一個類的內(nèi)部定義,這個定義在其他類內(nèi)部的類就被稱為內(nèi)部類,包含內(nèi)部類的類也被稱為外部類。內(nèi)部類主要有如下作用:
內(nèi)部類提供了更好的封裝,可以把內(nèi)部類隱藏在外部類之內(nèi),不允許同一個包中的其他類訪問該類。
內(nèi)部類成員可以直接訪問外部類的私有數(shù)據(jù),因為內(nèi)部類被當(dāng)成其外部類成員,同一個類的成員之間可以互相訪問。但外部類不能訪問內(nèi)部類的實現(xiàn)細節(jié),例如內(nèi)部類的成員變量。
匿名內(nèi)部類適合用于創(chuàng)建那些僅需要一次使用的類。
內(nèi)部類與外部類的區(qū)別:
內(nèi)部類比外部類可以多使用三個修飾符:private、protected、static——外部類不可以使用這三個修飾符。
非靜態(tài)內(nèi)部類不能擁有靜態(tài)成員。
非靜態(tài)內(nèi)部類定義內(nèi)部類非常簡單,只要把一個類放在另一個類的內(nèi)部定義即可。此次的“類內(nèi)部”包括類中的任何位置,甚至在方法中也可以定義內(nèi)部類(方法里定義的內(nèi)部類被稱為局部內(nèi)部類)。內(nèi)部類定義語法格式如下:
public class OuterClass { //此處可以定義內(nèi)部類 }
大部分時候,內(nèi)部類都被作為成員內(nèi)部類定義,而不是作為局部內(nèi)部類。成員內(nèi)部類是一種與成員變量、方法、構(gòu)造器和初始化塊相似的類成員:局部內(nèi)部類和匿名內(nèi)部類則不是類成員。
成員內(nèi)部類分為兩種:靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類,使用static修飾的成員內(nèi)部類
外部類的上一級程序單元是包,所以它只有2個作用域:同一包內(nèi)和任何位置。因此只需2種訪問權(quán)限:包訪問和公開訪問權(quán)限,正好對應(yīng)省略訪問控制符和public訪問控制符。省略訪問控制符是包訪問權(quán)限,即同一包中的其他類可以訪問省略訪問控制符的成員。因此如果一個外部類不使用任何訪問控制符,則只能被同一個包中其他類訪問。而內(nèi)部類的上一級程序單元是外部類,他就具有4個作用域:同一個類、同一個包、父子類和任何位置,因此可以使用4種訪問控制權(quán)限。
成員內(nèi)部類(包括靜態(tài)內(nèi)部類、非靜態(tài)內(nèi)部類)的class文件總是這種形式:OuterClass$InnerClass.class
在非靜態(tài)內(nèi)部類里可以直接訪問外部類的private成員。因為在非靜態(tài)內(nèi)部類對象里,保存了一個它所寄生的外部類對象的引用(當(dāng)調(diào)用非靜態(tài)內(nèi)部類的實例方法時,必須有一個非靜態(tài)內(nèi)部類實例,非靜態(tài)內(nèi)部類實例必須寄生在外部類實例里)
當(dāng)在非靜態(tài)內(nèi)部類的方法訪問某個變量時,系統(tǒng)優(yōu)秀在該方法內(nèi)查找是否存在該名字的局部變量,如果存在就使用該變量;如果不存在,則到該方法所在的內(nèi)部類中查找是否存在該名字的成員變量,如果存在則使用該成員變量;如果不存在,則在該內(nèi)部類所在的外部類中查找是否存在該名字的成員變量,如果存在則使用該成員變量;如果依然不存在,系統(tǒng)將出現(xiàn)編譯錯誤;提示找不到該變量。
因此,如果外部類成員變量、內(nèi)部類成員變量與內(nèi)部類里方法的局部變量同名,則可通過使用this、外部類類名.this作為限定來區(qū)分。
public class DiscernVariable { private String prop = "外部類的實例變量"; private class InClass { private String prop = "內(nèi)部類的實例變量"; public void info() { String prop = "局部變量"; // 通過外部類類名.this.varName 訪問外部類實例變量 System.out.println("外部類的實例變量值:"+DiscernVariable.this.prop); // 通過this.varName 訪問內(nèi)部類實例變量 System.out.println("內(nèi)部類的實例變量值:"+this.prop); // 直接訪問局部變量 System.out.println("局部變量值:"+prop); } } public void test() { InClass inClass = new InClass(); inClass.info(); } public static void main(String[] args) { new DiscernVariable().test(); } }
非靜態(tài)內(nèi)部類的成員可以訪問外部類的private成員,但反過來就不成立了。非靜態(tài)內(nèi)部類的成員只在非靜態(tài)內(nèi)部類范圍內(nèi)是可知的,并不能被外部類直接使用。如果外部類需要訪問非靜態(tài)內(nèi)部類的成員,則必須顯式創(chuàng)建非靜態(tài)內(nèi)部類對象來調(diào)用訪問其實例成員。
public class Outer { private int outProp = 9; class Inter { private int inProp = 5; public void accessOuterProp() { //非靜態(tài)內(nèi)部類可以直接訪問外部類的private成員變量 System.out.println("外部類的outProp值:"+outProp); } } public void accessInterProp() { //外部類不能直接訪問非靜態(tài)內(nèi)部類的實例變量 //下面代碼出現(xiàn)編譯錯誤 //System.out.println("內(nèi)部類的inProp值:"+inProp); //如需訪問內(nèi)部類的實例變量,必須顯式創(chuàng)建內(nèi)部類對象 System.out.println("內(nèi)部類的inProp值:"+new Inter().inProp); } public static void main(String[] args) { //執(zhí)行下面代碼,只創(chuàng)建了外部類對象,還未創(chuàng)建內(nèi)部類對象 Outer outer = new Outer(); outer.accessInterProp(); } }
非靜態(tài)內(nèi)部類對象必須寄生在外部類對象里,而外部類對象則不必一定有非靜態(tài)內(nèi)部類對象寄生其中。簡單地講,如果存在一個非靜態(tài)內(nèi)部類對象,則一定存在一個被他寄生的外部類對象。但外部類對象存在時,外部類對象里不一定寄生了非靜態(tài)內(nèi)部類對象。因此外部類對象訪問非靜態(tài)內(nèi)部類成員時,可能非靜態(tài)普通內(nèi)部類對象根本不存在!而非靜態(tài)內(nèi)部類對象訪問外部類成員時,外部類對象一定存在。
根據(jù)靜態(tài)成員不能訪問非靜態(tài)成員的規(guī)則,外部類的靜態(tài)成員、靜態(tài)代碼塊不能訪問非靜態(tài)內(nèi)部類,包括不能使用非靜態(tài)內(nèi)部類定義變量、創(chuàng)建實例等。總之,不允許在外部類的靜態(tài)成員中直接使用非靜態(tài)內(nèi)部類。
Java不允許在非靜態(tài)內(nèi)部類定義靜態(tài)成員。非靜態(tài)內(nèi)部類里不能有靜態(tài)方法、靜態(tài)成員變量、靜態(tài)初始化塊。
如果使用static來修飾一個內(nèi)部類,則這個內(nèi)部類就屬于外部類本身,而不屬于外部類的某個對象。因此使用static修飾的內(nèi)部類被稱為類內(nèi)部類,有的地方也稱為靜態(tài)內(nèi)部類。
static關(guān)鍵字的作用是把類的成員變成類相關(guān),而不是實例相關(guān),即static修飾的成員屬于整個類,而不屬于單個對象。外部類的上一級程序單元是包,所以不可使用static修飾;而內(nèi)部類的上一級程序單元是外部類,使用static修飾可以將內(nèi)部類變成外部類相關(guān),而不是外部類實例相關(guān)。因此static關(guān)鍵字不可修飾外部類,但可修飾內(nèi)部類。
靜態(tài)內(nèi)部類是外部類的類相關(guān)的,而不是外部類的對象相關(guān)的。也就是說,靜態(tài)內(nèi)部類對象不是寄生在外部類的實例中,而是寄生在外部類的類本身中。當(dāng)靜態(tài)內(nèi)部類對象存在時,并不存在一個被它寄生的外部類對象,靜態(tài)內(nèi)部類對象只持有外部類的類引用,沒有持有外部類對象的引用。如果允許靜態(tài)內(nèi)部類的實例方法訪問外部類的實例成員,但找不到被寄生的外部類對象,這將引起錯誤。
靜態(tài)內(nèi)部類是外部類的一個靜態(tài)成員,因此外部類的所有方法、所有初始化塊中可以使用靜態(tài)內(nèi)部類來定義變量、創(chuàng)建對象等。
外部類依然不能直接訪問靜態(tài)內(nèi)部類的成員,但可以使用靜態(tài)內(nèi)部類的類名作為調(diào)用者來訪問靜態(tài)內(nèi)部類的類成員,也可以使用靜態(tài)內(nèi)部類對象作為調(diào)用者來訪問靜態(tài)內(nèi)部類的實例成員。
1.在外部類使用內(nèi)部類
可以直接通過內(nèi)部類類名來定義變量,通過new調(diào)用內(nèi)部類構(gòu)造器來創(chuàng)建實例。 區(qū)別:不要在外部類的靜態(tài)成員(包括靜態(tài)方法和靜態(tài)初始化塊)中使用非靜態(tài)內(nèi)部類,因為靜態(tài)成員不能訪問非靜態(tài)成員。
2.在外部類以外使用內(nèi)部類
希望在外部類以外的地方訪問內(nèi)部類(靜態(tài)、非靜態(tài)),則內(nèi)部類不能使用private訪問控制權(quán)限,private修飾的內(nèi)部類只能在外部類內(nèi)部使用。 ▲ 省略訪問控制符的內(nèi)部類,只能被與外部類處于同一個包中的其他類訪問。 ▲ 使用protected修飾的內(nèi)部類,可被與外部類處于同一個包中的其他類和外部類的子類所訪問。 ▲ 使用public修飾的內(nèi)部類,可以在任何地方被訪問。 在外部類以外的地方定義內(nèi)部類(包括靜態(tài)和非靜態(tài))變量的語法格式如下: OuterClass.InnerClass varName 在外部類以外的地方創(chuàng)建非靜態(tài)內(nèi)部類實例的語法: OuterInstance.new InnerConstructor() class Out { //定義一個內(nèi)部類,不使用訪問控制符 //即只與同一個包中的其他類可訪問該內(nèi)部類 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的形式定義內(nèi)部類變量 Out.In in; 創(chuàng)建外部類實例,非靜態(tài)內(nèi)部類實例將寄生在該實例中 Out out = new Out(); 通過外部類實例和new來調(diào)用內(nèi)部類構(gòu)造器創(chuàng)建非靜態(tài)內(nèi)部類實例 in = out.new In("瞅啥瞅"); */ } }
定義一個子類繼承Out類的非靜態(tài)內(nèi)部類In類
public class SubClass2 extends Out.In { //顯式定義SubClass2的構(gòu)造器 public SubClass2(Out out) { //通過傳入的Out對象顯式調(diào)用In的構(gòu)造器 out.super("瞅你咋地"); } } 非靜態(tài)內(nèi)部類In類的構(gòu)造器必須使用外部類對象來調(diào)用,代碼中super代表調(diào)用In類的構(gòu)造器,而out則代表外部類對象(上面的Out、In兩個類直接來自前一個CreateInnerInsatence.java)。可以看出如果需要創(chuàng)建SubClass2對象時,必須先創(chuàng)建一個Out對象。因為SubClass2是非靜態(tài)內(nèi)部類In類的子類,非靜態(tài)內(nèi)部類In對象里必須與一個對Out對象的引用,其子類SubClass2對象里也應(yīng)該持有對Out對象的引用。當(dāng)創(chuàng)建SubClass2對象時傳給該構(gòu)造器的Out對象,就是SubClass2對象里Out對象引用所指向的對象。 非靜態(tài)內(nèi)部類In對象和SubClass2對象都必須持有指向Outer對象的引用,區(qū)別是創(chuàng)建兩種對象時傳入Out對象的方式不同:當(dāng)創(chuàng)建非靜態(tài)內(nèi)部類In類的對象時,必須通過Outer對象來調(diào)用new關(guān)鍵字;當(dāng)創(chuàng)建SubClass2類的對象時,必須使用Outer對象作為調(diào)用者來調(diào)用In類的構(gòu)造器。
3.在外部類以外使用靜態(tài)內(nèi)部類
在外部類以外的地方創(chuàng)建靜態(tài)內(nèi)部類實例的語法: new OutClass.InnerConstructor() class StaticOut { //定義一個靜態(tài)內(nèi)部類,不使用訪問控制符 //即同一個包中的其他類可訪問該內(nèi)部類 static class StaticIn { public StaticIn() { System.out.println("靜態(tài)內(nèi)部類的構(gòu)造器"); } } } public class CreateStaticInnerInstance { public static void main(String[] args) { StaticOut.StaticIn in = new StaticOut.StaticIn(); /* 上面代碼可改為如下兩行代碼 使用OuterClass.InnerClass的形式定義內(nèi)部類變量 StaticOut.StaticIn in; 通過new來調(diào)用內(nèi)部構(gòu)造器創(chuàng)建靜態(tài)內(nèi)部類實例 in = new StaticOut.StaticIn(); */ } } 不管是靜態(tài)內(nèi)部類還是非靜態(tài)內(nèi)部類,它們聲明變量的語法完全一樣。區(qū)別只是在創(chuàng)建內(nèi)部類對象時,靜態(tài)內(nèi)部類只需使用外部類即可調(diào)用構(gòu)造器,而非靜態(tài)內(nèi)部類必須使用外部類對象來調(diào)用構(gòu)造器。
使用靜態(tài)內(nèi)部類比使用非靜態(tài)內(nèi)部類要簡單很多,只要把外部類當(dāng)成靜態(tài)內(nèi)部類的包空間即可。因此當(dāng)程序需要使用內(nèi)部類時,應(yīng)該優(yōu)先考慮使用靜態(tài)內(nèi)部類。
局部內(nèi)部類如果把一個內(nèi)部類放在方法里定義,則這個內(nèi)部類就是一個局部內(nèi)部類,局部內(nèi)部類僅在該方法里有效。由于局部內(nèi)部類不能在外部類的方法以外的地方使用,因此局部內(nèi)部類也不能使用訪問控制符和static修飾符修飾。
對于局部成員而言,不管是局部變量還是局部內(nèi)部類,它們的上一級程序單元都是方法,而不是類,使用static修飾它們沒有任何意義。因此,所有的局部成員都不能使用static修飾。
Java8改進的匿名內(nèi)部類匿名內(nèi)部類適合創(chuàng)建只需要一次使用的類。創(chuàng)建匿名內(nèi)部類時會立即創(chuàng)建一個該類的實例,這個類定義立即消失,匿名內(nèi)部類不能重復(fù)使用。
new 實現(xiàn)接口() | 父類構(gòu)造器(實參列表) { //匿名內(nèi)部類的類體部分 }
關(guān)于匿名內(nèi)部類規(guī)則
匿名內(nèi)部類不能是抽象類,因為系統(tǒng)在創(chuàng)建匿名內(nèi)部類時,會立即創(chuàng)建匿名內(nèi)部類的對象。因此不允許將匿名內(nèi)部類定義成抽象類
匿名內(nèi)部類不能定義構(gòu)造器。由于匿名內(nèi)部類沒有類名,所以無法定義構(gòu)造器,但匿名內(nèi)部類可以定義初始化塊,可以通過實例初始化塊來完成構(gòu)造器需要完成的事情。
最常用的創(chuàng)建匿名內(nèi)部類的方式是需要創(chuàng)建某個接口類型的對象
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(); //調(diào)用test()方法時,需要傳入一個Product參數(shù) //此處傳入其匿名實現(xiàn)類的實例 ta.test(new Product() { public double getPrice() { return 567.8; } public String getName() { return "AGP顯卡"; } }); } }
上面程序中的AnonymousTest類定義了一個test()方法,該方法需要一個Product對象作為參數(shù),但Product是接口,無法直接創(chuàng)建對象,因此此次創(chuàng)建一個Product接口實現(xiàn)類的對象傳入該方法
如果這個Product接口實現(xiàn)類只需使用一次,則采用上述方式,定義匿名內(nèi)部類
當(dāng)通過實現(xiàn)接口來創(chuàng)建匿名內(nèi)部類時,匿名內(nèi)部類也不能顯式創(chuàng)建構(gòu)造器,因此匿名內(nèi)部類只有一個隱式的無參數(shù)構(gòu)造器,故new接口名后的括號里不能傳入?yún)?shù)值
如果通過繼承父類來創(chuàng)建匿名內(nèi)部類時,匿名內(nèi)部類將擁有和父類相似的構(gòu)造器,此次的相似指的是擁有相同的形參列表
當(dāng)創(chuàng)建匿名內(nèi)部類時,必須實現(xiàn)接口或抽象父類里的所有抽象方法
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(); //調(diào)用有參數(shù)的構(gòu)造器創(chuàng)建Device匿名內(nèi)部類的對象 ai.test(new Device("電子示波器") { public double getPrice() { return 67.8; } }); //調(diào)用無參數(shù)的構(gòu)造器創(chuàng)建Device匿名內(nèi)部類的對象 Device d = new Device() { //初始化塊 { System.out.println("匿名內(nèi)部類的初始化塊..."); } //實現(xiàn)抽象方法 public double getPrice() { return 56.2; } public String getName() { return "鍵盤"; } }; ai.test(d); } }
當(dāng)創(chuàng)建Device為父類的匿名內(nèi)部類時,既可以傳入?yún)?shù),代表父類帶參數(shù)的構(gòu)造器;也可以不傳入?yún)?shù),代表調(diào)用父類無參數(shù)的構(gòu)造器。
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開始,匿名內(nèi)部類、局部內(nèi)部類允許訪問非final局部變量 System.out.println(age); } }; a.test(); } }
如果局部變量被匿名內(nèi)部類訪問,那么局部變量相當(dāng)于自動使用了final修飾。
默認方法Java 8 新增了接口的默認方法。簡單說,默認方法就是接口可以有實現(xiàn)方法,而且不需要實現(xiàn)類去實現(xiàn)其方法,只需在方法名前面加個default關(guān)鍵字即可實現(xiàn)默認方法。
默認方法語法格式如下:
public interface PPAP { default void print() { System.out.println("I have an apple, I have a pen~"); } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/66284.html
摘要:自從轉(zhuǎn)到前段,差不多有一年時間沒有動了,今天老弟問我關(guān)于的面向?qū)ο螅约翰桓抑苯踊卮鹆耍苍S自己真的應(yīng)該去做相關(guān)的考究了。要有父類引用指向子類對象。外部類要訪問內(nèi)部類的成員,必須要建立內(nèi)部類的對象。 自從轉(zhuǎn)到前段,差不多有一年時間沒有動Java了,今天老弟問我關(guān)于Java的面向?qū)ο螅约翰桓抑苯踊卮鹆耍苍S自己真的應(yīng)該去做相關(guān)的考究了。然后在告訴他吧,記在這里當(dāng)做一個筆記吧。 什么...
摘要:前言是面對對象的語言,因此有必要單獨紀錄下對象的各種定義和理解。面對對象基本概述概述是基于面向過程的變成思想,是對面向過程的一種封裝。面對對象開發(fā)就是不斷的創(chuàng)建對象,使用對象,指揮對象做事情。面對對象設(shè)計其實就是在管理和維護對象之間的關(guān)系。 前言 java是面對對象的語言,因此有必要單獨紀錄下對象的各種定義和理解。 面對對象,主要包括:面向?qū)ο笏枷耄惻c對象及其使用,對象的內(nèi)存圖,成...
摘要:表達式的主要作用就是代替匿名內(nèi)部類的煩瑣語法。從這點來看,表達式的代碼塊與匿名內(nèi)部類的方法體是相同的。與匿名內(nèi)部類相似的是,由于表達式訪問了局部變量,該局部變量相當(dāng)于與一個隱式的修飾,因此不允許對局部變量重新賦值。 函數(shù)式接口 函數(shù)式接口(Functional Interface)就是一個只有一個抽象方法(可以包含多個默認方法或多個static方法)的普通接口,可以被隱式轉(zhuǎn)換為lamb...
摘要:構(gòu)造器沒有返回值一個對象變量并沒有實際包含一個對象,而僅僅引用一個對象,如有兩個部分。子類重寫方法的返回值范圍必須小于等于父類方法的返回值。枚舉類型中可以添加一些構(gòu)造器方法和域。 第三章 Java是一種強類型語言。 https://blog.csdn.net/qq_3619... 在Java中,整型的范圍與機器無關(guān)。 int 4字節(jié) short 2字節(jié) long ...
摘要:使用創(chuàng)建的字符串對象是運行時創(chuàng)建出來的,它被保存在運行時內(nèi)存區(qū),即堆內(nèi)存,不會放入常量池中。類成員創(chuàng)建的對象實例中根本不會擁有對應(yīng)類的類變量。抽象類的構(gòu)造器不能用于創(chuàng)建實例,主要是用于被其子類調(diào)用。 Java提供了final關(guān)鍵字來修飾變量,方法和類,系統(tǒng)不允許為final變量重新賦值,子類不允許覆蓋父類的final方法,final類不能派生子類。 Abstract 和 inte...
閱讀 429·2024-11-06 13:38
閱讀 809·2024-09-10 13:19
閱讀 937·2024-08-22 19:45
閱讀 1386·2021-11-19 09:40
閱讀 2626·2021-11-18 13:14
閱讀 4291·2021-10-09 10:02
閱讀 2318·2021-08-21 14:12
閱讀 1286·2019-08-30 15:54