瘋狂Java講義 讀書筆記(一),java講義讀書筆記
李剛老師的《瘋狂Java講義》(第三版)是我的啟蒙作品,前前後後一共看了3遍,每次都有新的認識。
接下來的一段時間就將一些經典的知識記錄下來。
1.電腦進階語言的執行方式分為編譯型和解釋型,前者運行高效但不能跨平台(C,C++,Object-C),後台可以跨平台但效率不高(Ruby,Python)。Java比較特殊,先編譯產生.class,再在JVM中解釋。
2.記憶體回收機制:依靠記憶體回收演算法,何時回收對Java程式員而言具有透明性,因此要養成良好的習慣——對於不需要的對象,不要引用他們。(在堆中進行回收)
3.Java是強型別語言:所有類變數必須先聲明後使用,指定類型的變數只能接受與類型相匹配的值。
4.Java支援兩種類型:1、基本類型:布爾類型、數實值型別 2.參考型別:類、介面、數組。
5.強制類型轉化:造成溢出時,之前一直覺得19.745會變成19(實在汗顏),其實是轉換成二進位後再進行截取。
6.常量池:在編譯期被確定,並已被儲存在.class檔案中的一些資料。包括類、方法、介面中的常量,也包括字串常量。
7.switch語句:控製表達式的資料類型只能是byte,short,char,int,枚舉,String.
8.break和continue可以通過標籤跳到指定的迴圈層
public class BreakTest2{ public static void main(String[] args) { outer: for (int i = 0 ; i < 5 ; i++ ) { for (int j = 0; j < 3 ; j++ ) { System.out.println("i的值為:" + i + " j的值為:" + j); if (j == 1) { break outer; } } } }}
9.棧和堆:1、當一個方法執行時,每個方法都會建立自己的記憶體棧,在這個方法內定義的變數將會逐個放入這塊棧記憶體裡,隨著方法的執行結束,這個方法的記憶體棧也將自動銷毀。因此,所有在方法中定義的局部變數都是放在棧記憶體中;2、當我們在程式中建立一個對象是,這個對象將被儲存到運行是資料區中,以便反覆利用,這個運行是資料區就是堆記憶體。堆記憶體中的對象不會因方法結束而銷毀,就算方法結束後,這個對象還可能被另一個變數所引用,則這個對象不會銷毀,只有當一個變數沒有任何引用變數引用它,那麼系統的記憶體回收行程才會在合適的時候回收它;
10.Arrays:Java8增加的工具類,在java.util包下,支援並發編程
// 定義一個a數組 int[] a = new int[]{3, 4 , 5, 6}; // 定義一個a2數組 int[] a2 = new int[]{3, 4 , 5, 6}; // a數組和a2數組的長度相等,每個元素依次相等,將輸出true System.out.println("a數組和a2數組是否相等:" + Arrays.equals(a , a2)); // 通過複製a數組,產生一個新的b數組 int[] b = Arrays.copyOf(a, 6); System.out.println("a數組和b數組是否相等:" + Arrays.equals(a , b)); // 輸出b數組的元素,將輸出[3, 4, 5, 6, 0, 0] System.out.println("b數組的元素為:" + Arrays.toString(b)); // 將b數組的第3個元素(包括)到第5個元素(不包括)賦為1 Arrays.fill(b , 2, 4 , 1); // 輸出b數組的元素,將輸出[3, 4, 1, 1, 0, 0] System.out.println("b數組的元素為:" + Arrays.toString(b)); // 對b數組進行排序 Arrays.sort(b); // 輸出b數組的元素,將輸出[0, 0, 1, 1, 3, 4] System.out.println("b數組的元素為:" + Arrays.toString(b)); }
11.Java語言通過new關鍵字調用構造器。
12.this關鍵字:1、讓類中的一個方法,訪問該類裡的另一個方法或者執行個體變數。2、this所代表的對象是不確定的,但他的類是確定的。3、在構造器中,this代表該構造器正在初始化的對象。4、如果在某個方法中把this作為傳回值,則可以多次連續調用同一個方法。
public class ReturnThis{ public int age; public ReturnThis grow() { age++; // return this返回調用該方法的對象 return this; } public static void main(String[] args) { ReturnThis rt = new ReturnThis(); // 可以連續調用同一個方法 rt.grow() .grow() .grow(); System.out.println("rt的age成員變數值是:" + rt.age); }}
13.static:不要使用對象去調用static修飾的成員變數,要使用類去調用。
14.值傳遞:將實際參數的副本傳入方法內,參數本身不會受到任何影響。
15.形參可變的方法:定義方法時,在最後一個形參的類型後增加三個點(...)。多個參數值被當成數組傳入。與傳入數組相比,形參可變更加簡潔,但一個方法只能有一個可變形參。
public class Varargs{ // 定義了形參個數可變的方法 public static void test(int a , String... books) { // books被當成數組處理 for (String tmp : books) { System.out.println(tmp); } // 輸出整數變數a的值 System.out.println(a); } public static void main(String[] args) { // 調用test方法 test(5 , "瘋狂Java講義" , "輕量級Java EE公司專屬應用程式實戰"); }}
16.遞迴演算法:一個方法調用自身,一定要向已知方向遞迴。
17.局部變數:形參(方法簽名中定義的變數),方法局部變數(在方法內定義),代碼塊局部變數(在代碼塊內定義),局部變數不屬於任何類或者執行個體,它總是儲存在其所在方法的棧記憶體中。
18.封裝:類的成員變數不直接暴露,而是通過方法實現操作和訪問,以便於在方法中添加一些限制條件
19.高內聚:儘可能把模組的內部資料、功能實現細節隱藏在模組內部獨立完成,不允許外部直接幹預;低耦合:僅暴露少量的方法給外部使用。
20.靜態匯入:JDK1.5增加的導包方法,用於匯入包內的靜態成員 import static package...
21.構造器:1、構造器是建立Java對象的重要途徑,但這個對象並不是完全由構造器負責建立的。2、子類的構造器必定會調用其父類的構造器(沒有super和this時預設調用無參的構造器)。
22.方法重寫(覆蓋)原則:1、兩同:方法名、參數列表相同 2、兩小:子類方法傳回值<=父類方法傳回值 3、一大:子類方法存取權限>=父類方法存取權限
23.當程式建立一個子類對象時,系統不僅會為該類中定義的執行個體變數分配記憶體,也會為它從父類繼承得到的所有執行個體變數分配記憶體。
24.引用變數類型:1、編譯時間類型:由聲明該變數時使用的類型決定 2、運行時類型:由實際賦給該變數的對象決定 3、編譯時間類型和運行時類型不同時,就出現了所謂的多態 4、引用變數在編譯階段只能調用其編譯時間類型所具有的方法,但運行時則執行它運行時類型所具有的方法(可用強制類型轉換解決問題)。
25.向上轉型:把一個子類對象直接賦值給父類引用變數;強制類型轉換:把一個父類對象賦給子類引用變數
26多態:1、相同類型的變數,調用同一個方法時呈現出多種不同的行為特徵叫做多態 2、成員變數不存在多態,總是調用父類的值
27.instanceof:判斷前面的對象是否是後面的類,或者其子類、實作類別的執行個體
String str="str";//trueSystem.out.println(str instanceof Object);Object obj=new Object();//falseSystem.out.println(obj instanceof String);
28.繼承與組合:1繼承代表"is a",組合代表"has a" 2.建立子類對象,系統會為其父類所定義的執行個體變數分配記憶體空間,因此繼承和組合在系統開銷上沒有太大的區別
29.初始化塊:1.初始化塊在構造器之前執行(編譯後初始化塊的內容會還原到構造器中) 2.靜態初始化塊在普通初始化塊之前執行
30.封裝類:封裝類的執行個體可以與數實值型別直接比較
31.toString:自訂類時,盡量重寫類的toString方法,便於輸出執行個體的值
32.==與equals:1.對於參考型別,只有二者指向同一個對象,==才會等於true 2.String類型情況如下,編譯時間確定的資料在常量池中,運行時產生的資料在堆記憶體中
// s1直接引用常量池中的"瘋狂Java" String s1 = "瘋狂Java"; String s2 = "瘋狂"; String s3 = "Java"; // s4後面的字串值可以在編譯時間就確定下來 // s4直接引用常量池中的"瘋狂Java" String s4 = "瘋狂" + "Java"; // s5後面的字串值可以在編譯時間就確定下來 // s5直接引用常量池中的"瘋狂Java" String s5 = "瘋" + "狂" + "Java"; // s6後面的字串值不能在編譯時間就確定下來, // 不能引用常量池中的字串 String s6 = s2 + s3; // 使用new調用構造器將會建立一個新的String對象, // s7引用堆記憶體中新建立的String對象 String s7 = new String("瘋狂Java"); System.out.println(s1 == s4); // 輸出true System.out.println(s1 == s5); // 輸出true System.out.println(s1 == s6); // 輸出false System.out.println(s1 == s7); // 輸出false
33.重寫equals的條件:1.自反性:x.equals(x)=true 2.對稱性:若x.equals(y)=true,則y.equals(x)=true 3.傳遞性:若x.equals(y)=true,y.equals(z)=true,則x.equals(z)=true 4.一致性:只要x.equals(y)=true且x,y不變,無論調用多少次結果都不變 5.對任何不適Null的x,x.equals(null)=false 6.equals相同,則hashcode相同
34.null類型的執行個體可以訪問類的靜態方法和靜態變數,在底層是通過該執行個體的類去訪問的
35.final:1.final變數不是不能被賦值,而是不能被改變 2.final變數必須由程式員顯示的指定初始值 3.final修飾引用變數時,引用地址不可以改變,對象可以改變 4.final修飾的方式不能被重寫,但是可以被重載
36.宏變數:定義final變數時就為該變數指定了初始值,而且可以在編譯時間就確定下來,編譯器會把程式中所有用到改變數的地方直接替換成該變數的值(進入常量池)
37.不可變類:1.private final修飾所有成員變數 2.只有getter沒有setter
38.緩衝池:先進先出緩衝執行個體,重寫了equals和hsahcode
class CacheImmutale{ private static int MAX_SIZE = 10; // 使用數組來緩衝已有的執行個體 private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE]; // 記錄緩衝執行個體在緩衝中的位置,cache[pos-1]是最新緩衝的執行個體 private static int pos = 0; private final String name; private CacheImmutale(String name) { this.name = name; } public String getName() { return name; } public static CacheImmutale valueOf(String name) { // 遍曆已緩衝的對象, for (int i = 0 ; i < MAX_SIZE; i++) { // 如果已有相同執行個體,直接返回該緩衝的執行個體 if (cache[i] != null && cache[i].getName().equals(name)) { return cache[i]; } } // 如果緩衝池已滿 if (pos == MAX_SIZE) { // 把緩衝的第一個對象覆蓋,即把剛剛產生的對象放在緩衝池的最開始位置。 cache[0] = new CacheImmutale(name); // 把pos設為1 pos = 1; } else { // 把新建立的對象緩衝起來,pos加1 cache[pos++] = new CacheImmutale(name); } return cache[pos - 1]; } public boolean equals(Object obj) { if(this == obj) { return true; } if (obj != null && obj.getClass() == CacheImmutale.class) { CacheImmutale ci = (CacheImmutale)obj; return name.equals(ci.getName()); } return false; } public int hashCode() { return name.hashCode(); }}public class CacheImmutaleTest{ public static void main(String[] args) { CacheImmutale c1 = CacheImmutale.valueOf("hello"); CacheImmutale c2 = CacheImmutale.valueOf("hello"); // 下面代碼將輸出true System.out.println(c1 == c2); }}
39.預設方法:1.JDK1.8後新增的方法,在介面中一共有三種方法,抽象方法(abstract),類方法(static),預設方法(default),後兩者必須有方法實現 2.使用介面的執行個體來調用預設方法
40.抽象類別:抽象類別作為多個子類的抽象父類,可以被當成系統實現過程的中間產品,這個中間產品已經實現了系統的部分功能,但這個產品依然不能當成最終產品,必須由進一步的完善,
41.內部類:1.提供更好的封裝 2.內部類成員可以直接存取外部類的私人資料 3.匿名內部類適用於建立只需要一次使用的類 4.局部內部類和匿名內部類不是類成員 5.包含內部類的類被稱為外部類
42.非靜態內部類:1.非靜態內部類對象裡儲存了一個外部類的引用 2.外部類對象訪問非靜態內部類成員時,可能非靜態普通內部類對象根本不存在 3.不允許在非靜態內部類定義靜態成員
43.靜態內部類:1.靜態內部類可以包含靜態成員和非靜態成員 2.靜態內部類是外部類的類相關,靜態內部類對象寄生在外部類的類本身中,只持有外部類的類引用,沒有外部類的對象引用
44.內部類的使用: 1.盡量使用靜態內部類(調用簡單)2.局部內部類沒有什麼卵用 3.非靜態內部類的構造器必須由其外部類的對象來調用
public class SubClass extends Out.In{ //顯示定義SubClass的構造器 public SubClass(Out out) { //通過傳入的Out對象顯式調用In的構造器 out.super("hello"); }}
45.匿名內部類:1.必須繼承一個父類或者實現一個介面 2.不能是抽象類別 3.不能定義構造器 4.JDK1.8以前被匿名內部類訪問的局部變數必須使用final修飾,1.8以後會自動加上final修飾
46.枚舉類:可以使用枚舉類來替代靜態final常量
public class Enum {public enum Season{ spring,summer,fall,winter;}public static void main(String[] args) { Season season=Season.spring; System.out.println(season); switch (season){ case spring: System.out.println("spring"); break; case summer: System.out.println("summer"); break; case fall: System.out.println("fall"); break; case winter: System.out.println("winter"); break; } }
47.記憶體回收:1.只負責回收對象,不負責實體資源 2.程式無法精確控制記憶體回收的運行 3.在回收對象前,會調用對象的finalize方法嘗試讓其獲得新的引用以複活 4.finalize方法何時被調用具有不確定性
public class FinalizeTest{ private static FinalizeTest ft = null; public void info() { System.out.println("測試資源清理的finalize方法"); } public static void main(String[] args) throws Exception { // 建立FinalizeTest對象立即進入可恢複狀態 new FinalizeTest(); // 通知系統進行資源回收// System.gc(); //① // 強制記憶體回收機制調用可恢複對象的finalize()方法// Runtime.getRuntime().runFinalization(); //② System.runFinalization(); //③ ft.info(); } public void finalize() { // 讓tf引用到試圖回收的可恢複對象,即可恢複對象重新變成可達 ft = this; }}
48.參考型別:1.強引用(最常見的引用) 2.軟引用(當系統記憶體空間不夠時會被回收) 3.弱引用(比軟引用層級更低,當系統記憶體回收機制運行時,無論記憶體夠不夠都會被回收) 4.虛引用(沒什麼用)