這是我讀過的第三本關於java基礎的書.第一本<<java從入門到精通>>這本書讓我靈識初開.第二本<<java敏捷開發>>這本書則是有一次被一位師傅批評基礎太差時讀的.今日我向一位前輩請教應該怎樣再進步一點.前輩推薦我讀<<Java核心技術>>並告訴我,先讀了這兩本查漏補缺,然后在給我推薦進階的書.工具書這種東西,每一本都不可能相同,比如我們的數學書,同樣是小學的,兩個地區的書可能就不同.
以下為這本java核心技術中所積累的知識,僅為記錄筆記
--------------------------------------------------------------------------------------------------------
自動類型轉換
兩個數值.如果有double,則轉換成double.否則有float,轉換成float.否則有long,轉換long.否則都轉換成int
類之間的關系
依賴(uses-a):A類依賴B類.A類需要調用B類
聚合(has-a):A類包含B類.
繼承(is-a):A類繼承自B類.A類包含B類的屬性行為,以及有自己的屬性和行為
對於方法參數
其實是有一個隱藏的參數,就是this.表示當前的調用對象,而靜態方法中,則不存在this.因為靜態方法不依賴實例對象
final修飾類的原則
如果一個類的內部,沒有提供可修改類本身狀態的方法.則可將該類設為final(被final修飾的類不可以被繼承,如果你繼承了一個final類,則代表你可以自定義方法修改final類的屬性,其破壞了封裝性.這個坑在某個面試中,被坑了....
如果一個class被聲明為final,那么其所有的方法也默認被設置為final,而字段則不在final中.被final修飾的類主要目的還是阻止其他類通過成為其子類的方式而修改本身類的行為.
java的參數統一是按照值傳遞的.基本數據類型傳遞的值的副本.引用數據類型傳遞的引用的副本,所以方法內部對參數進行的修改,不會影響實參.
public class Person { public int age = 1; }
public class Test { public static void main(String[] args) { int y = 1; change(1); System.out.println(y);//1 Person p = new Person(); change(p); System.out.println(p.age);//2 change2(p); System.out.println(p.age);//2 } public static void change(int x) { x = 2; } public static void change(Person p) { p.age = 2; } public static void change2(Person p) { p = new Person(); p.age = 3; } }
以上例子可以看出當我們傳遞一個基本數據類型參數,方法中的改變不會更改原有的值.這個對於上邊的法則是能對於的.但是我們傳遞一個引用類型得到實參,則有了令人產生歧義的結果.實際上,這個結果是很迷惑人的(起碼在一段長的時間,我也沒有真正搞懂上邊規則的真正意思.)我們先來看change2()這個方法,這個方法內部,對傳入的實參重新new了一個對象,也就是說,當前方法中的p已經被重新實例化了.所以,當你把新的實例化的對象中的屬性域修改了值,那么根據以上的法則,當然不會影響main()方法的實參.再來看,change1()的內部構造,他直接將實參對象的一個屬性域的值修改了,結果竟然映射到了,main()方法中.這豈不是有悖於以上法則?其實不然,我們可以追溯到基本數據類型,當一個int i = 1;被創建則 1 已經被分配了內存空間.我們我們將 i = 2 那么 i 只是換了一個應用值2,實際的1還是存在的(這里我們暫且不考慮垃圾回收)話題回來,我們在change()中修改了實參的一個屬性,那么這個實參的實際存儲地址變了嗎?沒有的.而change2()則是直接新建了一個對象,新開辟了一片內存,你在新的地址里更改自己的屬性,當然也不會影響實參的屬性.(ps關於這類的問題,我曾經看過很多形參,實參,傳值,傳址之類的博客,其實看得越多,越迷糊.如果你看到以上我的描述,覺得沒有茅塞頓開那么就請忘掉他.避免對你造成更大的困擾)
關於構造函數
在一個類中,如果有成員 int i; 而改成員沒有值.則會在new出該對象實例時,賦值為0;這個動作原來是在構造函數執行的.數值類型被默認為0,Boolean被默認為false,引用類型被默認為null.
equals特性
1 自反性: 對於任何非空引用x,x.equals(x);應當返回true.
2 對稱性: 對於任何引用x和y,當且僅當y.equals(x);返回true,x.equals(y);也應當返回true.
3 傳遞性: 對於任何引用x,y,z如果x.equals(y)返回true; y.equals(z)返回true;那么x.equals(z);應當返回true
4 一致性: 如果x和y的引用對象沒有發生變化,返回調用x.equals(y);應當返回同樣的結果.
5 對於任意非空引用 x,x.equals(null);應當返回false.
關於包裝類型Integer
對於包裝類型我們大家都熟悉.對於以下操作我們則是使用了包裝類型的特性:自動裝箱.自動拆箱
public static void main(String[] args) { Integer i = 100; i = i + 1; System.out.println(i); }
這里有個對於自動裝箱有個值得思考的問題,如果兩個相同的數值用==判斷,如果數值相同,則返回true.那么如果將這兩個數值裝箱為Integer還能用==判斷嗎?
public static void main(String[] args) { Integer i = 1000; Integer x = 1000; System.out.println(i == x);//false Integer z = 100; Integer y = 100; System.out.println(z == y);//true }
i 和 x 的==判斷是我們意料之中的.兩個對象 == 如果地址不同肯定是false嘛!那么 z 和 y呢?
書中的解釋
自動裝箱規范要求 boolean、byte、char 127, 介於 -128 ~ 127 之間的 short 和 int 被包裝到固定的對象中。
但是需要注意的如果我們顯式的創建兩個Integer可不會有這個特性.
public static void main(String[] args) { Integer i = 127; Integer x = 127; System.out.println(i == x);//true Integer z = new Integer(127); Integer y = new Integer(127); System.out.println(z == y);//false }
另外這里還有一點.之前我們說,當向一個方法傳遞引用類型的實參時,我們是有可能修改這個實參的屬性的.那么我們來看這個
public static void main(String[] args) { Integer i = new Integer(1); change(i); System.out.println(i);//1 } public static void change(Integer i) { i = i.valueOf(2); i = 3; }
白日做夢~~~~怎么可能會讓你修改一個基本類型的值呢.~~~~~
關於接口的默認方法
在JDK8中接口中可以提供默認的實現方法.如果,一個類的父類和接口有同樣的一個方法.子類會選擇使用誰的呢?
public class P1 { public void add(){ System.out.println("1"); } }
public interface P2 { default public void add(){ System.out.println("2"); } }
public interface P3 { default public void add(){ System.out.println("3"); } }
public class Person extends P1 implements P2,P3{ public static void main(String[] args) { Person p = new Person(); p.add();//1 } }
結果是繼承優先.
那如果兩個接口有同樣的默認方法呢?
結果是報錯.
public class Person implements P2{ public static void main(String[] args) { P2.get(); } }
接口的靜態方法
此外jdk8還運行接口定義靜態方法
public interface P2 { public static void get(){ System.out.println("get"); } }
public class Person implements P2{ public static void main(String[] args) { P2.get(); } }
我們可以直接使用接口 . 的方式使用靜態方法.
但是一下調用是不可以的.
可以看出當一個父接口有靜態方法時,子實現是沒有繼承父類的靜態方法的.這一點和繼承是不一樣的.
我們來看下繼承中的靜態方法.
public class P1 { public static void add(){ System.out.println("1"); } }
public class Person extends P1{ public static void main(String[] args) { Person.add(); } }
這兩個的區別要搞清楚.
Comparable 和 Comparator
在我們使用一些工具類的sort排序方法時,比如Arrays或者Collections時他們總是需要我們將需要排序的類實現 Comparable 接口中的.compareTo().原因就是,假設要對一個集合中的User進行排序,那么您總要告訴工具類您要的排序規則是啥對吧.那么再來說說既然有了Comparable 為啥還要有Comparator這個接口.在面向對象的設計理念中,一個類,他最好只有屬於自己特點功能的方法顯然,排序這個方法,只能算是工具方法.所以在一個類的設計初期,非常可能的並沒有實現Comparable 中的compareTo()方法.而在后期的開發業務功能時,我們發現,我們需要對User類進行排序呀.這時為了不破壞原有的代碼,我們則可以另外創建一個類,並讓這個類實現Comparator接口中的compare()方法.並在使用工具類時,將Comparator的實現類傳遞給工具類.這樣的設計理念主要還是為了保證一個類的完整性,已經單一功能性.
關於clone
在Object中有一個方法clone()此方法是一個受保護的方法.clone().關於這個受保護的clone()方法在子類中不能直接使用的問題.PS必須實現Cloneable標記方法,而且您還要覆蓋Objece類的clone()才能使用.至於為啥這個clone明明是受保護的類子類不能直接用,百度了好久,也沒找到答案,本着不鑽牛角尖的精神.咱們只來探究下clone()一起的淺拷貝深拷貝問題.
public class Test1 implements Cloneable{ public int age = 1; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
public class Run { public static void main(String[] args)throws Exception{ Test1 test1 = new Test1(); Test1 test11 = (Test1) test1.clone(); test11.age = 2; System.out.println(test1.age);//1 System.out.println(test11.age);//2 } }
結果是被拷貝的對象被改變了,不會影響原對象.這算是深拷貝.那咱們再來試試原對象中有引用類型,會怎樣.
public class Test1 implements Cloneable{ public int age = 1; public String name = "張三"; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
public class Run { public static void main(String[] args)throws Exception{ Test1 test1 = new Test1(); Test1 test11 = (Test1) test1.clone(); test11.age = 2; test11.name="李四"; System.out.println(test1.age);//1 System.out.println(test11.age);//2 System.out.println(test1.name);//張三 System.out.println(test11.name);//李四 } }
引用類型也會深拷貝.那咱們在來換個自定義的引用類型.
public class Person { public int height; public Person(int height) { this.height = height; } }
public class Test1 implements Cloneable{ public int age = 1; public String name = "張三"; public Person person = new Person(1); @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
public class Run { public static void main(String[] args)throws Exception{ Test1 test1 = new Test1(); Test1 test11 = (Test1) test1.clone(); test11.age = 2; test11.name="李四"; test11.person.height = 3; System.out.println(test1.age);//1 System.out.println(test11.age);//2 System.out.println(test1.name);//張三 System.out.println(test11.name);//李四 System.out.println(test1.person.height);//3 System.out.println(test11.person.height);//3 } }
結果,這次就變成淺拷貝了.這是幾個意思呢?為啥String 和 Person都是引用類型.String就是深拷貝,Person就是淺拷貝嘞.還是看看黃金屋中怎么說的.
所以Object的clone()方法就是淺拷貝.只不過對於 int 和 String 以及一些不可變的類型是安全的.所以給了我們是深拷貝的假象.所以要想實現深拷貝,我們還要覆蓋Object的clone()方法才行.
public class Test1 implements Cloneable { public int age = 1; public String name = "張三"; public Person person = new Person(1); @Override protected Object clone() throws CloneNotSupportedException { Test1 test1 = new Test1(); test1.age = age; test1.name = name; Person person = new Person(this.person.height); test1.person = person; return test1; } }
public class Run { public static void main(String[] args)throws Exception{ Test1 test1 = new Test1(); Test1 test11 = (Test1) test1.clone(); test11.age = 2; test11.name="李四"; test11.person.height = 3; System.out.println(test1.age);//1 System.out.println(test11.age);//2 System.out.println(test1.name);//張三 System.out.println(test11.name);//李四 System.out.println(test1.person.height);//1 System.out.println(test11.person.height);//3 } }
如此一來,則成功實現了深拷貝.PS實現深拷貝的方法有很多.例如更常見的利用序列化進行深拷貝.有興趣的可以自行了解下.
關於lambda(只記得這一些十分皮毛的東西,假如要應用到真正的開發,確實不是短時間內,我可以做到的.)
先來看一個例子,說明下lambda的基本語法
public class Person { private int age; public Person(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public class Test { public static void main(String[] args) { Person[] p = new Person[3]; p[0] = new Person(3); p[1] = new Person(2); p[2] = new Person(1); Arrays.sort(p, (Person x, Person y) ->{return x.getAge() - y.getAge();}); for (Person person : p) { System.out.println(person.getAge()); } } }
以上是使用Arrays中的sort()對Person對象數組進行排序,顯而易見之前sort()中的第二個參數是一個比較器.此時我們沒有new一個Comparator,實現compare()而是采用了lambda表達式.看看這個怪異的寫法.兩個參數,一個 -> 然后是一個表達式.嗯這么拆開看已經很明白啦.使用lambda表達式假裝創建了一個Comparator接口的匿名內部類,我們只需要關注compare方法的實現細節即可.
還可以這樣寫.
public class Test { public static void main(String[] args) { Person[] p = new Person[3]; p[0] = new Person(3); p[1] = new Person(2); p[2] = new Person(1); Arrays.sort(p, ( x, y) ->x.getAge() - y.getAge()); for (Person person : p) { System.out.println(person.getAge()); } } }
因為已經確定了數組中的類型為Person,所以省略.compare方法中只有一個行代碼.不帶大括號,也不使用return關鍵字.
關於lambda暫且放下.等待抽一個時間系統學習