從大學課程設計、畢業設計到工作中的一些小功能調整,自己的Java水平一直是會點語法+面向搜索引擎編程級別。想要更加深入地學習Java的東西,但是Java圣經又太厚,自己也不是特別擅長看這些技術書籍的人,在別人的建議下選了《Head First Java》來看。
縱觀全書,這本書的重點在Java的基礎語法、對象、多態與繼承等概念上的講解(講得挺好的),對于一些異常處理、線程、IO、網絡方面更多是簡單的介紹,并沒有深入講解。
原本想通過這本書學習線程方面的概念,為閱讀其他更加深入講解線程方面的文章打下基礎,但是似乎無法達到目的。
盡管如此,我還是通過閱讀這本書學習的到了一些新的東西。這篇筆記,就是為了整理記錄這些新學到的知識,以鞏固我對這些知識的掌握。
當我們聲明一個非基本數據類型的變量時,我們通常這樣寫:Dog myDog = new Dog();
在這簡單的一行代碼中,其實包含了三個步驟:
// 1. 聲明一個Dog類型的引用變量Dog myDog// 2. 創建Dog對象 new Dog// 3. 將創建的Dog對象,賦值給myDog這個引用變量Dog myDog = new Dog();
所以,當我們聲明對象數據的時候,實際上是聲明了該對象的引用變量數據。
在Java虛擬機驅動的時候,會從底層操作系統獲得一塊內存來執行Java程序。在內存中,要關注這兩塊區域:對象的生存空間堆和方法調用及變量生存的空間棧。
堆又被稱為可垃圾回收的堆,一旦對象失去了引用,就有可能被回收。
實例變量是被聲明在類而不是方法中,所以實例變量存在于所屬的對象中(堆中)。
而局部變量則是被生命在方法中,所以局部變量存在于棧中。
Book a = new Book();Book b = new Book(); //這里有兩個引用變量,兩個對象Book c = a; //此時有三個引用變量,兩個引用變量,c與a指向同一個對象b = a; // 此時b也與a指向同一個對象,b原本指向的對象失去了引用,處于可回收狀態
子類會繼承父類除了private的所有實例變量和方法。
運用多態時,引用類型可以是實際對象類的父類;參數和返回類型都可以多態。
Animal[] animals = new Animal[5];animals[0] = new Dog();animals[1] = new Cat();Class Vet{ public void giveshot(Animal a){ a.makeNoise();}Vet vet = new Vet();v.giveshot(new Dog());}
父類的方法可以在子類中被覆蓋,但是標記了final的方法無法被覆蓋。
一個類只能繼承一個父類。但是繼承可以是多層繼承。B繼承A,C繼承B,則C也是A的子類。
所有對象都是Object的子類,可以用Object來實現多態,但是一般不這樣做。
Object o = new Dog();int i = o.hashCode(); // 可行,因為Object本身有hashCode()方法o.makeNoise(); // 不可執行,因為此時o的引用類型是Object,Object沒有makeNoise()方法,無法執行。Animal a = (Dog) o;a.makeNoise(); // 可行,當o賦值給a時進行了類型轉換,Dog是Animal的子類,所以可以賦值成功。Animal有makeNoise()方法,所以可以執行
當一個類執行構造函數時,會先執行完父類的構造函數。如果存在多層繼承,就會一直到最初的父類執行完構造函數,才一層一層向下執行。
在子類的構造函數中執行父類的構造函數:
public Dog(String name){//調用父類的構造函數super();}
當子類的構造函數中沒喲調用super()時,編譯器在編譯時在我們編寫的構造函數中添加super();
當我們想重載一個構造函數,但是又調用到這個構造函數時:
Class Dog{ String name; public Dog(){ // 調用了有一個String參數的構造函數 this("myDog"); } public Dog(String name){ //調用父類的構造函數 super(); }}
在構造函數中,this()和super()都要存在于構造函數的第一行,不得同時存在。
抽象類沒有實體,抽象類中的方法也沒有方法體。
接口類是抽象類,抽象類不一定是接口類。
implements接口后,要實現該接口類的所有方法。
extends只能extend一個父類,implements可以implement好多個接口。
非靜態方法需要有實例才能調用,靜態方法不需要實例,直接以類名就可以調用。
// 非靜態方法Dog d = new Dog();d.makeNoise();// 靜態方法Math.min(3,7);
靜態方法中沒有實例變量,也不允許調用非靜態的變量。
靜態方法不允許調用費靜態的方法。
靜態變量是同一個類所有實例共享的。每一個實例都有一個屬于自己的實例變量。但靜態變量是每個類一個。這里可能會有多個實例對同一個靜態變量同時進行修改的問題。
靜態變量會在該類有任何靜態方法執行之前就初始化。
final的變量代表值無法被改變,final的方法代表無法被覆蓋,final的類代表無法被繼承。以下例子來自java提高篇(十五)-----關鍵字final
public class Person { private String name; Person(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; }}public class FinalTest { private final String final_01 = "chenssy"; //編譯期常量,必須要進行初始化,且不可更改 private final String final_02; //構造器常量,在實例化一個對象時被初始化 private static Random random = new Random(); private final int final_03 = random.nextInt(50); //使用隨機數來進行初始化 //引用 public final Person final_04 = new Person("chen_ssy"); //final指向引用數據類型 FinalTest(String final_02){ this.final_02 = final_02; } public String toString(){ return "final_01 = " + final_01 +" final_02 = " + final_02 + " final_03 = " + final_03 + " final_04 = " + final_04.getName(); } public static void main(String[] args) { System.out.println("------------第一次創建對象------------"); FinalTest final1 = new FinalTest("cm"); System.out.println(final1); System.out.println("------------第二次創建對象------------"); FinalTest final2 = new FinalTest("zj"); System.out.println(final2); System.out.println("------------修改引用對象--------------"); final2.final_04.setName("chenssy"); System.out.println(final2); }}------------------Output:------------第一次創建對象------------final_01 = chenssy final_02 = cm final_03 = 34 final_04 = chen_ssy------------第二次創建對象------------final_01 = chenssy final_02 = zj final_03 = 46 final_04 = chen_ssy------------修改引用對象--------------final_01 = chenssy final_02 = zj final_03 = 46 final_04 = chenssy
finally塊中的代碼無論有無異常都會執行。
異常也是多態的,Exception是所有異常的父類。
異??梢詔hrows 也可以 try/catch。
可以為每個不同的異常編寫不同的catch塊,但是要注意子類要放在父類之前catch
try{ // do somethings} catch (DogException de){ //deal DogException} catch (AnimalException ae){ //deal AnimalException}// 如果AnimalException的catch塊在DogException之前,那么DogException也會被AnimalException的catch塊捕獲,就不會落到后面的DogException的catch塊了。
內部類可以使用外部類的所有方法和變量,包括標記為private的。
內部類的實例一定會綁定在外部類的實例上。
內部類的其中一個適用場景:一個界面,需要監聽多個按鈕點擊事件并且不同按鈕的點擊事件有不同的響應。
IO這一章簡單地講了一下文件操作,著重講了序列化的內容。
其實這章序列化說得不好,建議看這里Java 序列化的高級認識
序列化程序會對象相關的所有東西都存儲起來,被對象的實例變量所引用的所有對象都會被實例化。
如果要讓類能夠被序列化,必須實現Serializable
如果某實例變量不能/不應該被序列化,就把他標記為瞬時的(transient),這樣序列化時候,程序會將它跳過。到了解序列化的時候這個引用變量會被置為null。
解序列化的時候,所有的類都必須讓JVM找到。
解序列化時,新的對象會被分配到堆上,但構造函數不會執行。
如果對象在繼承樹上的有不可序列化的祖先類,則該不可序列化類以及在它之上的類的構造函數(就算是可序列化的類)就會執行。從第一個不可序列化的父類開始,之上的類都會回到初始狀態。
靜態文件不會被序列化。
序列化標志:如果在解序列化之前,類已經發生了修改,可能會導致解序列化失敗。
會損害序列化的修改:
刪除實例變量
改變實例變量的類型
將非瞬時的實例變量改為瞬時的
改變繼承的繼承層次
將類從可序列化改為不可序列化
將實例變量變成靜態的
較為安全的修改:
加入新的實例變量(還原時取默認值)
在繼承層次中加入新的類
在繼承層次中刪除類
將實例變量從瞬時改為非瞬時
一致的序列化ID有利于保證反序列化的成功。
服務器與客戶端通過socket連接來溝通。
當ServerSocket接收到請求時,會在另外的一個端口做一個socket連接來處理客戶端的請求。
線程代表獨立的執行空間。
多線程同時執行時,實際上是多個線程隨機輪流執行的。
線程進入可執行狀態時,會在執行中和可執行這兩種狀態中切換。但是也可能進入堵塞狀態。堵塞狀態可能是閑置、等待其他縣城完成、等待串流數據、等待被占用的對象等原因引起的。
構造線程時需要傳入一個任務對象,這個任務對象需要實現Runnable接口。
并發:不同線程對同一個對象同時進行處理,可能引起問題。
鎖:要讓對象在線程上有足夠的安全性,就要對不可分割執行的指令上鎖(同步化)。
如果線程嘗試進入同步化的方法,必須取得對象的鑰匙如果鑰匙被別的線程拿走了,線程只能等待。
如果兩個線程互相持有對方正在等待執行的方法的鑰匙,就會發生死鎖。
常見的集合類型,有序的集合中的元素必須是可比較的,Comparable的:
TreeSet:有序且防止重復的集合。
HashMap:Key-Value集合,Key不可重復
HashSet:防止重復的集合,可快速找到相符元素
LinkedList:針對經常插入或者刪除中間元素所涉及的高效率集合(不如ArrayList實用)
LinkedHashMap:可記住元素插入順序,可設定依照原宿上次存儲先后來排序的HashMap。
ArrayList中的sort()方法,可進行已實現了Comparable接口的類的排序,也可以使用實現了Comparator接口的內部類來進行排序。
// 實現Comparable接口class Song inplements Comparable<Song>{ public int compareTo(Song s){ return title. compareTo(s.getTitle()); }}// 實現Comparatorclass Song{class ArtistCompare implements Comparator<Song>{ public int Compare (Song one, Song two){ return one.getArtist().compareTo(two.getArtist()); }ArtistCompare artistCompare = new ArtistCompare(); Collections.sort(songList, artistCompare);}}
如果equal()被覆蓋過,hashCode()方法也應該相應覆蓋。
equal()默認行為是執行==的比較,即判斷兩個引用變量是否引用堆中的同一個對象。如果equals()沒有被覆蓋過,那么兩個對象永遠不會被視為相等的。
a.equals(b)必須與a.hashCode()==b.hashCode()等值,但a.hashCode()==b.hashCode() 不一定與a.equals(b)等值。
//這里的list僅接受ArrayList<Animal>public void takeThing(ArrayList<Animal> list) //這里的list對象可以接受ArrayList<Dog>、ArrayList<Cat>等繼承Animal的對象的ArrayList// 泛型的extends等價于實體類的extends或者implementspublic <T extends Animal> void takeThing(ArrayList<T> list)//萬用字符也可以讓ArrayList接受Animal的子類// 使用萬用字符,能夠調用list中任何元素的方法,但是不能增加元素。public void takeAnimals(ArrayList<? extends Animal> animals){ for(Animal a:animals){ a.eat(); //合法 } animals.add(new Dog()); //不合法的操作}// 第二、第三種寫法執行起來是一樣的,但是在一般用第二種,因為需要傳入多個對象時,第二種方法不需要多次聲明public <T extends Animal> void takeThing(ArrayList<T> one, ArrayList<T> two)
遠端過程調用的過程:
啟動RMI registry
遠程服務被初始化(生成stub和skeleton)
遠程服務向RMI registry注冊
客戶端查詢RMI registry
客戶端從RMI registry獲取stub
客戶端調用stub上的方法
stub將方法的調用送到服務器上
啟動服務前應先啟動注冊器
遠程服務的參數和返回都需要做成可序列化
Jini--adaptive discovery:自動注冊、知道接口名稱就可以自動適配下發stub
Jini--self-healing networks:通過續約的方式確定服務的狀態,超過續約時間不進行續約就會任務該服務已離線。
數據隱藏:將成員變量標記為private,將getters、setters標記為public。
實例變量總會有默認值:無論有沒有明確賦值或者調用setter,實例變量總會有默認值。
integer --- 0
floating point --- 0.0
boolean --- false
reference --- null
==:使用==來比較兩個基本數據類型或判斷兩個引用變量是否指向同一個對象。==可以用來比較任何類型的兩個變量,因為它只是比較其中的字節組合。
equals():使用equals()來判斷兩個對象是否在意義上向相等
int a = 3;byte b =3;if(a == b){ // true}Foo c = new Foo();Foo d = new Foo();Foo e = c;if(c == d){// false}if(c == e){// true}if(c.equals(d)){// true}
x++與++x:x++先執行x+1,再執行賦值;++x先執行賦值,再執行x+1
長運算符(| &)與短運算符(|| &&):
在&&表達式中,左右兩邊都為true這個表達式返回true,當左邊返回false時,JVM不會執行右邊的計算就直接返回false;
在||表達式中,左右兩邊都為false這個表達式返回false,當左邊返回true時,JVM不會執行右邊的計算就直接返回true;
& 和 | 在boolean表達式會強制JVM執行兩邊的運算,但一般長表達是用在位運算中。
當一個類沒有構造方法時,編譯器再編譯時會默認加上一個無參的構造方法。但是如果一個類已有一個構造方法,則編譯器不會再加上無參的構造方法。
格式化說明
位運算:移位運算參考:JAVA移位操作符你真的懂嗎?
// 位非 ~int x = 10; //00001010x = ~x; //11110101// 位與 & 、位或 |、位異或 ^int x = 10; //00001010int x = 6; //00000110// 位與 &: 兩位都是1返回1,否則返回0int a =x&y; //00000010// 位或 |: 有一位為1就返回1,否則返回0int a =x&y; //00001110// 位異或 ^: 位相同返回1,否則返回0int a =x&y; //11110010// 移位運算 左移<<,右移>>,無符號右移>>>,需要結合數據類型來看。int x = -11; //11111011// 左移 1位,等于值*2,向左邊移動,并且在低位補0.int a= x<<1; //11110110// 右移1位,等于值/2,帶符號右移,若左操作數是正數,則高位補“0”,若左操作數是負數,則高位補“1”.int a= x>>1; //1111101// 無符號右移,無論左操作數是正數還是負數,在高位都補“0”int a= x>>>1; //0111101
不變性:String類型和包裝類都是不可變的。創建后就值就不可以改變。JVM中有一個String Pool,不受GC的影響。如果新建的String引用變量的值在String Pool中有相同值的對象,會直接引用這個對象,而不是新建一個String對象。
斷言:執行時沒有特殊設置JVM會自動忽略斷言,運行時打開JVM的斷言設置,則可以在不影響任意代碼的前提下進行除錯。
靜態嵌套類:可以在沒有外層實例的情況下使用的類。
public class Outer{ static class Inner{ void do(){ // do somethings } } class Test{ public static void main (String args[]){ Outer.Inner a = new Outer.Inner(); a.do(); } }}
非靜態的嵌套類是通常成為內部類。
匿名內部類:
button.addActionListen(new ActionListen{ public void actionPerformed(ActionEvent e){ // do somethings }})
default與protected:default-同一個包內可存取,protected-允許不同包的子類繼承它的成員。
多維數組:int[][] a2d = new int[4][2]>>這里由5個數組組成。
枚舉ENUM:Java 枚舉(enum) 詳解7種常見的用法