JAVA編程思想——分析閱讀

来源:https://www.cnblogs.com/castamere/archive/2020/01/21/12221163.html
-Advertisement-
Play Games

需要源碼、JDK1.6 、編碼風格參考阿裡java規約 7/12開始 有點意識到自己喜歡理論大而泛的模糊知識的學習,而不喜歡實踐和細節的打磨,是因為粗心浮躁導致的麽? cron表達式使用 設計能力、領域建模能力 其他: 海明威的硬幣:老人與海 工具準備: java編程思想電子版 別人整理的思維導圖 ...


需要源碼、JDK1.6 、編碼風格參考阿裡java規約

7/12開始

有點意識到自己喜歡理論大而泛的模糊知識的學習,而不喜歡實踐和細節的打磨,是因為粗心浮躁導致的麽?

cron表達式使用

設計能力、領域建模能力

其他:

海明威的硬幣:老人與海


工具準備:

java編程思想電子版

別人整理的思維導圖


前言

適用範圍:Java SE5/6 版本。

Java的設計目標是:為程式員減少複雜性,縮短代碼的開發時間,跨平臺復用。

學習方法:一模式或一節點就進入一練習,思維與實踐並行,現學現賣。

每當我認為我已經理解了併發編程時,又會有新的奇山峻嶺等待這我去征服。——作者都花了好幾個月寫併發這一篇章併發出這樣的感慨,我們又有什麼理由妄自菲薄呢。

緒論

學習語言時:需要在頭腦中創建一個模型,以加強對這種語言的深入理解;如果遇到了疑問,就將它反饋到頭腦的模型中並推斷出答案。

疑問:模型是什麼意思?java略圖?巨集語言是什麼?

A:模型指的是思維導圖。有個一整個的概念。

按照人類學習語言的方式。?

一、對象導論

1 知識

人們所能夠解決的問題的複雜性直接取決於抽象的類型和質量。類型即指所抽象的是什麼,也可以說用的是什麼類型的語言。Java,C ,彙編,Python等。其中想C或者彙編要基於電腦的結構來求解問題,面向過程;而Java 等面向對象的語言是基於問題來求解,面向對象。

面向對象的5個基本特性:

  1. 萬物皆對象。抽象
  2. 程式是對象的集合,它們通過發送消息來告知彼此所要做的。對象之間的方法調用
  3. 每個對象都有自己的由其他對象所構成的存儲。封裝
  4. 每個對象都擁有其類型。class,繼承
  5. 某個特定類型的所有對象都可以接受同樣的消息。多態

類實際上就是一個數據類型,程式員根據需求,通過添加新的數據類型(class)來擴展編程語言,而不需要像面向過程語言那樣只能使用現有的用來表示機器中的存儲單元的數據類型。

類創造出來的對象就是服務提供者(好處:有助於提高對象的內聚性,或者說通過設計模式的六大原則來設計對象提供服務),通過將問題的解決分解成對象集合的方式去調用(現有類庫)和設計創建對象。

訪問控制的原因:

  1. 讓調用者無法觸及他們不應該觸及的部分,且通過控制符可以讓調用者很容易地區分哪些東西對他們很重要(public),哪些是可以忽略的(private)。
  2. 允許類或庫設計者可以改變類內部的工作方式而不用擔心會影響到調用方。

子類通過添加新方法(is like a 關係 )和覆蓋父類方法(is a 關係)來改變與父類的差異。(但要滿足里式替換原則)

單(跟)繼承的好處:

  1. 確保所有對象都屬於同一個基本類型。
  2. 保證所有對象都具備某些功能。
  3. 極大簡化參數的傳遞。(如參數可以直接放Object對象類型?)
  4. 使垃圾回收器的實現變得容易得多。

使用不同的容器選擇點是:

  • 不同容器提供了不同類型的介面和外部行為。
  • 不同的容器對於某些操作具有不同的效率。(例如:ArrayList查找快,增刪慢(相對於LinkedList);LinkedList查找慢,增刪快)

最後,最好要對其他語言(如python)有個清晰的認識,java語言是否符合項目的設計及未來的發展需要。

2 疑問

什麼是CGI?

3 思想總結

面向對象語言JAVA的特性是:抽象、封裝、繼承、多態。使用面向對象語言可以讓我們更好的面向問題來設計解決程式,也更容易讀懂和維護該程式的代碼,相對於面向過程語言來說。

二、一切皆對象

1 知識

Java通過操作引用來操縱對象,就像我們用遙控器來操縱電視機一樣。

5個不同的地方存儲數據

  1. 寄存器。最快,因為它在處理器內部。
  2. 堆棧。速度僅次於寄存器,位於RAM,必須知道確切的生命周期來移動堆棧指針分配和釋放記憶體。基本數據類型和對象引用存儲在堆棧中。
  3. 堆。通用記憶體池也位於RAM。堆不同於堆棧的好處是不需要知道存儲數據或對象的生命周期,因此分配和釋放記憶體需要更多時間。用於存放所有的Java對象。
  4. 常量存儲。存放在程式代碼中或者只讀的ROM(只讀存儲器),因為不變所有是安全的。
  5. 非RAM存儲。一般是流對象和持久化對象。在程式不運行時也可以存在。

基本數據類型如下:

基本類型的值存儲在堆棧中,每種類型所占存儲空間大小不變,因此更具可移植性。

BigInteger(支持任意精度的整數) 和 BigDecimal (支持任意精度的定點數,貨幣計算)屬於高精度數據類型,以方法調用代替運算符實現運算,因此運行速度較慢,是以速度換取精度的數據類型。

PS: 定點和浮點的區別

類的成員變數與局部變數是否需要初始化的區別:

類的成員變數如果是基本數據類型,即使沒有初始化值,java也會確保它獲得一個對應類型的預設值,防止程式錯誤。而java不會對局部變數進行預設初始化,如果局部變數沒有初始化賦值,則會編譯報錯說變數沒有初始化。

類的成員變數預設初始化值如下:

構建程式時通過反寫功能變數名稱作為包名來避免名字衝突。(com.xixi)

2 疑問

什麼是處理器?什麼是寄存器?處理器內部結構是什麼樣的?為什麼寄存器存儲最快?

堆棧通過上下移動堆棧指針來分配和釋放記憶體,那麼多線程的時候是如何分配記憶體的呢?

javadoc如何提取註釋,怎麼用?工作中沒看到有用到,現在還有用麽?

3 思想總結

萬物皆對象!

三、操作符

1 知識

別名現象是因為java操作中操作的是對對象的引用,所以會出現別名現象。註意方法參數傳遞是基本數據類型是值傳遞,對象類型是”引用傳遞“(如果替換整個對象沒事,但如果修改對象內的屬性值的話,原有對象會發生變化)。

整數乘除會直接去掉結果的小數位,不是四捨五入。

a++ :先生成值,再執行++運算。

++a : 先執行++運算,再生成值。

類對象比較時 : == 或者 != 比較的是對象的引用(即地址),而equals()比較的是內容。

普通的對象用equals()比較的還是引用,和 == 一樣,要想比較內容就得重寫equals()方法。

基本數據類型比較直接用 == 或者 !=

java指數的寫法

//編譯器通常會將指數作為雙精度(double)處理
double e = 1E-43d;// = 10 -43次方
double f = 1.6E43d;// = 1.6 * 10 43次

2 疑問

Random(47)的使用?隨機數怎麼有效地創造及生成隨機數的註意事項。

A:Random(47)使用見下:

    @Test
    public void randomTest(){
        //如果不傳參數生成隨機數對象Random,則java會把當前時間作為隨機數生成器的種子seed,則每一次執行
        //都會產生不同的輸出。而如果傳入固定值的seed,則每一次輸出都是可預見性的相同的值,每一次nextInt      //的返回值按次序都是相同的,固定seed方便測試驗證。
        Random r = new Random(47);
        //每次調用nextInt方法傳入的參數如100表示所產生的隨機數的上限,下限為0,如果不想得到0的結果可以          +1
        System.out.println( r.nextInt(100)+1);//隨機數小於上限且不等於
        System.out.println( r.nextInt(100)+1);
        System.out.println( r.nextInt(100)+1);
        //Random r1 = new Random();
    }

如何重寫equals()方法比較內容?

單精度和雙精度的區別?

8進位和16進位的寫法?

@Test
public void otherTest(){
    int c = 0x2f;//16進位 零x
    System.out.println(Integer.toBinaryString(c));
    int d = 0177;
    System.out.println(Integer.toBinaryString(d));
    //int e = 01987; //前面如果加了零,表示這個數是8進位的數,每位最大值不能超過7
}

java 按位操作符 & | ^ ~ 和移位操作符<< >> >>>的使用?在演算法中用的是否普遍?

3 思想總結

一些基本的操作符使用。

四、控制執行流程

1 知識

無窮迴圈的形式

for(;;)
//或者
while(true){
    //裡面沒有結束條件break;
}

禁止使用標簽跳躍,防止濫用,程式混亂。

switch 新特性:與 enum 枚舉或 String 一起使用。

吸血鬼數字解法

參考答案

@Test
public void xixueguiTest(){
    int num =0;
    for (int i=10;i<100;i++){
        for (int j=i+1;j<100;j++){
            int target=i*j;
            if (target<1000||target>9999){
                continue;
            }
            num++;
            int[] targetNum = { target / 1000, target / 100 % 10, target / 10 % 100 % 10, target%10 };
            int[] strNum = { i % 10, i / 10, j % 10, j / 10 };
            Arrays.sort(targetNum);
            Arrays.sort(strNum);
            if (Arrays.equals(targetNum,strNum)){
                System.out.println(target + " = " + i + " * " + j);
            }
        }
    }
    System.out.println(num);
}

2 疑問

3 思想總結

一些普通的流程式控制制,如 while , for , break , continue , switch

五、初始化與清理

1 知識

初始化

創建對象時通過自動調用構造器來確保初始化。

類和方法的命名:名字起的好可以使系統易於理解和修改。

方法重載或構造器重載相當於人類語言的冗餘性——可以從具體的語句中推斷出含義。重載的規則由參數類型,個數,順序的不同來確定,一般不推薦順序不同。註意,返回值的不同不能用於重載,因為有時候調用有返回值的方法並不必須要返回值,這樣編譯器無法區分是調用哪個。

基本數據類型的重載思想是能從一個較小類型如int自動提升至一個較大類型如double,如果要把較大類型如double轉為較小類型如long則必須強轉。這部分我覺得除非不得已,絕對不進行這種自動提升重載方法,不便於理解。

this關鍵字的使用場合:

  • 只有當需要明確指出對當前對象的引用時,才需要使用this關鍵字。如需要返回對當前對象的引用時,
  • return this.
  • 將當前對象傳遞給其他方法時(作為參數)。
  • 一個類中有多個構造器,構造器之間調用另一構造器使用this。this(a,b),this(a)。註意構造器調用必須置於第一行,因此構造器調用其他構造器一次只能調用一個,要調用多個就要構造器間嵌套調用。註意:只能構造器調用構造器,構造器禁止被其他方法調用。
  • 通過構造器給類成員變數賦值,如 this.a = a;

靜態初始化只有在必要時刻(類第一次載入.class文件時:一般是類對象的第一次創建或第一次直接用類訪問靜態數據時)才會進行。之後無論創建多少對象,靜態數據都只占用一份存儲區域。

PS:構造器實際上也是static靜態方法。

對象的創建過程:

  1. java解釋器查找類路徑,定位如Monkey.class 文件。
  2. 載入Monkey.class,執行所以靜態初始化動作。
  3. 在堆上為Monkey對象分配足夠的存儲空間。
  4. 存儲空間清零,Monkey對象的所以類成員變數置為預設值,如0,false,null。
  5. 執行所有類成員變數的初始化動作。
  6. 執行構造器。

用代碼塊來初始化類成員變數的實例與靜態成員變數的初始化差不多,代碼塊來初始化類成員變數的實例也在構造器之前執行,區別在於靜態成員變數的初始化只有一次,而代碼塊來初始化非靜態類成員變數在每次創建對象時都會執行。

數組初始化

編譯器不允許指定數組的大小,數組的創建是在運行時刻進行的。數組的長度一旦確定則不可變。創建數組為非基本數據類型時,該數組為引用數組,數組元素存儲的是引用對象的引用(地址)。

註意:通過花括弧{}初始化列表時最後一個逗號是可有可無的。

@Test
public void arrayTest(){
    //數組內元素的值會根據數據類型自動初始化為空值,如int為 0.
   int[] a = new int[5];
   Integer[] b = {1,2,new Integer(3),};
    //註意構建格式 new String[]{};
   String[] maomao = new String[]{"mao","mao","hong"};
}

focus: 數組使用參考

清理

垃圾回收註意事項:

  • 對象可能不被垃圾回收。
  • 垃圾回收並不等於”析構“(c++)。
  • 垃圾回收置於記憶體有關。

fanalize()方法用於釋放為本地方法(一種在java中調用非java代碼的方式)分配的記憶體。

GC前,會調用finalize()方法,所以可以重寫finalize()方法來驗證終結條件。

System.gc():強制GC(不一定觸發)

自適應垃圾回收技術思想依據:對於任何“活”的對象,一定能最終追溯到其存活在堆棧或靜態存儲區之中的引用。

類的初始化順序:

成員變數定義的先後順序決定了變數初始化的順序。而類成員變數初始化是最早的,然後是構造器,再然後是方法。

可變參數列表

應用場合:參數個數和類型未知時使用。

可變參數列表本質上還是數組,當我們指定參數時,編譯器實際上會為什麼去填充數組,所以我們可以用foreach迭代遍歷。可變參數列表也可以接受其對應類型的數組當做可變參數列表。

可變參數列表不依賴於自動包裝機制,實際使用的是基本數據類型,且可變參數列表可以使用自動包裝機制裝箱拆箱。

推薦:在使用可變參數列表時,如果有重載方法,則應該只使用一個版本類型(如下)的重載方法,而不是參數類型個數不同等重載方法。

private void printArray(int a,Object... obj){
    for (Object o : obj) {
        System.out.print(o);
    }
    System.out.println();
}
private void printArray(float a,Object... obj){
    for (Object o : obj) {
        System.out.print(o);
    }
    System.out.println();
}

枚舉enum

常量命名:大寫字母及下劃線。

enum與switch是絕佳的組合,我們可以將enum用作另外一種創建數據類型的方式。

2 疑問

在創建一個類時,在定義時就被初始化了的String域域通過構造器初始化的String域兩種方式的差異?

A: 差別在於strA一開始被初始化為"strA",而strB先被初始化為null,然後再被初始化為"strB" .

class Test{  
    private String strA = "strA";  
      
    private String strB;  
      
    Test(){  
        strB = "strB";  
    }  
}  

代碼塊來初始化非靜態類成員變數的實例如何支撐匿名內部類的初始化?

java枚舉的使用?有什麼特別實用的使用技巧麽?

3 思想總結

初步理解java的初始化和自動清理過程,註意數組的初始化使用,可變參數列表適用於參數個數和類型未知的場合,枚舉enum用於自定義數據類型,並最好能與switch配合使用最佳。

六、訪問許可權控制

1 知識

訪問許可權控制的前景交代是:迭代,重構,需求變更等為了增加可讀性、理解性和可維護性。

訪問許可權控制修飾詞:把變動的事物與保持不變的事物區分開,是對具體實現的隱藏,即封裝。(package和import)

一個類只有一個public class類,其他類都不算public 的,它們主要是為主public類提供支持的。

用導入限定的命名空間位置描述來提供一個管理名字空間的機制。

java包命名規則是全部使用小寫字母連接。

有衝突名字情況下,則必須返回到指定全面的方式。

將構造器私有(private),則不能通過構造器來創建對象,而必須調用專門的如getInstance()方法來創建對象。且因為構造器是私有的,他阻止對此類的繼承。

protected 修飾符處理的是繼承的概念(包含包訪問許可權)。

類只能定義包訪問許可權和public許可權,如果是預設的包訪問許可權,則該類不能被非同一個包下的類創建訪問。

java比c的名字空間控制好在:

  • package 定義每個類的包路徑
  • 包命名模式(功能變數名稱反寫)
  • 關鍵字import

2 疑問

如何把類庫空間置於包中?或者說設計項目時如何分配類的位置和創建包分類。

java如何實現條件編譯?如何定義程式為調試版和發佈版?現在這樣定義還有價值麽?

private在多線程環境下的重要性?

3 思想總結

訪問許可權控制的原因有二:

  • 隔離不屬於客戶端所需介面的部分實現封裝,也使客戶端能更清楚地理解哪些對他們是重要的和哪些是可以忽略的。
  • 讓方法或介面提供者可以安全地更改和迭代類內部的工作方式和結構。

許可權是約定俗成的東西,許可權的修飾符可以更好地方便於程式的理解迭代和維護。

七、復用類

1 知識

復用類的方式有二:組合與繼承。

初始化類成員變數的方式有四:

  1. 在定義對象的地方。這樣則總能在被構造器調用之前被初始化。
  2. 在類構造器中。
  3. 在使用這些對象之前,稱為惰性初始化(一般是使用前判空,為空則初始化該對象)。
  4. 使用實例初始化。(代碼塊等)

繼承自動得到父類中所有的域和方法,所有一般在設計繼承時會將所有的成員變數(數據成員)指定為private,所有的方法指定為public。

子類調用父類方法用super關鍵字(如:super.action() )。

當創建一個子類對象時,該對象包含了一個父類的子對象。因此當子類創建對象時,會先去創建父類子對象,及父類對象,然後再初始化子類的成員變數,最後調用子類的構造器。(p130練習5)PS:創建父類時會調用父類構造器【註意:每次創建子類時都會去調用父類構造器創建一個父類的子對象,即每次創建子對象都會調用父類構造器創建對象,不是想static常量那樣只創建一次的】,如果是預設構造器則可以不寫,預設調用父類的無參構造器,如果是只有帶參數的構造器,則必須在子類構造器第一行顯式調用父類構造器【super(3) 】。

組合與繼承

組合和繼承都允許在新的類中放置子對象(成員變數),組合是顯示地這麼做,而繼承是隱式的這麼做。一般組合中的成員變數會修飾為private,為了安全而隱藏具體實現。

組合與繼承的選擇在於兩種之間的關係是"is -a"還是 "has-a"關係。選擇繼承時要判斷自己需不需要從新類向父類進行向上轉型,需要才可以用繼承。

向上轉型和向下轉型用於有繼承結構的對象,方便一視同仁地操作對象。

final關鍵字

final 修飾一般表示這是無法改變的。

不可改變的理由:設計或效率。

final數據

一個既是static又是final的域只占據一段不能改變的存儲空間。

對於基本類型,final使數值恆定不變,而對於對象引用(數組也是),final使引用恆定不變,但對象自身卻可以被修改。因此,使引用成為final沒有使基本類型成為final的用處大。

空白final:指被聲明為final但又未給定初值的域。可以通過構造器初始化final域來對一個類的final域初始化值,使之可以根據對象的不同而有所不同,且又保持恆定不變的特性。

final參數:無法在方法中更改參數引用鎖指向的對象或基本參數的值(PS:參數引用的對象的值還是可以更改,太雞肋,沒用)。可以讀參數,無法修改參數。主要用來向匿名內部類傳遞數據。

final方法

使用的原因:一是鎖定方法,防止被繼承覆蓋重寫修改該方法的含義。二是效率。(在虛擬機是HotSpot已經沒必要了)

類中所有private方法都隱式的指定為是final的。但因為方法是private的,所有隻在該類中私有使用,不影響其他繼承類也繼續叫這個方法名。

final類

被final修飾的類不可被繼承。final類的域是可變的,和其他的一樣,除非被final修飾;而final類的所有方法都無法覆蓋重寫,因為final禁止繼承,所以的方法都隱式指定為final的。final類的方法就無需加final修飾了。

設計類時,除非明確不想被覆蓋,否則不應給類或方法加final修飾,既沒有效率提高可能,又可能會妨礙其他人通過繼承來複用這個類。

繼承與初始化(p146案例)

  1. 一開始會先載入父類的static靜態變數初始化,
  2. 然後再載入子類的static靜態變數初始化,
  3. 再然後是初始化父類的成員變數為預設值,
  4. 然後是初始化子類的成員變數為預設值,
  5. 然後是調用父類的構造器,
  6. 再然後調用子類的構造器。

    //典型常量定義方式:
    /**
    public 公開的 ; static 靜態的,只有一份 ; final 常量,不可變的
    /
    public static final int VALUE_TWO = 3;

    //final修飾的數據只能在運行時才能確認他的值,編譯時是不能確認他的值的,從可以把隨機值賦值給final域得知
    static final int INT_A = rand.nextInt(20);

其他:

toString()方法會在編譯器列印一個String而傳遞的是一個對象時自動調用該對象的toString()方法,因此如果想要使要列印日誌的對象具備這樣的行為時只有編寫一個toString()方法,否則列印的是對象的地址類型組合信息。

每個類都可以創建一個main()方法,方便單元測試,且無需刪除。即使一個程式中含有多個類,也只有命令行鎖調用的哪個類的main()方法會被調用,及我們run的那個類方法。

ArrayList 代替了 Vector;

HashMap 代替了 HashTable

2 疑問

final參數主要用來向匿名內部類傳遞數據,對於引用參數的內部的值是可以改變的,加final有什麼意義麽?

設計程式時應如何權衡考慮呢?

3 思想總結

多用組合,少用繼承。

設計類時要遵循單一職責,繼承是要遵循里式替換原則。

final關鍵字主要用在設計上想得到不可變元素、方法和類上時。

設計系統時應考慮程式開發是增量過程,如圖人類的學習;要有擴展性,像進化的生命體而不是想設計摩天大樓一樣快速見效?

八、多態

1 知識

多態的作用:消除類型之間的耦合關係。

綁定:將一個方法調用同一個方法主體關聯起來被稱作綁定。

前期綁定:在程式執行前進行綁定,如面向過程語言C。

後期綁定:在運行時根據對象的類型進行綁定,如JAVA。

Java除了static、final(private也屬於final)方法之外,都是後期綁定。所以當我們聲明一個方法為final時,意思是關閉動態綁定。

多態的"缺陷"

  • 父類中只有非private 方法才能被覆蓋。否則其實在子類中名字相同的只是個全新的方法,並沒有覆蓋。
  • 成員變數在多態時訪問的是父類的成員變數值(如果成員變數相同的時候,這時候子類包含這兩個名字相同的成員變數值,一個是父類的,通過super.field調用,一個是子類的,this.field),方法訪問的是子類的覆寫方法(如果有覆蓋重寫的話)
  • static靜態方法是類方法,與對象無關,不具有多態性

在實際工作中,我們一般不會這樣,一般會把成員變數都修飾為private(再通過get/set方法提供訪問),且對於父類和子類的成員變數命名也不會相同,避免引起混淆。

//多態
Shape s = new Cycle();
//普通創建對象
Cycle c = new Cycle();

多態和構造器

構造器的特殊任務:檢查對象是否被正確地構造。

複雜對象調用構造器的順序:

  1. 在對象創建之前,將分配給對象的存儲空間初始化為二進位零。
  2. 調用父類構造器,這個步驟會遞歸傳遞到Objcet對象。
  3. 按照聲明順序調用成員變數的初始化方法。
  4. 調用子類構造器的主體。

註意:每一個類的初始化都遵循如果第一次調用的話,會有static成員變數先初始化,然後是類的成員變數的初始化,再然後是構造器的調用。這一調用順序在父類或者成員對象的調用中都適用。當然要註意的是如果類的static成員變數已經不是第一次初始化則不會再調用了。

PS:如果父類在初始化的構造器中調用覆蓋的方法,則根據多態調用的其實是子類覆蓋的方法,只是由於子類還未初始化,其中如果有成員變數的話則值為0.

編寫構造器準則:

用儘可能簡單的方法使對象進入正常狀態;如果可以的話,構造器避免調用其他方法。構造器唯一可以安全調用的是final修飾的方法(包括private)。其他的會多態到子類上去。

協變返回類型允許我們寫方法時返回更具體的對象類型。比如不是Shape而是Cycle.

通過繼承表達行為間的差異,並用成員變數(相同的介面,不同的類型賦予不同的子類)表達狀態上的變化。

註意:

多態時創建的對象調用只能是父類有的方法,因為多態時引用是向上轉型的,子類的擴展方法會“丟失”,如果要使用子類的擴展方法則要向下轉型。如果向下轉型不成功(不是該類型或其父類)則會報ClassCastException(類轉型異常)。

2 疑問

不同的類型修飾構造器有什麼區別,一般private是不想讓對象被創建,用於單例,那public、protected、和預設的使用有什麼講究麽?

static可以修飾構造器麽?

A:構造器就是預設的static了,所以不允許。confirmed.

不同訪問許可權的修飾符修飾的static成員變數、類成員變數、構造器初始化的先後順序是按照聲明順序來的麽?

3 思想總結

多態讓程式針對一體性類型的對象統一處理有了可能,讓程式的開發更加迅速,代碼編寫更加人性化處理,也使得擴展和更加容易。

但是也要註意多態的缺陷,那就是多態針對的是對象的公共行為,對象的靜態方法和對象成員變數及私有行為(private、final)都是不能多態的。

還有,因為多態增加了對象的組織複雜和龐大,所以使用的原則是多用組合,少用繼承。

九、介面

1 知識

介面和內部類(特別是匿名內部類)為我們提供了一種將介面與實現分離的更加結構化的方法。

抽象類:

它是普通的類與介面之間的一種中庸之道。特別是在不能使用純介面的時候。抽象類適用於重構,這樣我們可以將公共方法沿著繼承層次結構向上移動。(PS:只有類名上修飾了abstract則不管有沒有抽象方法,該類都是一個抽象類,不能被創造出對象。當然更可以全部是abstract,這種其實就是介面了。)

介面:

介面可以包含成員變數。其成員變數都是static和final的(使介面成為便捷的用來創建常量組的工具,不過如果是enum枚舉類型常量的話最好還是用enum,直觀好看),即靜態常量;介面內所有的方法和成員變數都是public的,無論是否寫public修飾。

如果介面定義時不加public修飾符(介面或者類裡面的介面定義),則該介面只有包訪問許可權,只能在同一個包內使用。引申出介面可以嵌套在類或其他介面中。

Java通過多介面實現多重繼承,其他具體實現類或者抽象類都只能單繼承。

在介面和抽象類選擇中儘量選擇介面來設計。(作者的建議是前期如果沒有必要可以直接選擇設計類而不是介面,看中需要性)

在繼承和實現的介面中方法名一樣時,一起按照重寫和重載的規則來,相同則只要有一個實現就行(或者重寫),不同的則看方法簽名不同實現重載。(如果只是返回值類型不同則無法重載編譯器會報錯無法實現)

介面配合策略模式和適配器模式使得程式更加地靈活。

2 疑問

P185嵌套介面一節有什麼用?

介面中的類呢?也是常量麽?

3 思想總結

面向介面編程才能解耦,依賴倒置。

十、內部類

1 知識

一個類的定義在另一個類的定義內部,就是內部類。

內部類的使用可以很方便地隱藏實現細節。

註意:註意是要在另一個類的內部,如果是在外面就是一個普通的類,當然該類不能是public的,因為一個類文件只能有一個public類型的類;如果是內部類的話,則可以是public的。

內部類可以直接方法其外部類的方法和成員變數,包括private等所有的方法。(這是因為內部類對象在創建時會秘密捕獲一個指向外部類的引用。)

內部類還是一個完整的類,跟其他類一模一樣,有類的訪問許可權限制等。區別在於一:內部類依賴於外部類,因此。內部類對其外部類是完全透明可見的,包括其private的私有成員變數,外部類也能訪問。

二:要註意內部的定義的作用域,超出作用域則內部類不可用。

在方法和作用域內的內部類

內部類可以定義在任何地方,包括方法的參數,方法內部(不能用private修飾),方法的作用域內(局部內部類)。

匿名類不可能有構造器。(想要用的話可以用父類的帶參構造器,父類不能是介面才行,必須是普通類或者抽象類)

匿名內部類可以有欄位,方法,還能夠對其欄位執行初始化操作。

匿名內部類使用外部定義的對象或值時,編譯器要求其參數引用是final類型的。如果是通過匿名內部類的父類構造器傳遞參數進來的話,則不需要是final類型的,因為這個參數並不會被匿名內部類直接使用。所以加final修飾是為了保證直接使用時該內部類的成員變數不可變?

匿名內部類與正規的繼承區別:

匿名內部類既可以擴展類,也可以實現介面,但不能兩者兼備。如是是實現介面,也只能實現一個介面。

嵌套類

把內部類聲明為static類型的我們稱為嵌套類,嵌套類不需要內部類對象與其外部類對象之間有聯繫。

  1. 創建嵌套類對象,不需要外部類的對象。
  2. 不能從嵌套類的對象中訪問非靜態的外部類對象。(因為嵌套類沒有保存外部類對象的引用,不需要依賴外部類)

嵌套類與內部類的區別

普通內部類的欄位與方法,只能放在類的外部層次上,所以普通的內部類不能有static數據和static域,也不能包含嵌套類。但是嵌套類可以有static數據和static域,也能包含嵌套類。

嵌套類是內部類的一種,其編譯後的class文件還是在外部類文件中。

在介面中寫的內部類因為是介面裡面的,自動是public和static的,所以介面中的內部類是嵌套類。我們可以在介面中放置內部類(嵌套類)代碼。可以用於創建某些公共代碼,使得他們可以被某個介面的所以不同實現所共用。

內部類無論嵌套了多少層的內部類,它都能透明地訪問所以它嵌入的外部類的所有成員(即使是外部類的private成員、方法)。

為什麼需要內部類

內部類實現一個介面與外部類實現這個介面的區別:

外部類實現一個介面不是總能享受到介面帶來的方便,有時需要用到介面的實現。(比如外部類已經有同名的方法實現了,無法重覆覆蓋寫出想要的覆蓋方法)

這時候由內部類實現介面的優勢:每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外部類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。

內部類使得多重繼承的解決方案變得完整:內部類允許繼承多個非介面類型。(類或抽象類,因為每個內部類都可以去繼承不同的類,這樣這個外部類就有了多種繼承的能力)

實現2個介面時我們可以選擇使用單一類(多實現),或者使用內部類來實現。而如果實現的是2個抽象類或者具體的類,而不是介面,就只能使用內部類才能實現多重繼承。

內部類使用前提:如果不需要解決多重繼承的問題,那麼自然還是用別的編程方式。

使用內部類可以獲得的特性:

  1. 內部類可以有多個實例,每個實例都有自己的狀態信息,並且與其外部類對象的信息相互獨立。
  2. 在單個外部類中,可以讓多個內部類以不同的方式實現同一個介面,或繼承同一個類。(實現不同的行為效果,有點策略模式的意思)
  3. 創建內部類對象的時刻並不依賴於外部類對象的創建。(通過static方法調用或者創建的是嵌套類?)
  4. 內部類並沒有令人迷糊的"is -a"關係,他就是一個獨立的實體。

主要用來響應事件的系統被稱為事件驅動系統。

註意這個處理事件結合的寫法,把要迭代處理的事件用一個新的集合包裝,再處理完畢之後對原有的處理事件集合進行移除事件(在此期間可能會原事件集合還可能會進行添加待處理事件,所以這樣處理邏輯是極好的),這樣就不會影響到迭代迴圈的長度。

通過內部類,使變化的事務與不變的事務相互分離(模板方法)。內部類允許:

  • 控制框架的完整實現是有的那個的類創建的,從而使得實現的細節被封裝起來。內部類用來表示解決問題所必需的各種不同的action()。
  • 內部類能夠很容易地訪問外部類的任意成員,所以可以避免這種實現變得笨拙。

內部類覆蓋

如果兩個外圍類都有相同名字的內部類,而這兩個外圍類是繼承關係的話,這時候這兩個內部類是完全獨立的兩個實體,各自在自己的命名空間內,沒有繼承關係。而當這兩個內部類一個明確繼承另外一個的時候,則他們便有繼承關係。和普通類繼承沒什麼不同。

局部內部類

局部內部類指在代碼塊裡面的內部類,典型如方法體的裡面。局部內部類不能有訪問說明符(public許可權訪問符),其他的和內部類一樣。局部內部類與匿名內部類的使用區別:

  • 我們需要不止一個該內部類的對象。
  • 我們需要一個已命名的構造器,或者需要重載構造器。匿名內部類做不到,因為匿名內部類只能用於實例初始化。

如果不是這些需要,還是直接用匿名內部類好了。

其他知識點補充:

訪問說明符:指的是訪問許可權符,如public,private。

回調:

參考

如下圖所示, 回調是一種雙向的調用方式, 其實而言, 回調也有同步和非同步之分, 講解中是同步回調, 第二個例子使用的是非同步回調 。

回調的思想是:

  • 類A的a()方法調用類B的b()方法
  • 類B的b()方法執行完畢主動調用類A的callback()方法

通俗而言: 就是A類中調用B類中的某個方法C, 然後B類中反過來調用A類中的方法D, D這個方法就叫回調方法。

//創建內部類對象
Outer outer = new Outer;
Outer.Inner inner = outer.getInner();

//內部類的基本使用如下
public class Outter {
    public class Inner{
        public void getShow(){
            //兩種調用方法,第一種比較明顯能展示該方法是外部類的方法
            Outter.this.show();
            show();
        }

        public Outter getOuter(){
            //內部類中,this表示該內部類,Outter.this 表示內部類對應的外部類的引用對象
            return Outter.this;
        }
    }
    //靜態內部類則不需要創建外部類對象,直接用類調用方式
     public static class StaticInner{
        public static void staticShow(){
            System.out.println("staticShow");
        }
     }
    public void show(){
        System.out.println("outer");
    }

    public Inner getInner(){
        return new Inner();
    }

    public static void main(String[] args) {
        Outter outter =new Outter();
        //內部類的創建可以如下:使用外部類的引用.new語法創建內部類對象
        Outter.Inner inner = outter.new Inner();
        //也可以通過外部類方法來創建一個
        Inner in = outter.getInner();
        in.getShow();
        inner.getShow();
        inner.getOuter().show();
        Outter.StaticInner.staticShow();
    }
}
//=====================================================
//有參匿名內部類使用方法 前提是得有普通類或者抽象類(帶有參數構造器)實現。
public abstract class Fishing {

    int i =0;

    public Fishing() {
    }

    public Fishing(int i) {
        this.i = i;
    }

    public void fish(){
        System.out.println("fish"+i);
    }
}

public class Human {
        public Fishing getFishing(int a){
            return new Fishing(a){
                private String name = "maomao";
                @Override
                public void fish() {
                    super.fish();
                }
            };
        };
    }

    public static void main(String[] args) {
        Human human = new Human();
        Fishing fishing = human.getFishing(10);
        fishing.fish();
    }
}
//=====================================================
//p199 用匿名內部類實現工廠方法
public class Platform {

    public static void ride( CycleFacrory facrory){
        facrory.getCycle().getName();
    }
    public static void main(String[] args) {
        ride(Bicycle.facrory);
        ride(Unicycle.facrory);
        ride(Tricycle.facrory);
    }
}

public class Bicycle implements Cycle {
    //靜態單例工廠創建
    public static CycleFacrory facrory = new CycleFacrory() {
        @Override
        public Cycle getCycle() {
            return new Bicycle();
        }
    };

    @Override
    public void getName() {
        System.out.println("Bicycle");
    }
}
//工廠介面
public interface CycleFacrory {

    public Cycle getCycle();
}
//產品介面
public interface Cycle {

    public void getName();
}

2 疑問

靜態類里的方法沒有加static修飾還是靜態方法麽?

A:不是,待詳細解答。

為什麼方法內部的內部類不能用private修飾?

匿名內部類使用外部定義的對象或值時,編譯器要求其參數引用是final類型的,為什麼呢?

靜態類里的類(static修飾的class)和方法都沒有static修飾,屬於靜態方法和靜態常量麽?

嵌套類的作用?

普通內部類的欄位與方法,只能放在類的外部層次上,所以普通的內部類不能有static數據和static域,也不能包含嵌套類。但是嵌套類可以有static數據和static域,也能包含嵌套類。為什麼通的內部類不能有static數據和static域?

創建內部類對象的時刻並不依賴於外部類對象的創建。為什麼不需要?什麼時候不需要?(通過static方法調用或者創建的是嵌套類?)

閉包?閉包的作用?

回調?回頭網上結合研究下

enclossingClassReference.super(); ?繼承內部類時為什麼要用這個?

P245 練習26里如何生成一個帶參數的構造器?目前能實現的只能是帶繼承的內部類的外部類的引用參數,而不能創建內部類自帶的參數,因為這樣無法繼承了。待處理?

3 思想總結

內部類的使用主要用於配合彌補介面無法達到的多重繼承的時候使用,還有用於封裝實現,達到解耦。

其實如非必要,在設計階段儘量規避使用內部類實現,大部分情況下單繼承及介面實現便能滿足需求了。

十一、持有對象

1 知識

基本的容器使用:List、Set、Queue、Map。

數組使用的局限:數組具有固定的尺寸的局限性,使用起來不夠靈活。

集合容器通過使用泛型,就可以在編譯期防止將錯誤類型的對象放置到容器中。

如果不需要使用每個元素的索引(對索引有操作),可以使用foreach來迭代數據。

使用多態來創建容器,但是,當想要使用具體容器類的額外的功能時,就要直接創建具體類。如下:

List list = new ArrayList();
//List沒有peek()方法
LinkedList linkedList = new LinkedList();
linkedList.peek();

//Arrays.asList();底層表示還是數組,因此不能調整尺寸,不能使用add()或delete(),但可以修改set()
List list1 = Arrays.asList();

//通過顯示類型參數說明,告訴編譯器對於Arrays.<Snow>asList();產生的List類型,這樣才能編譯成功。
List<Snow> list2 = Arrays.<Snow>asList();

//數組容器的列印 必須使用方法
Arrays.toString()
//其他容器不需要,直接toString()即可(即直接傳入對象就可以調用預設toString()列印容器)。
//Collection列印出來的內容用方括弧括住,逗號分隔。[1,2,3,4]
//Map列印出來的內容用大括弧括住,逗號分隔,鍵和值由等號聯繫。{rat=1,tiger=2,monkey=3,drogon=4}

ArrayList 和 LinkedList 都是List類型,它們都按照被插入的順序保存元素。

HashSet、 TreeSet 、 LinkedHashSet 都是Set類型,Set的保存的值都是唯一的,存儲元素方式的區別:

HashSet: 通過散列值來存儲元素,使用HashSet可以最快地獲取元素。

TreeSet: 按照比較結果升序來保存對象,如果存儲順序很重要用TreeSet 。

LinkedHashSet : 對存儲順序,按照被添加的順序保存對象。

Map: 也稱關聯數組,像一個簡單的資料庫。

HashMap:提供最快的查詢技術,不是按照明顯的順序保存元素。

TreeMap:按照比較結果升序來保存鍵。

LinkedHashMap:按照被添加的順序保存對象,同時保留了HasMap的查詢速度。

List

ArrayList :優勢在隨機訪問元素,但是在List的中間插入和移除元素時較慢。

LinkedList :優勢在與在List的中間插入和移除元素時代價較低,並提供了優化的順序訪問。缺點是在隨機訪問方面相對比較慢。

P256 ListFeatures.class 演示了ArrayList 的主要常用操作方法。

ArrayList的indexOf()、remove()方法針對的是對象的引用操作及對象(比較基於equals()方法),這樣就不會因為相同的對象也能被remove。
ArrayList通過containsAll()方法來判斷包含關係,並不會因為排序和順序問題而不同,比較的只是元素的包含關係。
retainAll()是取交集的操作。
isEmpty()判斷容器集合是否為空
clear()清除集合操作。
Pet[] pet = list.toArray(new Pet[0]);//返回一個具有合適尺寸的數組。

迭代器

//迭代器模式,不關心具體的容器集合類型
Iterator iterator = list.iterator();
//ListIterator只能用於各種List類的訪問。可以進行雙向移動,往前往後,且可以增刪改查。
ListIterator listIterator = list.listIterator();

LinkedList

LinkedList 還添加了可以使其用作棧、隊列或雙端隊列的方法。有些方法作用相同而名字不同,是為了在特定用法的上下文環境中更加適用(特別是在Queue)如getFirst()和element()與peek().三個都是獲取第一個元素的方法,前面兩個如果List為空則拋出異常,而第三個為空時返回null。

還有其他一些也是一樣的。

TreeSet: 按照比較結果升序來保存對象,我們還可以對TreeSet傳入我們想要的排序特性。

//不區分大小寫字母排序
SortedSet set = new TreeSet(String.CASE_INSENSITIVE_ORDER);

Stack

LinkedList具有能夠直接實現棧的所以功能的方法,因此可以直接將LinkedList作為棧使用。

類名之後的告訴編譯器這將是一個參數化類型,當類被使用時會被實際類型所替換,就是T.

Java.util.Stack 設計欠佳。

public class Stack<T> {

    private LinkedList<T> storage = new LinkedList<>();
    public void push(T v){
        storage.addFirst(v);
    }
    public T peek(){
        return storage.getFirst();
    }
    public T pop(){
        return storage.removeFirst();
    }
    public boolean empty(){
        return storage.isEmpty();
    }
    @Override
    public String toString(){
        return storage.toString();
    }
}

Set

Set不保存重覆的元素。

Set最常被使用的是測試歸屬性,經常要詢問某個對象是否在某個Set中,因此查詢是Set中最重要的操作。而,使用HashSet可以最快地獲取元素。

//使用contains()方法測試歸屬性
boolean contains = set1.contains("a");

Map

將對象映射到其他對象的能力是一種解決編程問題的殺手鐧。

Map可以返回它的鍵Set(因為鍵都是唯一的),它的值Collection(因為它的值可以是相同的,所以不是set),或者它的鍵值對的Set(是個EntrySet)。

@Test
public void mapTest1() {
  Map<Integer,Integer> map = new HashMap();
    map.put(Integer.valueOf(1),1);
    map.put(Integer.valueOf(2),1);
    boolean containsKey = map.containsKey(1);
    System.out.println("containsKey="+containsKey);
    boolean containsValue = map.containsValue(1);
    System.out.println("containsValue="+containsValue);
    Set<Integer> keySet = map.keySet();
    System.out.println("keySet="+keySet);
    Collection<Integer> values = map.values();
    System.out.println("values="+values);
    Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
    System.out.println("entrySet="+entrySet);
}

Queue

隊列是一個典型的先進先出(FIFO)容器。

隊列在併發編程中特別重要,因為它可以安全地將對象從一個任務傳輸給另一個任務。

LinkedList 可以作為Queue的一種實現,因為它實現了Queue介面。

queue.offer()方法將一個元素插入到隊尾。

peek()和element()在不移除的情況下返回隊頭。peek()在隊列為空是返回null,element()則拋異常。

poll() 和 remove() 移除並返回隊頭。poll()在隊列為空是返回null,remove()則拋異常。

PriorityQueue

優先順序隊列的下一個彈出元素是最需要的元素(具有最高的優先順序)。

PriorityQueue 確保當我們調用peek()、poll() 和 remove()時,獲取的元素是隊列中優先順序最高的元素。

如果需要,可以通過提供自己的Comparator來修改這個順序。

@Test
public void queueTest() {
    Queue<String> queue = new LinkedList();
    String[] split = "i am iron man".split(" ");
    for (int i = 0; i < split.length; i++) {
        queue.offer(split[i]);
    }
    while (queue.peek() != null) {
        //System.out.print(JSON.toJSONString(queue.remove()));
        System.out.print(queue.remove()+" ");
    }
}

    @Test
    public void queueTest() {
        Queue<String> queue = new LinkedList();
        String[] split = "i am iron man".split(" ");
        for (int i = 0; i < split.length; i++) {
            queue.offer(split[i]);
        }
        while (queue.peek() != null) {
            //System.out.print(JSON.toJSONString(queue.remove()));
            System.out.print(queue.remove()+" ");
        }
        java.util.List<Integer> list = Arrays.asList(19, 2, 3, 5, 7, 10);
        PriorityQueue<Integer> priorityQueue = new PriorityQueue(list);
        //註意,必須用方法列印出來,如果只是用JSON.toJSONString(priorityQueue)是不能得到效果的,因為存儲並不固定,而是通過獲取元素時比較來確定優先順序的
        while (priorityQueue.peek()!= null){
            System.out.print(priorityQueue.remove()+" ");
        }
        System.out.println();
        //System.out.println(JSON.toJSONString(priorityQueue));
        PriorityQueue<Integer> priorityQueue1 = new PriorityQueue(list.size(),Collections.reverseOrder());
        priorityQueue1.addAll(list);
        while (priorityQueue1.peek()!= null){
            System.out.print(priorityQueue1.remove()+" ");
        }
       // System.out.println(JSON.toJSONString(priorityQueue1));
    }

我們可以通過參數化Collection來表示容器之間的共性或者實現iterator()方法來實現迭代器功能。C++沒有Collection而是通過迭代器來表示容器的共性。Java都有。

foreach:

只要我們創建的類實現了Iterable介面,就能用foreach語法糖。Iterable介面包含一個能夠產生Iterator的Iterator()方法。

foreach語句可以用於數組或其他實現了Iterable介面的類,但註意:數組不是一個Iterable。

其他知識:

@Suppress Warnings (unchecked):表示只有有關“不受檢查的異常”的警告信息應該被抑制。這樣就不會有黃色警告了。

預設的toString()方法將列印類名@散列碼(例:Apple@11b86e7 。是hashCode()方法生成的無符號16進位數)。

容器結構圖:

2 疑問

既然LinkedHashMap按照被添加的順序保存對象,同時保留了HasMap的查詢速度。為什麼我們不用LinkedHashMap替換HashMap?其實還是有影響的?

ArrayList 和 LinkedList 只是在List中插入數據快慢有區別?在預設add方法插入有性能區別麽?LinkedList 缺點是在隨機訪問方面相對比較慢,那麼迭代器順序訪問區別還很大麽?

A:ArrayList 在中間插入確實效率不佳,但在首尾插入呢?

ArrayList 和 Stack 這些後面尖括弧裡面的值是有區別的麽?還是只是一個類型代稱。

散列?

SortedSet set = new TreeSet(); SortedSet 不是 TreeSet的父類或者介面,為什麼也能多態呢?

A: 是有介面繼承關係的。可以在idea中右擊類選擇diagrams-show diagrams.查看類繼承結構。

Set對於其填充的值只能add一種類型麽?那為什麼ArrayList可以是Objcet類型的?set不行?

public class TreeSet<E> extends AbstractSet<E>{
public boolean add(E e) {
    return m.put(e, PRESENT)==null;
}
}

A:目前實驗發現HashSet是可以隨意添加各種類型的元素,而TreeSet不行?待瞭解原因。

Map.Entry<Integer, Integer> ?map.entrySet();返回的這個entry,entrySet是個什麼類型的數據結構?本質是其實就是個Map麽?

3 思想總結

數組一旦生成,其容量或者說長度是不能改變的,這是數組使用的局限性。

Collection保存單一的元素,而Map保存相關聯的鍵值對。

如果需要進行大量的隨機訪問,則使用ArrayList,如果經常從表中間插入或刪除元素,則使用LinkedList。

Queue 或Stack(棧,不是java.util裡面的那個),由LinkedList 提供支持。

新程式不應該使用過時的Vector、Hashtable、Stack。

總的其實只有四種容器:Map、List、Set、Queue

十二、通過異常處理錯誤

1、如何編寫正確的異常處理程式

2、如何自定義異常。

1 知識

使用異常的好處(對編程語言來說加入異常的好處):

  • 降低處理錯誤代碼的複雜度,否則處理錯誤代碼繁多且臃腫,影響正常核心邏輯的理解和處理。
  • 只需要在一個地方處理錯誤(異常處理程式,catch塊)。
  • 將正常代碼和問題處理代碼相分離。

異常最重要的方面之一就是如果發生問題,它將不允許程式沿著其正常的路徑繼續走下去。C或 C++不行,尤其是C沒有任何辦法強製程序在出現問題時停止在某條路徑上運行下去。

我們拋出異常時總是用new在堆上創建異常對象。異常一般會有預設構造器或者接受字元串作為參數的構造器,以便把相關信息放入異常對象的構造器;或者兩種都有。

只有匹配的catch子句才能得到執行,執行後變跳出異常處理程式塊,有finally則執行,否則結束。

異常處理的兩種模型:

  • 終止模型。將異常拋出不處理。
  • 恢復模型。修正錯誤,重新嘗試調用出問題的方法。

創建自定義異常

要自定義異常類,必須從已有的異常類繼承.對異常來說,最重要的部分就是類名,異常的名稱應該望文生義。如果需要自定義異常描述信息則添加有參構造器即可。

異常說明

即在方法後加上聲明可能會拋出的異常,告知此方法的調用者。

public void exceptionTest() throws TestExecption {}

如果方法沒有throws 異常說明,則說明此方法不會拋出任何異常(也有可能被try catch了)。

PS: 任何繼承了RuntimeException的異常(包含它自己,這些稱為不受檢查異常,屬於錯誤,將被自動捕獲),是可以在沒有異常說明的情況下被拋出的,因為這時候是運行時異常。

在定義設計抽象基類和介面時可以預留聲明方法將拋出異常,方便子類和介面實現類可以拋出預先聲明的異常。

我們可以在catch處理時通過捕獲Exception基類來捕獲所以類型的異常,Throwable 也可以。所以如果要分批catch處理,那麼最好將Exception放在末尾,防止它搶先匹配捕獲異常。

//會重新裝填異常信息,如果調用fillInStackTrace,那麼會把調用的那一行變成異常的新發生地。
//fillInStackTrace()方法在thow 異常時的Throwable構造器裡面調用封裝異常信息
fillInStackTrace();

異常鏈

異常鏈:在捕獲一個異常後拋出另一個異常,並且希望把原始異常的信息保存下來。

兩種方式實現吧其他類型的異常鏈接起來:

  • 繼承Error、Exception、RuntimeException(主要是後面兩個),實現帶cause參數的構造器。
  • 調用initCause()方法連接異常。

    @Test
    public void exceptionTest() throws Exception {
    //throw new TestExecption();
    try {
    //throw new TestExecption("i am god");
    //throw new TestExecption(new NullPointerException());
    throw (Exception) new TestExecption().initCause(new IndexOutOfBoundsException());
    } catch (Exception testExecption) {
    //預設輸出標準錯誤流
    testExecption.printStackTrace();
    //可以指定輸出流——標準流
    testExecption.printStackTrace(System.out);
    }
    }

Error用來表示編譯時和系統錯誤,一般程式員不關心;Exception是可以被拋出的基本類型,是程式員應該關心的。

只能在代碼中忽略RuntimeException及其子類的異常(因為RuntimeException是種錯誤,無法執行下去了),其他類型異常的處理都是由編譯器強制實施的。

如果一個方法中有多個return,包括finally塊也有,則最後返回的是finally塊里的return.

使用try+finally則就是異常也正常執行,這種情況下可以丟失異常。

通過強制子類遵守父類方法的異常說明對象的可替換性就得到了保證。子類不能拋出大於父類異常聲明的類型,最大的是Throwable。

父類拋出異常,這時子類方法可以不拋出任何異常。因為不影響已有的程式。

不要在構造器中打開資源什麼的,否則構造器就算能執行finally或者catch關閉也不好。

通用的清理資源規則是:在創建需要關閉資源,清理對象之後,立即進入一個try-finally語句塊。

異常匹配

拋出異常的時候,異常處理程式會按照代碼書寫順序找出“最近”的處理程式。匹配處理之後將不再繼續查找。catch會捕獲其異常類及所有從它派生的異常。

當我們還不知道怎麼handle 異常的時候把異常catch了,導致後面異常被吃了,這種是有問題的。

所有模型都是錯誤的,但有些是能用的。

異常處理的可選方式(重要)

被檢查異常的優缺點:

優點是一次說明能增加開發人員的效率,並提高代碼的質量,對小項目,小程式友好。

缺點是對於大項目來說,過多的異常類型聲明及檢查導致項目無法管理,開發效率下降,也不能很好的提高代碼質量。

總的來說,Java的”被檢測異常“帶來的麻煩比好處要多。

原因是被檢查異常強迫程式員在不知道該採取什麼措施的時候提供異常處理程式,這是不現實的。(亞信的代碼就是這樣,方法里一堆的異常聲明;優車就好很多,控制得很好。)

異常機制及強靜態類型檢查必要的原因是:

  1. 不在於編譯器是否會強製程序員去處理錯誤,而是要有一致的、使用異常來報告錯誤的模型。
  2. 不在於什麼時候進行檢查,而是一定要有類型檢查。必須強製程序使用正確的類型,置於這種強制是在編譯器還是運行時並不重要。

減少編譯時施加的約束能顯著提高程式員的編程效率。反射和泛型就是用來補償靜態類型檢查所帶來的過多限制。

好的程式設計語言能幫助程式員寫出好程式,但無論哪種語言都避免不了程式員用它寫出壞程式。

對於被檢查異常的處理方式:

  • 把異常傳遞給控制台。就不需要寫try-catch處理了。
  • 把”被檢查異常“ 轉換為 ”不被檢查異常“。方法有2:
  1. 即把”被檢查異常“包裝進RuntimeException,這樣方法也不用異常聲明(因為RuntimeException是不被檢查異常,不需要聲明或者處理)。
  2. 創建自己的RuntimeException子類,這樣拋出的異常也是不受檢查的,也不需要異常聲明或者try-catch處理。

    @Test
    public void exceptionTest() {//不需要聲明異常 throws Execption
    try {
    throw new TestExecption();
    } catch (TestExecption e) {
    e.printStackTrace();
    throw new RuntimeException(e);
    }
    }

異常使用指南:

  1. 在恰當的基本處理問題。(即在知道該如何處理異常的情況下才捕獲異常。)
  2. 解決問題並且重新調用產生異常的方法。(很少這樣處理)
  3. 進行少許修補,然後繞過異常發生的地方繼續執行。(一般是異常不影響流程或者可以忍受,直接忽略異常往下執行。)
  4. 用別的數據進行計算,以代替方法預計會返回的值。(也很少用到)
  5. 把當前運行環境下能做的事情進來做完,然後把相同的異常重新拋到更高層。(跟第三點差不多,這個也有用到)
  6. 把當前運行環境下能做的事情進來做完,然後把不同的異常重新拋到更高層。
  7. 終止程式。
  8. 進行簡化。(如果異常模式使得問題變得太複雜,則相對惱人)
  9. 讓類庫和程式更安全。

2 疑問

什麼叫析構函數?好像是指垃圾回收?

為什麼要用finally來關閉資源,關閉文件呢?不關閉會怎麼樣?

try+finally 的使用場景?是對異常忽略的時候用?

構造器也可以拋異常?為什麼要拋異常?相當於一個方法是麽,比較構造器可以調用方法?

3 思想總結

java異常處理機制將正常的邏輯與異常部分分開,讓我們可以更專註地分別處理這兩個問題。

異常處理的報告功能是異常的精髓所在,即錯誤日誌列印或沉澱等。

一致的錯誤報告系統意味著我們再也不必對所寫的每一段代碼都質問自己是否有錯誤被遺漏。

十三、字元串

1 知識

String對象是不可變的。String對象方法等之間的傳遞實際上是引用的一個拷貝。而該引用所指向的對象一直待在單一的物理位置上,從未動過。

String對象具有隻讀特性,所以指向它的任何引用都不可能改變它的值,也不會對其他的引用有什麼影響。

在使用字元串連接時如果拿不准用哪種方式,可以用javap來分析程式代碼。查看java代碼是如何工作的可以用javap反編譯代碼:

javap -c Demo    //(類名) -c表示將生成JVM位元組碼

重載“+” 與StringBuilder

通過javap反編譯可看到,在使用重載“+”時,編譯器或自動幫我們創建一個StringBuilder對象來構造連接出最終的String對象。但是要註意的時,在迴圈體里如果還是用重載“+”來連接String對象的話,編譯器自動創建的StringBuilder是在迴圈體內產生的,這意味著每一次迴圈都會創建一個新的StringBuilder對象。因此,在連接迴圈體內的String對象時,要自行創建StringBuilder對象在迴圈體外append()拼接,並且在使用append()方法連接時禁用append(a+ ":" +c)這種投機取巧的方式,否則編譯器又會自

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 我看的這本是Bjarne Stroustrup寫的,南開大學的楊巨峰和王剛譯的。這本書不適合初學者看,我就是大概翻了翻其中感興趣的章節。 這本書第14章的標題是“歷史和相容性”,這節內容我看了收穫很深。p144-145的內容值得去看。 從中可以看出,ISO C和ISO C++是K&R C [Kern ...
  • 這次是關於spring的面試題,和上次一樣依舊挑了幾個具有代表性的。 一. 談談你對 Spring 的理解 Spring 是一個開源框架,為簡化企業級應用開發而生。Spring 可以是使簡單的 JavaBean 實現以前只有 EJB 才能實現的功能。Spring 是一個 IOC 和 AOP 容器框架 ...
  • YAML 是 "YAML Ain't a Markup Language"(YAML 不是一種標記語言)的遞歸縮寫。在開發的這種語言時,YAML 的意思其實是:"Yet Another Markup Language"(仍是一種標記語言)。 YAML 的語法和其他高級語言類似,並且可以簡單表達清單、 ...
  • 原文地址: "http://www.work100.net/training/java if else.html" 更多教程: "光束雲 免費課程" 條件語句 序號|文內章節|視頻 : :|: |: 1| "概述" | 2| "if...else語句" | 3| "if...else...if... ...
  • 當我開始使用Flutter實施該應用程式時,我開始擔心“如何最好地編寫?”以及“如何使其更好地放置?”。 在這種情況下,您將需要參考GitHub上發佈的代碼和應用程​​序。 因此,我收集了似乎對Flutter 應用程式開發有用的代碼/庫/專有技術。 Flutter公式:編寫您的第一個Flutter應 ...
  • GC日誌 Heap PSYoungGen total 305664K, used 26214K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000) eden space 262144K, 10% used [0x00000000e ...
  • 報錯信息: qly@qlyComputer:~$ pip Traceback (most recent call last): File "/usr/bin/pip", line 9, in <module> from pip import main ImportError: cannot impo ...
  • 原文地址: "http://www.work100.net/training/java" 更多教程: "光束雲 免費課程" Java入門 Java 是由 Sun Microsystems 公司於1995年5月推出的高級程式設計語言。 Java 可運行於多個平臺,如 、`Mac OS UNIX`版本的 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...