Java快速掃盲指南

文章轉自:https://segmentfault.com/a/1190000004817465#articleHeader22

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自帶的許多類往往都是:

  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( ) 方法(一般叫作終結方法),那么不再使用這個對象(或對象不可達)后的某個時間會調用這個方法,但要在垃圾回收程序回收分配給這個對象的空間之前調用。終結方法用于清理對象使用的資源。

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

引用類型與基本類型比較

typewhich
基礎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。

強引用

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

軟引用

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

弱引用

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

虛引用

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

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

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

String

String在Java中算是一個有意思的類型,是final類型,因此不可以繼承這個類、不能修改這個類。為了提高效率節省空間,我們常常會使用StringBuffer類。

那么這里先談談String的特性,然后再說StringBuffer。先來段代碼:

String s = "Hello";
s = s + " world!";

問題是:這兩行代碼執行后,原始的 String 對象中的內容到底變了沒有?

沒有。因為 String 被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。在 這段代碼中,s 原先指向一個 String 對象,內容是 "Hello",然后我們對 s 進行了+操作,那 么 s 所指向的那個對象是否發生了改變呢?答案是沒有。這時,s 不指向原來那個對象了, 而指向了另一個 String 對象,內容為"Hello world!",原來那個對象還存在于內存之中,只 是 s 這個引用變量不再指向它了。

通過上面的說明,我們很容易導出另一個結論,如果經常對字符串進行各種各樣的修改,或 者說,不可預見的修改,那么使用 String 來代表字符串的話會引起很大的內存開銷。因為 String 對象建立之后不能再改變,所以對于每一個不同的字符串,都需要一個 String 對象來 表示。這時,應該考慮使用 StringBuffer類,它允許修改,而不是每個不同的字符串都要生 成一個新的對象。并且,這兩種類的對象轉換十分容易。

同時,我們還可以知道,如果要使用內容相同的字符串,不必每次都 new 一個 String。例 如我們要在構造器中對一個名叫 s 的 String 引用變量進行初始化,把它設置為初始值,應當這樣做:

 public class Demo { private String s; ... public Demo { s = "Initial Value"; } ... //而非 s = new String("Initial Value"); }

后者每次都會調用構造器,生成新對象,性能低下且內存開銷大,并且沒有意義,因為 String 對象不可改變,所以對于內容相同的字符串,只要一個 String 對象來表示就可以了。也就 說,多次調用上面的構造器創建多個對象,他們的 String 類型屬性 s 都指向同一個對象。 上面的結論還基于這樣一個事實:對于字符串常量,如果內容相同,Java 認為它們代表同 一個 String 對象。而用關鍵字 new 調用構造器,總是會創建一個新的對象,無論內容是否 相同。

至于為什么要把 String 類設計成不可變類,是它的用途決定的。其實不只 String,很多 Java 標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是面向對象思想的體現。不可變類有一些優點,比如因為它的對象是只讀的,所以多線程并發訪問也不會有任何問題。當然也有一些缺點,比如每個不同的 狀態都要一個對象來代表,可能會造成性能上的問題。所以 Java 標準類庫還提供了一個可 變版本,即 StringBuffer。

下一個問題:

String s =?new?String("xyz");

創建了幾個 String Object?二者之間有什么區別?

兩個或一個?,”xyz”對應一個對象,這個對象放在字符串常量緩沖區,常量”xyz”不管出現多少遍,都是緩沖區中的那一個。New String 每寫一遍,就創建一個新的對象,它一句那個 常量”xyz”對象的內容來創建出一個新 String 對象。如果以前就用過’xyz’,這句代表就不會 創建”xyz”自己了,直接從緩沖區拿。

String 和 StringBuffer 的區別

JAVA 平臺提供了兩個類:String 和 StringBuffer,它們可以儲存和操作字符串,即包含多個字符的字符數據。這個 String 類提供了數值不可改變的字符串。而這個 StringBuffer 類提供 的字符串進行修改。當你知道字符數據要改變的時候你就可以使用 StringBuffer。典型地, 你可以使用 StringBuffers 來動態構造字符數據。另外,String 實現了 equals 方法,new String(“abc”).equals(newString(“abc”)的結果為true,而StringBuffer沒有實現equals方法, 所以,new StringBuffer(“abc”).equals(newStringBuffer(“abc”)的結果為 false。

接著要舉一個具體的例子來說明,我們要把1到100的所有數字拼起來,組成一個串。

StringBuffer sbf = new StringBuffer();for(int i=0;i<100;i++){ sbf.append(i); }

上面的代碼效率很高,因為只創建了一個 StringBuffer 對象,而下面的代碼效率很低,因為 創建了101個對象。

 String str = new String(); for(int i=0;i<100;i++) { str = str + i; }

在講兩者區別時,應把循環的次數搞成10000,然后用 endTime-beginTime 來比較兩者執 行的時間差異。

String 覆蓋了 equals 方法和 hashCode 方法,而 StringBuffer沒有覆蓋 equals 方法和 hashCode 方法,所以,將 StringBuffer對象存儲進 Java集合類中時會出現問題

StringBuilder與 StringBuffer的區別

StringBuilder不是線程安全的,但是單線程中中的性能比StringBuffer高。

String與常量池

String對象創建方式

  String str1 = "abcd";String str2 = new String("abcd");System.out.println(str1==str2);//false

這兩種不同的創建方法是有差別的:

  • 第一種方式是在常量池中拿對象

  • 第二種方式是直接在堆內存空間創建一個新的對象。只要使用new方法,便會創建新的對象

連接表達式 +

  1. 只有使用引號包含文本的方式創建的String對象之間使用“+”連接產生的新對象才會被加入字符串池中。

  2. 對于所有包含new方式新建對象(包括null)的“+”連接表達式,它所產生的新對象都不會被加入字符串池中。

String str1 = "str";
String str2 = "ing";String str3 = "str" + "ing"; String str4 = str1 + str2; System.out.println(str3 == str4);//false String str5 = "string"; System.out.println(str3 == str5);//true
demo1
public static final String str1 = "ab"; public static final String str2 = "cd"; public static void main(String[] args) { String s = str1 + str2; // 將兩個常量用+連接對s進行初始化 String t = "abcd"; if (s == t) { System.out.println("s等于t,它們是同一個對象"); } else { System.out.println("s不等于t,它們不是同一個對象"); } }
  • s等于t,它們是同一個對象

  • A和B都是常量,值是固定的,因此s的值也是固定的,它在類被編譯時就已經確定了。也就是說:String s=A+B; 等同于:String s="ab"+"cd";

demo2
public static final String str1;
public static final String str2; static { str1 = "ab"; str2 = "cd"; } public static void main(String[] args) { // 將兩個常量用+連接對s進行初始化 String s = str1 + str2; String t = "abcd"; if (s == t) { System.out.println("s等于t,它們是同一個對象"); } else { System.out.println("s不等于t,它們不是同一個對象"); } }
  • s不等于t,它們不是同一個對象

  • A和B雖然被定義為常量,但是它們都沒有馬上被賦值。在運算出s的值之前,他們何時被賦值,以及被賦予什么樣的值,都是個變數。因此A和B在被賦值之前,性質類似于一個變量。那么s就不能在編譯期被確定,而只能在運行時被創建了。

思考一下
String s1 =?new?String("xyz"); 創建了幾個對象?

答:考慮類加載階段和實際執行時。

  1. 類加載對一個類只會進行一次。"xyz"在類加載時就已經創建并駐留了(如果該類被加載之前已經有"xyz"字符串被駐留過則不需要重復創建用于駐留的"xyz"實例)。駐留的字符串是放在全局共享的字符串常量池中的。

  2. 在這段代碼后續被運行的時候,"xyz"字面量對應的String實例已經固定了,不會再被重復創建。所以這段代碼將常量池中的對象復制一份放到heap中,并且把heap中的這個對象的引用交給s1 持有。

  • 故:這條語句創建了2個對象。

java.lang.String.intern()

運行時常量池相對于CLass文件常量池的另外一個重要特征是具備動態性,Java語言并不要求常量一定只有編譯期才能產生,也就是并非預置入CLass文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是String類的intern()方法。

String的intern()方法會查找在常量池中是否存在一份equal相等的字符串,如果有則返回該字符串的引用,如果沒有則添加自己的字符串進入常量池。

public static void main(String[] args) { String s1 = new String("計算機"); String s2 = s1.intern(); String s3 = "計算機"; System.out.println("s1 == s2? " + (s1 == s2)); System.out.println("s3 == s2? " + (s3 == s2)); } //s1 == s2? false //s3 == s2? true

較為豐富的demo

public class Test { public static void main(String[] args) { String hello = "Hello", lo = "lo"; System.out.println((hello == "Hello") + " "); System.out.println((Other.hello == hello) + " "); System.out.println((other.Other.hello == hello) + " "); System.out.println((hello == ("Hel" + "lo")) + " "); System.out.println((hello == ("Hel" + lo)) + " "); System.out.println(hello == ("Hel" + lo).intern()); } }
class Other {static String hello = "Hello"; }
package other;public class Other { public static String hello = "Hello"; } //true true true true false true
  • 在同包同類下,引用自同一String對象

  • 在同包不同類下,引用自同一String對象

  • 在不同包不同類下,依然引用自同一String對象

  • 在編譯成.class時能夠識別為同一字符串的,自動優化成常量,引用自同一String對象

  • 在運行時創建的字符串具有獨立的內存地址,所以不引用自同一String對象

神奇的數組

數組類型不是類,但數組實例是對象。這意味著,數組從 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中的自動裝箱與拆箱

關于異常

圖是我自己做的。如果覺得子類父類傻傻分不清,可以按照“紅橙黃綠”這個順序來,最高父類是紅。

  • 遇上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 創建對象的幾種方式

  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類型,取出時需要類型轉換。所以會有泛型(這樣也不用考慮取出時的類型轉換了)。另外集合里存儲的是引用,所以泛型不能使用基本類型。

常見集合?

?

集合概覽?

集合家族一覽?

  • 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 包中定義。

?

轉載于:https://www.cnblogs.com/juniorjava/p/6963107.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/392534.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/392534.shtml
英文地址,請注明出處:http://en.pswp.cn/news/392534.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

xcode擴展_如何將Xcode插件轉換為Xcode擴展名

xcode擴展by Khoa Pham通過Khoa Pham 如何將Xcode插件轉換為Xcode擴展名 (How to convert your Xcode plugins to Xcode extensions) Xcode is an indispensable IDE for iOS and macOS developers. From the early days, the ability to build and install custom plugins ha…

leetcode 861. 翻轉矩陣后的得分(貪心算法)

有一個二維矩陣 A 其中每個元素的值為 0 或 1 。 移動是指選擇任一行或列&#xff0c;并轉換該行或列中的每一個值&#xff1a;將所有 0 都更改為 1&#xff0c;將所有 1 都更改為 0。 在做出任意次數的移動后&#xff0c;將該矩陣的每一行都按照二進制數來解釋&#xff0c;矩…

數據分析團隊的價值_您的數據科學團隊的價值

數據分析團隊的價值This is the first article in a 2-part series!!這是分兩部分的系列文章中的第一篇&#xff01; 組織數據科學 (Organisational Data Science) Few would argue against the importance of data in today’s highly competitive corporate world. The tech…

mysql 保留5位小數_小猿圈分享-MySQL保留幾位小數的4種方法

今天小猿圈給大家分享的是MySQL使用中4種保留小數的方法&#xff0c;希望可以幫助到大家&#xff0c;讓大家的工作更加方便。1 round(x,d)用于數據x的四舍五入, round(x) ,其實就是round(x,0),也就是默認d為0&#xff1b;這里有個值得注意的地方是&#xff0c;d可以是負數&…

leetcode 842. 將數組拆分成斐波那契序列(回溯算法)

給定一個數字字符串 S&#xff0c;比如 S “123456579”&#xff0c;我們可以將它分成斐波那契式的序列 [123, 456, 579]。 形式上&#xff0c;斐波那契式序列是一個非負整數列表 F&#xff0c;且滿足&#xff1a; 0 < F[i] < 2^31 - 1&#xff0c;&#xff08;也就是…

博主簡介

面向各層次&#xff08;從中學到博士&#xff09;提供GIS和Python GIS案例實驗實習培訓&#xff0c;以解決問題為導向&#xff0c;以項目實戰為主線&#xff0c;以科學研究為思維&#xff0c;不講概念&#xff0c;不局限理論&#xff0c;簡單照做&#xff0c;即學即會。 研究背…

自定義Toast 很簡單就可以達到一些對話框的效果 使用起來很方便

自定義一個layout布局 通過toast.setView 設置布局彈出一些警示框 等一些不會改變的提示框 很方便public class CustomToast {public static void showUSBToast(Context context) {//加載Toast布局 View toastRoot LayoutInflater.from(context).inflate(R.layout.toas…

微信小程序阻止冒泡點擊_微信小程序bindtap事件與冒泡阻止詳解

bindtap就是點擊事件在.wxml文件綁定:cilck here在一個組件的屬性上添加bindtap并賦予一個值(一個函數名)當點擊該組件時, 會觸發相應的函數執行在后臺.js文件中定義tapMessage函數://index.jsPage({data: {mo: Hello World!!,userid : 1234,},// 定義函數tapMessage: function…

同情機器人_同情心如何幫助您建立更好的工作文化

同情機器人Empathy is one of those things that can help in any part of life whether it’s your family, friends, that special person and even also at work. Understanding what empathy is and how it effects people took me long time. I struggle with human inter…

數據庫課程設計結論_結論

數據庫課程設計結論When writing about learning or breaking into data science, I always advise building projects.在撰寫有關學習或涉足數據科學的文章時&#xff0c;我總是建議構建項目。 It is the best way to learn as well as showcase your skills.這是學習和展示技…

mongo基本使用方法

mongo與關系型數據庫的概念對比&#xff0c;區分大小寫&#xff0c;_id為主鍵。 1.數據庫操作 >show dbs #查看所有數據庫 >use dbname #創建和切換數據庫&#xff08;如果dbname存在則切換到該數據庫&#xff0c;不存在則創建并切換到該數據庫&#xff1b;新創建的…

leetcode 62. 不同路徑(dp)

一個機器人位于一個 m x n 網格的左上角 &#xff08;起始點在下圖中標記為“Start” &#xff09;。 機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角&#xff08;在下圖中標記為“Finish”&#xff09;。 問總共有多少條不同的路徑&#xff1f; 例如&…

第一名數據科學工作冠狀病毒醫生

背景 (Background) 3 years ago, I had just finished medical school and started working full-time as a doctor in the UK’s National Health Service (NHS). Now, I work full-time as a data scientist at dunnhumby, writing code for “Big Data” analytics with Pyt…

mysql時間區間效率_對于sql中使用to_timestamp判斷時間區間和不使用的效率對比及結論...

關于日期函數TO_TIMESTAMP拓展&#xff1a;date類型是Oracle常用的日期型變量&#xff0c;時間間隔是秒。兩個日期型相減得到是兩個時間的間隔&#xff0c;注意單位是“天”。timestamp是DATE類型的擴展&#xff0c;可以精確到小數秒(fractional_seconds_precision)&#xff0c…

ajax 賦值return

ajax 獲得結果后賦值無法成功&#xff0c; function grades(num){ var name"";   $.ajax({    type:"get",     url:"",     async:true,     success:function(result){     var grades result.grades;     …

JavaScript(ES6)傳播算子和rest參數簡介

by Joanna Gaudyn喬安娜高登(Joanna Gaudyn) JavaScript(ES6)傳播算子和rest參數簡介 (An intro to the spread operator and rest parameter in JavaScript (ES6)) 擴展運算符和rest參數都被寫為三個連續的點(…)。 他們還有其他共同點嗎&#xff1f; (Both the spread opera…

python爬蟲消費者與生產者_Condition版生產者與消費者模式

概述&#xff1a;在人工智能來臨的今天&#xff0c;數據顯得格外重要。在互聯網的浩瀚大海洋中&#xff0c;隱藏著無窮的數據和信息。因此學習網絡爬蟲是在今天立足的一項必備技能。本路線專門針對想要從事Python網絡爬蟲的同學而準備的&#xff0c;并且是嚴格按照企業的標準定…

【Python包】安裝teradatasql提示找不到pycryptodome模塊錯誤(pycrypto,pycryptodome和crypto加密庫)...

1.問題描述 安裝teradatasql時&#xff0c;出現錯誤Could not find a version that satisfies the requirement pycryptodome&#xff0c;具體如下&#xff1a; 2.解決方法 查看Python第三方庫目錄$PYTHON_HOME/lib/python3.6/site-packages目錄下沒有pycryptodome目錄&#xf…

leetcode 860. 檸檬水找零(貪心算法)

在檸檬水攤上&#xff0c;每一杯檸檬水的售價為 5 美元。 顧客排隊購買你的產品&#xff0c;&#xff08;按賬單 bills 支付的順序&#xff09;一次購買一杯。 每位顧客只買一杯檸檬水&#xff0c;然后向你付 5 美元、10 美元或 20 美元。你必須給每個顧客正確找零&#xff0…

簡述yolo1-yolo3_使用YOLO框架進行對象檢測的綜合指南-第二部分

簡述yolo1-yolo3In the last part, we understood what YOLO is and how it works. In this section, let us understand how to apply it using pre-trained weights and obtaining the results. This article is greatly inspired by Andrew Ng’s Deep Learning Specializat…