李剛老師的《瘋狂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"; //true System.out.println(str instanceof Object); Object obj=new Object(); //false System.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.虛引用(沒什麼用)