摘要:不相等的對象要具有不相等的哈希碼為了哈希表的操作效率,這一點很重要,但不是強制要求,最低要求是不相等的對象不能共用一個哈希碼。方法和方法協同工作,返回對象的哈希碼。這個哈希碼基于對象的身份生成,而不是對象的相等性。
本文面向
剛學完Java的新手們。這篇文章不講語法,而是一些除了語法必須了解的概念。
將要去面試的初級工程師們。查漏補缺,以免遭遇不測。
目前由于篇幅而被挪出本文的知識點:
淺析JVM之內存管理
Java多線程筆記
Java反射學習小記
簡談Java String
JDK,JRE和 JVM 的區別JVM:java 虛擬機,負責將編譯產生的字節碼轉換為特定機器代碼,實現一次編譯多處執行;
JRE:java運行時環境,包含了java虛擬機jvm,java基礎類庫。是使用java語言編寫的程序運行所需要的軟件環境;
JDK:java開發工具包,是編寫java程序所需的開發工具。JDK包含了JRE,同時還包含了編譯器javac,調試和分析工具,JavaDoc。
Java是如何編譯和執行的?上圖表示了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自帶的許多類往往都是:
對比一些簡單的屬性值
再對比復雜的屬性值or對比業務上最快能區分對象的值
再對比其他的值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方法
hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結構中確定對象的存儲地址的;
如果兩個對象相同,就是適用于equals(java.lang.Object) 方法,那么這兩個對象的hashCode一定要相同;
如果對象的equals方法被重寫,那么對象的hashCode也盡量重寫,并且產生hashCode使用的對象,一定要和equals方法中使用的一致,否則就會違反上面提到的第2點;
兩個對象的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
一個只有在運行時才能拋出異常,一個在編譯期便可以發現錯誤。
封裝類 Java中為什么要為基本類型提供封裝類呢?是為了在各種類型間轉化,通過各種方法的調用。否則你無法直接通過變量轉化。
比如,現在int要轉為String
int a=0; String result=Integer.toString(a);
比如現在要用泛型
Listnums;
這里< >里面就需要指定一個類。如果用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中的自動裝箱與拆箱
關于異常圖是我自己做的。如果覺得子類父類傻傻分不清,可以按照“紅橙黃綠”這個順序來,最高父類是紅。
遇上Error就是跪了,你就別想拯救了。
Exception一般由編碼、環境、用戶操作輸入出現問題,我們要可以捕捉的也處于這一塊兒。
運行時異常由java虛擬機由自己捕獲自己拋出。
檢查異常則由自己捕獲自己拋出多重catch,順序是從子類到父類。
異常拋出throw:將產生的異常拋出。交給上層去處理。異常鏈----A方法拋出異常,B方法嘗試捕獲。main中調用B,B捕獲的異常中會有A異常的信息。
throws:聲明將要拋出何種類型的異常。
下面是異常類族譜
捕捉的異常時,不要僅僅調用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 創建對象的幾種方式用new 語句創建對象,這是最常見的創建對象的方法
運用反射手段,調用 java.lang.Class 或者 java.lang.reflect.Constructor 類的 newInstance( ) 實例方法
調用對象的 clone( ) 方法
運用反序列化手段,調用 java.io.ObjectInputStream 對象的 readObject( ) 方法
(1)和(2)都會明確的顯式的調用構造函數;(3)是在內存上對已有對象的影印,所以不會調用構造函數 (4)是從文件中還原類的對象,也不會調用構造函數。
序列化(Serializable )與反序列化(Deserialize)對象序列化(Serializable)是指將對象轉換為字節序列的過程,而反序列化則是根據字節序列恢復對象的過程。
簡單的來說就是從object變成了byte,用于傳輸。
序列化一般用于以下場景:
永久性保存對象,保存對象的字節序列到本地文件中;
通過序列化對象在網絡中傳遞對象;
通過序列化在進程間傳遞對象。
只有實現了Serializable和Externalizable接口的類的對象才能被序列化。
小Tips:對子類對象進行反序列化操作時,如果其父類沒有實現序列化接口,那么其父類的構造函數會被顯式的調用。
java.io.ObjectOutputStream代表對象輸出流,它的writeObject(Objectobj)方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。
java.io.ObjectInputStream代表對象輸入流,它的readObject( )方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回。
覆蓋 (Override) 和重載 (Overload)Override:方法覆蓋是說子類重新定義了父類的方法,方法覆蓋必須有相同的方法名,參數列表和返回類型。一般會有個@Override注解。
Overload:Java中的方法重載發生在同一個類里面兩個或者是多個方法的方法名相同但是參數不同的情況
集合框架對象存入集合時會變成Object類型,取出時需要類型轉換。所以會有泛型(這樣也不用考慮取出時的類型轉換了)。另外集合里存儲的是引用,所以泛型不能使用基本類型。
常見集合
集合概覽
集合家族一覽
Set 是一種 Collection,不過其中沒有重復的對象;List 也是一種 Collection,其中的元素按順序排列(不過可能有重復)。
SortedSet 和 SortedMap 是特殊的集和映射,其中的元素按順序排列。
Collection、Set、List、Map、SortedSet 和 SortedMap 都是接口,不過 java.util 包定義了多個具體實現,例如基于數組和鏈表的列表,基于哈希表或二叉樹的映射和集。除此之外,還有兩個重要的接口:Iterator 和 Iterable,用于遍歷集合中的對象。
Collection接口Collection
集(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
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 包中定義。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65681.html
摘要:特點高性能且易于使用,旨在實現簡單并快速的事務提交與回滾。大部分項目只需要少量的配置即可地址介紹是開源的診斷工具。當然,它們的重點是,和。 該文已加入筆主的開源項目——JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識的文檔類項目),地址:https://github.com/Snailclimb... 。覺得不錯的話,記得點個Star。 1. JavaGuide ...
摘要:最近瀏覽,收藏了一些還算不錯的面試學習相關的倉庫,分享給大家,希望對你有幫助。除了這九個倉庫,再推薦幾個不錯的學習方向的倉庫給大家。數高達的筆記后端尤其是程序員的學習倉庫兩個算法相關的倉庫,刷的小伙伴必備 最近瀏覽 Github ,收藏了一些還算不錯的 Java面試/學習相關的倉庫,分享給大家,希望對你有幫助。我暫且按照目前的 Star 數量來排序。 本文由 SnailClimb 整理...
摘要:為了防止這種現象,我們可以對字節碼進行混淆。動態鏈接庫是目標文件的集合,目標文件在動態鏈接庫中的組織方式是按照特殊方式形成的。 一、已知防護策略 1.不可或缺的混淆 Java 是一種跨平臺、解釋型語言,Java 源代碼編譯成的class文件中有大量包含語義的變量名、方法名的信息,很容易被反編譯為Java 源代碼。為了防止這種現象,我們可以對Java字節碼進行混淆。混淆不僅能將代碼中的類...
摘要:在中,拖放是標準的一部分,任何元素都能夠拖放。如果需要設置允許放置,我們必須阻止對元素的默認處理方式方法。 系列文章 關于前端上傳文件全面基礎掃盲貼(零)關于前端上傳文件全面基礎掃盲貼(一) ----- XMLHttpRequest關于前端上傳文件全面基礎掃盲貼(二) ----- File關于前端上傳文件全面基礎掃盲貼(三) ----- FormData關于前端上傳文件全面基礎掃盲貼(...
摘要:為了解救上面說到的問題是向提交的一個草案,旨在推出一套標準的,其基本功能是實現用對本地文件進行操作。出于安全性的考慮,該只對本地文件提供有限的訪問。 系列文章 關于前端上傳文件全面基礎掃盲貼(零)關于前端上傳文件全面基礎掃盲貼(一) ----- XMLHttpRequest關于前端上傳文件全面基礎掃盲貼(二) ----- File關于前端上傳文件全面基礎掃盲貼(三) ----- For...
閱讀 2473·2021-11-16 11:45
閱讀 2442·2021-10-11 10:59
閱讀 2250·2021-10-08 10:05
閱讀 3813·2021-09-23 11:30
閱讀 2369·2021-09-07 09:58
閱讀 789·2019-08-30 15:55
閱讀 772·2019-08-30 15:53
閱讀 1922·2019-08-29 17:00