Java快速掃盲指南

本文面向html

  • 剛學完Java的新手們。這篇文章不講語法,而是一些除了語法必須瞭解的概念。java

  • 將要去面試的初級工程師們。查漏補缺,以避免遭遇不測。程序員

目前因爲篇幅而被挪出本文的知識點:面試

JDK,JRE和 JVM 的區別

  • JVM:java 虛擬機,負責將編譯產生的字節碼轉換爲特定機器代碼,實現一次編譯多處執行;緩存

  • JRE:java運行時環境,包含了java虛擬機jvm,java基礎類庫。是使用java語言編寫的程序運行所須要的軟件環境;安全

  • JDK:java開發工具包,是編寫java程序所需的開發工具。JDK包含了JRE,同時還包含了編譯器javac,調試和分析工具,JavaDoc。

Java是如何編譯和執行的?

JavaCompileAndExcuteStep.png

上圖表示了Java代碼是怎麼編譯和加載的

整個流程從 Java 源碼開始,通過 javac 程序處理後獲得類文件,這個文件中保存的是編譯源碼後獲得的 Java 字節碼。類文件是 Java 平臺能處理的最小功能單位,也是把新代碼傳給運行中程序的惟一方式。

新的類文件經過類加載機制載入虛擬機,從而把新類型提供給解釋器執行。

Object的重要方法

全部類都直接或間接擴展 java.lang.Object 類。這個類定義了不少有用的方法,並且你能夠根據需求來重寫這些方法。

toString( )方法

toString( ) 方法的做用是返回對象的文本表示形式。鏈接字符串或使用 System.out.println( ) 等方法時,會自動在對象上調用這個方法。給對象提供文本表示形式,十分利於調試或記錄日誌,並且精心編寫的 toString( ) 方法還能給報告生成等任務提供幫助。

Object 類中的 toString( ) 方法返回的字符串由對象所屬的類名和對象的十六進制形式哈希碼(由 hashCode( ) 方法計算獲得,本章節稍後會介紹)組成。這個默認的實現方式提供了對象的類型和標識兩個基本信息,但通常並沒什麼用。

equals( )方法

== 運算符測試兩個引用是否指向同一個對象(比較兩個內存單元的內容是否同樣)。若是要測試兩個不一樣的對象是否相等,必須使用 equals( ) 方法。任何類都能覆蓋 equals( ) 方法,定義專用的相等比較方式。Object.equals( ) 方法直接使用 == 運算符,只有兩個對象是同一個對象時,才斷定兩者相等。

不少類以及自定義類的equals方法都須要重寫,是須要根據場景與需求來定製的。JDK自帶的許多類每每都是:

  1. 對比一些簡單的屬性值

  2. 再對比複雜的屬性值or對比業務上最快能區分對象的值

  3. 再對比其餘的值or對比地址、長度

主要爲了將那些不匹配的狀況儘快排除

hashCode( )方法

Java中的hashCode方法就是根據必定的規則將與對象相關的信息(好比對象的存儲地址,對象的字段等)映射成一個數值,這個數值稱做爲散列值。 若是集合中已經存在一萬條數據或者更多的數據,若是採用equals方法去逐一比較,效率必然是一個問題。此時hashCode方法的做用就體現出來了,當集合要添加新的對象時,先調用這個對象的hashCode方法,獲得對應的hashcode值,實際上在HashMap的具體實現中會用一個table保存已經存進去的對象的hashcode值,若是table中沒有該hashcode值,它就能夠直接存進去,不用再進行任何比較了;若是存在該hashcode值,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址,因此這裏存在一個衝突解決的問題,這樣一來實際調用equals方法的次數就大大下降了。

另外注意,默認的hashCode會發起native調用,若是用hashCode對兩個對象對比,會致使開銷增大。
hashcode方法的做用

只要覆蓋了 equals( ) 方法,就必須覆蓋 hashCode( ) 方法。hashCode( ) 方法返回一個整數,用於哈希表數據結構。若是兩個對象經 equals( ) 方法測試是相等的,它們就要具備相同的哈希碼。不相等的對象要具備不相等的哈希碼(爲了哈希表的操做效率),這一點很重要,但不是強制要求,最低要求是不相等的對象不能共用一個哈希碼。爲了知足最低要求,hashCode( ) 方法要使用稍微複雜的算法或位操做。

Object.hashCode( ) 方法和 Object.equals( ) 方法協同工做,返回對象的哈希碼。這個哈希碼基於對象的身份生成,而不是對象的相等性。(若是須要使用基於身份的哈希碼,能夠經過靜態方法 System.identityHashCode( ) 獲取 Object.hashCode( ) 方法的返回值。)

hashCode和equal方法

  1. hashCode的存在主要是用於查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結構中肯定對象的存儲地址的;

  2. 若是兩個對象相同,就是適用於equals(java.lang.Object) 方法,那麼這兩個對象的hashCode必定要相同;

  3. 若是對象的equals方法被重寫,那麼對象的hashCode也儘可能重寫,而且產生hashCode使用的對象,必定要和equals方法中使用的一致,不然就會違反上面提到的第2點;

  4. 兩個對象的hashCode相同,並不必定表示兩個對象就相同,也就是不必定適用於equals(java.lang.Object)方法,只可以說明這兩個對象在散列存儲結構中,如Hashtable,他們"存放在同一個籃子裏"。

    HashCode和equal方法

Comparable::compareTo( )方法

若是一個類實現了 Comparable 接口,就能夠比較一個實例是小於、大於仍是等於另外一個實例。這也代表,實現 Comparable 接口的類能夠排序。

由於 compareTo( ) 方法不在 Object 類中聲明,因此由每一個類自行決定實例可否排序。若是能排序就定義 compareTo( ) 方法,實現實例排序的方式。

compareTo( ) 方法返回一個 int 類型的值,這個值須要進一步說明。若是當前對象(this)小於傳入的對象,compareTo( ) 方法應該返回一個負數;若是兩個對象相等,應該返回 0;若是當前對象大於傳入的對象,應該返回一個正數。

clone( )方法

Object 類定義了一個名爲 clone( ) 的方法,這個方法的做用是返回一個對象,並把這個對象的字段設爲和當前對象同樣。clone( ) 方法不經常使用,緣由有兩個。其一,只有類實現了 java.lang.Cloneable 接口,這個方法纔有用。Cloneable 接口沒有定義任何方法(是個標記接口),所以若想實現這個接口,只需在類簽名的 implements 子句中列出這個接口便可。其二,clone( ) 方法聲明爲 protected,所以,若是想讓其餘類複製你的對象,你的類必須實現 Cloneable 接口,並覆蓋 clone( ) 方法,並且要把 clone( ) 方法聲明爲 public。

clone( ) 方法很難正確實現,而副本構造方法實現起來更容易也更安全。

finalize( )方法

一種古老的資源管理技術叫終結(finalization),開發者應該知道有這麼一種技術。然而,這種技術幾乎徹底廢棄了,任何狀況下,大多數 Java 開發者都不該該直接使用。

只有少數應用場景適合使用終結,並且只有少數 Java 開發者會遇到這種場景。若是有任何疑問,就不要使用終結,處理資源的 try 語句每每是正確的替代品。

終結機制的做用是自動釋放再也不使用的資源。垃圾回收自動釋放的是對象使用的內存資源,不過對象可能會保存其餘類型的資源,例如打開的文件和網絡鏈接。垃圾回收程序不會爲你釋放這些額外的資源,所以,終結機制的做用是讓開發者執行清理任務,例如關閉文件、中斷網絡鏈接、刪除臨時文件,等等。

終結機制的工做方式是這樣的:若是對象有 finalize( ) 方法(通常叫做終結方法),那麼再也不使用這個對象(或對象不可達)後的某個時間會調用這個方法,但要在垃圾回收程序回收分配給這個對象的空間以前調用。終結方法用於清理對象使用的資源。

另外注意,這是一個實例方法。而在類上,沒有等效的機制。

引用類型與基本類型比較

type which
基礎 byte short int long float double char boolean
引用 數組 對象

8種基本類型對應的包裝類也是被final修飾。另外,String類和StringBuffer類也是被final修飾的。

引用類型和對象與基本類型和基本值有本質的區別。

  • 八種基本類型由 Java 語言定義,程序員不能定義新基本類型。引用類型由用戶定義,所以有無限多個。例如,程序能夠定義一個名爲 Point 的類,而後使用這個新定義類型的對象存儲和處理笛卡兒座標系中的 (x, y) 點。

  • 基本類型表示單個值。引用類型是聚合類型(aggregate type),能夠保存零個或多個基本值或對象。例如,咱們假設的 Point 類可能存儲了兩個 double 類型的值,表示點的 x 和 y 座標。char[ ] 和 Point[ ] 數組類型是聚合類型,由於它們保存一些 char 類型的基本值或 Point 對象。

  • 基本類型須要一到八個字節的內存空間。把基本值存儲到變量中,或者傳入方法時,計算機會複製表示這個值的字節。而對象基本上須要更多的內存。建立對象時會在堆(heap)中動態分配內存,存儲這個對象;若是再也不須要使用這個對象了,存儲它的內存會被自動垃圾回收。

把對象賦值給變量或傳入方法時,不會複製表示這個對象的內存,而是把這個內存的引用存儲在變量中或傳入方法。

在 Java 中,引用徹底不透明,引用的表示方式由 Java 運行時的實現細節決定。若是你是 C 程序員的話,徹底能夠把引用看做指針或內存地址。不過要記住,Java 程序沒法使用任何方式處理引用。

彷佛看的有點暈?來點兒代碼吧!

下述代碼處理 int 類型基本值:

int x = 42;
int y = x;

執行這兩行代碼後,變量 y 中保存了變量 x 中所存值的一個副本。在 Java 虛擬機內部,這個 32 位整數 42 有兩個獨立的副本。

如今,想象一下把這段代碼中的基本類型換成引用類型後再運行會發生什麼:

Point p = new Point(1.0, 2.0);
Point q = p;

運行這段代碼後,變量 q 中保存了一份變量 p 中所存引用的一個副本。在虛擬機中,仍然只有一個 Point 對象的副本,可是這個對象的引用有兩個副本----這一點有重要的含義。假設上面兩行代碼的後面是下述代碼:

System.out.println(p.x);  // 打印p的x座標:1.0
q.x = 13.0;               // 如今,修改q的x座標
System.out.println(p.x);  // 再次打印p.x,此次獲得的值是13.0

由於變量 p 和 q 保存的引用指向同一個對象,因此兩個變量均可以用來修改這個對象,並且一個變量中的改動在另外一個變量中可見。數組也是一種對象,因此對數組來講也會發生一樣的事,以下面的代碼所示:

// greet保存一個數組的引用
char[ ] greet = { 'h','e','l','l','o' };
char[ ] cuss = greet;             // cuss保存的是同一個數組的引用
cuss[4] = '!';                   // 使用引用修改一個元素
System.out.println(greet);       // 打印「hell!」

把基本類型和引用類型的參數傳入方法時也有相似的區別。假若有下面的方法:

void changePrimitive(int x) {
    while(x > 0) {
        System.out.println(x--);
    }
}

調用這個方法時,會把實參的副本傳給形參 x。在這個方法的代碼中,x 是循環計數器,向零遞減。由於 x 是基本類型,因此這個方法有這個值的私有副本——這是徹底合理的作法。

但是,若是把這個方法的參數改成引用類型,會發生什麼呢?

void changeReference(Point p) {
    while(p.x > 0) {
        System.out.println(p.x--);
    }
}

調用這個方法時,傳入的是一個 Point 對象引用的私有副本,而後使用這個引用修改對應的 Point 對象。例如,有下述代碼:

Point q = new Point(3.0, 4.5); // 一個x座標爲3的點
changeReference(q);            // 打印3,2,1,並且修改了這個Point對象
System.out.println(q.x);       // 如今,q的x座標是0!

調用 changeReference( ) 方法時,傳入的是變量 q 中所存引用的副本。如今,變量 q 和方法的形參 p 保存的引用指向同一個對象。這個方法可使用它的引用修改對象的內容。可是要注意,這個方法不能修改變量 q 的內容。也就是說,這個方法能夠隨意修改引用的 Point 對象,但不能改變變量 q 引用這個對象這一事實。

那麼在用運算符:==時,也會有差異。

相等運算符(==)比較基本值時,只測試兩個值是否同樣(即每一位的值都徹底相同)。而 == 比較引用類型時,比較的是引用而不是真正的對象。也就是說,== 測試兩個引用是否指向同一個對象,而不測試兩個對象的內容是否相同。

Java 的四種引用

在 JDK1.2 後,Java 對引用概念擴充,分爲強引用、軟引用、弱引用、虛引用。強度漸弱。

在開始瞭解前,最好先稍微瞭解一下Java Memory Model。個人這篇文章中簡單的講了一下JMM

強引用

就是值在程序代碼之中廣泛存在的,相似 Object obj = new Object() 這類的引用,只要強引用還在,垃圾收集器永遠不會回收掉被引用的對象。

軟引用

它關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍內進行第二次回收。提供 SoftReference 類來實現軟引用。

弱引用

強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。提供 WeakReference 類來實現軟引用。

虛引用

一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來去的一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。提供 PhantomReference 類來實現軟引用。

Java 7之基礎 - 強引用、弱引用、軟引用、虛引用

Java垃圾回收機制與引用類型

神奇的數組

數組類型不是類,但數組實例是對象。這意味着,數組從 java.lang.Object 類繼承了方法。數組實現了 Cloneable 接口,並且覆蓋了 clone( ) 方法,確保數組始終能被複制,並且 clone( ) 方法從不拋出 CloneNotSupportedException 異常。數組還實現了 Serializable 接口,因此只要數組中元素的類型能被序列化,數組就能被序列化。並且,全部數組都有一個名爲 length 的字段,這個字段的修飾符是 public final int,表示數組中元素的數量。

由於數組擴展自 Object 類,並且實現了 Cloneable 和 Serializable 接口,因此任何數組類型都能放大轉換成這三種類型中的任何一種。並且,特定的數組類型還能放大轉換成其餘數組類型。若是數組中的元素類型是引用類型 T,並且 T 能指定給類型 S,那麼數組類型 T[ ] 就能指定給數組類型 S[ ]。注意,基本類型的數組不能放大轉換。例如,下述代碼展現了合法的數組放大轉換:

String[ ] arrayOfStrings;      // 建立字符串數組
int[ ][ ] arrayOfArraysOfInt;   // 建立int二維數組
Object[ ] oa = arrayOfStrings;// String能夠指定給Object,所以String[ ]能夠指定給Object[ ]
Comparable[ ] ca = arrayOfStrings;// String實現了Comparable接口,所以String[ ]能夠視做Comparable[ ]
Object[ ] oa2 = arrayOfArraysOfInt;// int[ ]是Object類的對象,所以int[ ][ ]能夠指定給Object[ ]
// 全部數組都是能夠複製和序列化的對象
Object o = arrayOfStrings;
Cloneable c = arrayOfArraysOfInt;
Serializable s = arrayOfArraysOfInt[0];

由於數組類型能夠放大轉換成另外一種數組類型,因此編譯時和運行時數組的類型並不老是同樣。這種放大轉換叫做"數組協變"(array covariance)。

因此在某種意義上,集合框架比數組好用:

Object [] objectArray = new Long[1];
objectArray[0] = "I dont fit in"; //Throws ArrayStoreException
List<Object> ol = new ArrayList<Long>(); //Incompatible types
ol.add("I dont fit in");

一個只有在運行時才能拋出異常,一個在編譯期即可以發現錯誤。

封裝類

Java中爲何要爲基本類型提供封裝類呢?

是爲了在各類類型間轉化,經過各類方法的調用。不然你沒法直接經過變量轉化。

好比,如今int要轉爲String

int a=0;
String result=Integer.toString(a);

好比如今要用泛型

List<Integer> nums;

這裏< >裏面就須要指定一個類。若是用int,則報錯。

自動裝箱(autoboxing)與拆箱(unboxing)

自動裝箱是 Java 編譯器在基本數據類型和對應的對象包裝類型之間作的一個轉化。

基本類型和引用類型的表現徹底不一樣。有時須要把基本值當成對象,爲此,Java 平臺爲每一種基本類型都提供了包裝類。Boolean、Byte、Short、Character、Integer、Long、Float 和 Double 是不可變的最終類,每一個實例只保存一個基本值。包裝類通常在把基本值存儲在集合中時使用。 例如

java.util.List:
List numbers =newArrayList( );// 建立一個List集合
numbers.add(newInteger(-1));// 存儲一個包裝類表示的基本值
int i =((Integer)numbers.get(0)).intValue( );// 取出這個基本值

把 int 轉化成 Integer,double 轉化成 Double等,反之就是自動拆箱。

Integer  a=1;//這就是一個自動裝箱,若是沒有自動裝箱的話,須要這樣Integer  a=new Integer(1)  
int b=a;//這就是一個自動拆箱,若是沒有自動拆箱的話,須要這樣:int b=a.intValue( )

這樣就能看出自動裝箱和自動拆箱是簡化了基本數據類型和相對應對象的轉化步驟。

自動拆裝箱將會致使性能問題,由於有些數字不屬於緩存範圍——意味着會產生新的對象,尤爲是在集合框架中會嚴重致使性能降低。

請運行一下下面的代碼,並探究一下:

public static void main(String []args){
    Integer a = 1;
    Integer b = 1;
    Integer c = 200;
    Integer d = 200;
    System.out.println(a==b);
    System.out.println(c==d);
}

Java中的自動裝箱與拆箱

關於異常

JavaThrowable.jpg

圖是我本身作的。若是以爲子類父類傻傻分不清,能夠按照「紅橙黃綠」這個順序來,最高父類是紅。

  • 趕上Error就是跪了,你就別想拯救了。

  • Exception通常由編碼、環境、用戶操做輸入出現問題,咱們要能夠捕捉的也處於這一起。

  • 運行時異常由java虛擬機由本身捕獲本身拋出。

  • 檢查異常則由本身捕獲本身拋出多重catch,順序是從子類到父類。

異常拋出

  • throw:將產生的異常拋出。交給上層去處理。異常鏈----A方法拋出異常,B方法嘗試捕獲。main中調用B,B捕獲的異常中會有A異常的信息。

  • throws:聲明將要拋出何種類型的異常。

下面是異常類族譜

ThrowableSystem.png

捕捉的異常時,不要僅僅調用printStackTreace( )去打印輸出,應該添加事務回滾等操做。catch(Exception)能夠捕捉遺漏的異常。最後在finally語句裏記得釋放資源。

Java異常處理的10個最佳實踐

這裏是我收集的 Java 編程中異常處理的 10 個最佳實踐。你們對 Java 中的受檢異常(checked Exception)褒貶不一,這種語言特性要求該異常必須被處理。在本文中,咱們儘量少使用受檢異常,同時也要學會在 Java 編程中,區別使用受檢和非受檢異常。

1 爲可恢復的錯誤使用受檢異常,爲編程錯誤使用非受檢異常。

對 Java 開發者來講,選擇受檢仍是非受檢異常老是讓人感到困惑。受檢異常保證你會針對錯誤狀況提供異常處理代碼,這是一種從語言層面上強制你編寫健壯代碼的一種方式,但同時也引入大量雜亂的代碼並致使其可讀性變差。固然,若是你有可替代方式或恢復策略的話,捕獲異常並作處理看起來彷佛也合情合理。在 Java 編程中選擇受檢異常仍是運行時異常的更多信息,請參考 checked vs unchecked exceptions。

2 在 finally 程序塊中關閉或者釋放資源

這是 Java 編程中一個廣爲人知的最佳實踐和一個事實上的標準,尤爲是在處理網絡和 IO 操做的時候。在 finally 塊中關閉資源能保證不管是處於正常仍是異常執行的狀況下,資源文件都能被合理釋放,這由 finally 語句塊保證。從 Java7 開始,新增長了一項更有趣的功能:自動資源管理,或者稱之爲ARM塊。儘管如此,咱們仍然要記住在 finally 塊中關閉資源,這對於釋放像 FileDescriptors 這類資源相當重要,由於它在 socket 和文件操做中都會被用到。

3 在堆棧信息中包含引發異常的緣由

Java 庫和開源代碼在不少狀況下會將一種異常包裝成另外一種異常。這樣記錄和打印根異常就變得很是重要。Java 異常類提供了 getCause() 方法來獲取致使異常的緣由,這能夠提供更多有關異常發生的根本緣由的信息。這條實踐對調試或排除故障大有幫助。在把一個異常包裝成另外一種異常時,記住須要把源異常傳遞給新異常的構造器。

4 始終提供異常的有意義的完整信息

異常信息是最重要的,在其中,你能找到問題產生的緣由,由於這是出問題後程序員最早看到的地方。記得始終提供精確的真實的信息。例如,對比下面兩條 IllegalArgumentException 的異常信息:

message 1: 「Incorrect argument for method」 message 2: 「Illegal value for ${argument}: ${value}

第一條消息僅說明了參數是非法的或不正確的,但第二條消息包括了參數名和非法值,這對找到錯誤緣由很重要。在編寫異常處理代碼的時候,應當始終遵循該 Java 最佳實踐。

5 避免過分使用受檢異常

受檢異常的強制性在某種程度上具備必定的優點,但同時它也使得代碼可讀性變差,混淆了正常的業務邏輯代碼。你能夠經過適度使用受檢異常來最大限度地減小這類狀況的發生,這樣能夠獲得更簡潔的代碼。你一樣可使用 Java7 的新功能,好比在一個catch語句中捕獲多個異常,以及自動管理資源,以此來移除一些冗餘的代碼。

6 將受檢異常轉爲運行時異常

這是在諸如 Spring 之類的框架中用來減小使用受檢異常的方式之一,大部分 JDBC 的受檢異常都被包裝進 DataAccessException 中,DataAccessException異常是一種非受檢異常。這個最佳實踐帶來的好處是能夠將特定的異常限制到特定的模塊中,好比把 SQLException 拋到 DAO 層,把有意義的運行時異常拋到客戶端層。

7 記住異常的性能代價高昂

須要記住的一件事是異常代價高昂,同時讓代碼運行緩慢。假如你有一個方法從 ResultSet 中進行讀取,它常常會拋出 SQLException 而不是將 cursor 移到下一元素,這將會比不拋出異常的正常代碼執行的慢的多。所以最大限度的減小沒必要要的異常捕捉,去修復真正的根本問題。不要僅僅是拋出和捕捉異常,若是你能使用 boolean 變量去表示執行結果,可能會獲得更整潔、更高性能的解決方案。修正錯誤的根源,避免沒必要要的異常捕捉。

8 避免空的 catch 塊

沒有什麼比空的 catch 塊更糟糕的了,由於它不只隱藏了錯誤和異常,同時可能致使你的對象處於不可用狀態或者髒狀態。空的 catch 塊沒有意義,除非你很是確定異常不會以任何方式影響對象的狀態,但在程序執行期間,用日誌記錄錯誤依然是最好的方法。這在 Java 異常處理中不只僅是一個最佳實踐,並且是一個最通用的實踐。

9 使用標準異常

第九條最佳實踐是建議使用標準和內置的 Java 異常。使用標準異常而不是每次建立咱們本身的異常,這對於目前和之後代碼的可維護性和一致性,都是最好的選擇。重用標準異常使代碼可讀性更好,由於大部分 Java 開發人員對標準的異常更加熟悉,好比 JDK 中的RuntimeException,IllegalStateException,IllegalArgumentException,NullPointerException,他們能立馬知道每種異常的目的,而不是在代碼或文檔裏查找用戶自定義異常的目的。

10 爲方法拋出的異常編寫文檔

Java 提供了 throw 和 throws 關鍵字來拋出異常,在 javadoc 中能夠用@throw 爲任何可能被拋出的異常編寫文檔。若是你編寫 API 或者公共接口,這就變得很是重要。當任何方法拋出的異常都有相應的文檔記錄時,就能潛在的提醒任何調用該方法的開發者。

Java 建立對象的幾種方式

  1. 用new 語句建立對象,這是最多見的建立對象的方法

  2. 運用反射手段,調用 java.lang.Class 或者 java.lang.reflect.Constructor 類的 newInstance( ) 實例方法

  3. 調用對象的 clone( ) 方法

  4. 運用反序列化手段,調用 java.io.ObjectInputStream 對象的 readObject( ) 方法

  5. (1)和(2)都會明確的顯式的調用構造函數;(3)是在內存上對已有對象的影印,因此不會調用構造函數 (4)是從文件中還原類的對象,也不會調用構造函數。

序列化(Serializable )與反序列化(Deserialize)

對象序列化(Serializable)是指將對象轉換爲字節序列的過程,而反序列化則是根據字節序列恢復對象的過程。

簡單的來講就是從object變成了byte,用於傳輸。

序列化通常用於如下場景:

  1. 永久性保存對象,保存對象的字節序列到本地文件中;

  2. 經過序列化對象在網絡中傳遞對象;

  3. 經過序列化在進程間傳遞對象。

只有實現了Serializable和Externalizable接口的類的對象才能被序列化。

小Tips:對子類對象進行反序列化操做時,若是其父類沒有實現序列化接口,那麼其父類的構造函數會被顯式的調用。

java.io.ObjectOutputStream表明對象輸出流,它的writeObject(Objectobj)方法可對參數指定的obj對象進行序列化,把獲得的字節序列寫到一個目標輸出流中。

java.io.ObjectInputStream表明對象輸入流,它的readObject( )方法從一個源輸入流中讀取字節序列,再把它們反序列化爲一個對象,並將其返回。

覆蓋 (Override) 和重載 (Overload)

  • Override:方法覆蓋是說子類從新定義了父類的方法,方法覆蓋必須有相同的方法名,參數列表和返回類型。通常會有個@Override註解。

  • Overload:Java中的方法重載發生在同一個類裏面兩個或者是多個方法的方法名相同可是參數不一樣的狀況

集合框架

對象存入集合時會變成Object類型,取出時須要類型轉換。因此會有泛型(這樣也不用考慮取出時的類型轉換了)。另外集合裏存儲的是引用,因此泛型不能使用基本類型。

常見集合
CollectionsFrameWork.png

集合概覽
AllConllection.jpg

集合家族一覽
CollectionsFamily.png

  • Set 是一種 Collection,不過其中沒有重複的對象;List 也是一種 Collection,其中的元素按順序排列(不過可能有重複)。

  • SortedSet 和 SortedMap 是特殊的集和映射,其中的元素按順序排列。

  • Collection、Set、List、Map、SortedSet 和 SortedMap 都是接口,不過 java.util 包定義了多個具體實現,例如基於數組和鏈表的列表,基於哈希表或二叉樹的映射和集。除此以外,還有兩個重要的接口:Iterator 和 Iterable,用於遍歷集合中的對象。

Collection接口

Collection<e> 接口是參數化接口,表示由泛型 E 對象組成的集合。這個接口定義了不少方法,用來把對象添加到集合中,把對象從集合中移除,測試對象是否在集合中,以及遍歷集合中的全部元素。還有一些方法能夠把集合中的元素轉換成數組,以及返回集合的大小。

Set接口

集(set)是無重複對象組成的集合:不能有兩個引用指向同一個對象,或兩個指向 null 的引用,若是對象 a 和 b 的引用知足條件 a.equals(b),那麼這兩個對象也不能同時出如今集中。多數通用的 Set 實現都不會對元素排序,但並不由止使用有序集(SortedSet 和 LinkedHashSet 就有順序)。並且集與列表等有序集合不一樣,通常認爲,集的 contains 方法,不論以常數時間仍是以對數時間都爲1,運行效率都高。

List接口

List 是一組有序的對象集合。列表中的每一個元素都有特定的位置,並且 List 接口定義了一些方法,用於查詢或設定特定位置(或叫索引)的元素。從這個角度來看,List 對象和數組相似,不過列表的大小能按需變化,以適應其中元素的數量。和集不一樣,列表容許出現重複的元素。

除了基於索引的 get( ) 和 set( ) 方法以外,List 接口還定義了一些方法,用於把元素添加到特定的索引,把元素從特定的索引移除,或者返回指定值在列表中首次出現或最後出現的索引。從 Collection 接口繼承的 add( ) 和 remove( ) 方法,前者把元素添加到列表末尾,後者把指定值從列表中首次出現的位置移除。繼承的 addAll( ) 方法把指定集合中的全部元素添加到列表的末尾,或者插入指定的索引。retainAll( ) 和 removeAll( ) 方法的表現與其餘 Collection 對象同樣,若是須要,會保留或刪除多個相同的值。

List 接口沒有定義操做索引範圍的方法,可是定義了一個 subList( ) 方法。這個方法返回一個 List 對象,表示原列表指定範圍內的元素。子列表會回饋父列表,只要修改了子列表,父列表當即就能察覺到變化。

Map接口

映射(map)是一系列鍵值對,一個鍵對應一個值。Map 接口定義了用於定義和查詢映射的 API。Map 接口屬於 Java 集合框架,但沒有擴展 Collection 接口,所以 Map 只是一種集合,而不是 Collection 類型。Map 是參數化類型,有兩個類型變量。類型變量 K 表示映射中鍵的類型,類型變量 V 表示鍵對應的值的類型。例如,若是有個映射,其鍵是 String 類型,對應的值是 Integer 類型,那麼這個映射能夠表示爲 Map<string,integer>

Map 接口定義了幾個最有用的方法:put( ) 方法定義映射中的一個鍵值對,get( ) 方法查詢指定鍵對應的值,remove( ) 方法把指定的鍵及對應的值從映射中刪除。通常來講,實現 Map 接口的類都要能高效執行這三個基本方法:通常應該運行在常數時間中,並且毫不能比在對數時間中運行的性能差。

Map 的重要特性之一是,能夠視做集合。雖然 Map 對象不是 Collection 類型,但映射的鍵能夠當作 Set 對象,映射的值能夠當作 Collection 對象,而映射的鍵值對能夠當作由 Map.Entry 對象組成的 Set 對象。(Map.Entry 是 Map 接口中定義的嵌套接口,表示一個鍵值對。)

Queue接口和BlockingQueue接口

隊列(queue)是一組有序的元素,提取元素時按順序從隊頭讀取。隊列通常按照插入元素的順序實現,所以分紅兩類:先進先出(first-in, first-out,FIFO)隊列和後進先出(last-in, first-out,LIFO)隊列。

LIFO 隊列也叫棧(stack),Java 提供了 Stack 類,但強烈不建議使用,應該使用實現 Deque 接口的類。

隊列也可使用其餘順序:優先隊列(priority queue)根據外部 Comparator 對象或 Comparable 類型元素的天然順序排序元素。與 Set 不一樣的是,Queue 的實現每每容許出現重複的元素。而與 List 不一樣的是,Queue 接口沒有定義處理任意索引位元素的方法,只有隊列的頭一個元素能訪問。Queue 的全部實現都要具備一個固定的容量:隊列已滿時,不能再添加元素。相似地,隊列爲空時,不能再刪除元素。不少基於隊列的算法都會用到滿和空這兩個狀態,因此 Queue 接口定義的方法經過返回值代表這兩個狀態,而不會拋出異常。具體而言,peek( ) 和 poll( ) 方法返回 null 表示隊列爲空。所以,多數 Queue 接口的實現不容許用 null 做元素。

阻塞式隊列(blocking queue)是一種定義了阻塞式 put( ) 和 take( ) 方法的隊列。put( ) 方法的做用是把元素添加到隊列中,若是須要,這個方法會一直等待,直到隊列中有存儲元素的空間爲止。而 take( ) 方法的做用是從隊頭移除元素,若是須要,這個方法會一直等待,直到隊列中有元素可供移除爲止。阻塞式隊列是不少多線程算法的重要組成部分,所以 BlockingQueue 接口(擴展 Queue 接口)在 java.util.concurrent 包中定義。

相關文章
相關標籤/搜索