本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看html
喜歡的話麻煩點下Star哈git
文章首發於個人我的博客:程序員
www.how2playlife.comgithub
本文是微信公衆號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部份內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了不少我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,若有侵權,請聯繫做者。 該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接着瞭解每一個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,造成本身的知識框架。爲了更好地總結和檢驗你的學習成果,本系列文章也會提供每一個知識點對應的面試題以及參考答案。面試
若是對本系列文章有什麼建議,或者是有什麼疑問的話,也能夠關注公衆號【Java技術江湖】聯繫做者,歡迎你參與本系列博文的創做和修訂。算法
變量就是申請內存來存儲值。也就是說,當建立變量的時候,須要在內存中申請空間。後端
內存管理系統根據變量的類型爲變量分配存儲空間,分配的空間只能用來儲存該類型數據。數組
所以,經過定義不一樣類型的變量,能夠在內存中儲存整數、小數或者字符。緩存
Java語言提供了八種基本類型。六種數字類型(四個整數型,兩個浮點型),一種字符類型,還有一種布爾型。
byte:
short:
int:
long:
float:
double:
boolean:
char:
//8位 byte bx = Byte.MAX_VALUE; byte bn = Byte.MIN_VALUE; //16位 short sx = Short.MAX_VALUE; short sn = Short.MIN_VALUE; //32位 int ix = Integer.MAX_VALUE; int in = Integer.MIN_VALUE; //64位 long lx = Long.MAX_VALUE; long ln = Long.MIN_VALUE; //32位 float fx = Float.MAX_VALUE; float fn = Float.MIN_VALUE; //64位 double dx = Double.MAX_VALUE; double dn = Double.MIN_VALUE; //16位 char cx = Character.MAX_VALUE; char cn = Character.MIN_VALUE; //1位 boolean bt = Boolean.TRUE; boolean bf = Boolean.FALSE;
`127` `-128` `32767` `-32768` `2147483647` `-2147483648` `9223372036854775807` `-9223372036854775808` `3.4028235E38` `1.4E-45` `1.7976931348623157E308` `4.9E-324` `` `true` `false`
常量在程序運行時是不能被修改的。
在 Java 中使用 final 關鍵字來修飾常量,聲明方式和變量相似:
final double PI = 3.1415927;
雖然常量名也能夠用小寫,但爲了便於識別,一般使用大寫字母表示常量。
字面量能夠賦給任何內置類型的變量。例如:
byte a = 68; char a = 'A'
Java 5增長了自動裝箱與自動拆箱機制,方便基本類型與包裝類型的相互轉換操做。在Java 5以前,若是要將一個int型的值轉換成對應的包裝器類型Integer,必須顯式的使用new建立一個新的Integer對象,或者調用靜態方法Integer.valueOf()。
//在Java 5以前,只能這樣作 Integer value = new Integer(10); //或者這樣作 Integer value = Integer.valueOf(10); //直接賦值是錯誤的 //Integer value = 10;`
在Java 5中,能夠直接將整型賦給Integer對象,由編譯器來完成從int型到Integer類型的轉換,這就叫自動裝箱。
`//在Java 5中,直接賦值是合法的,由編譯器來完成轉換` `Integer value = 10;` `與此對應的,自動拆箱就是能夠將包裝類型轉換爲基本類型,具體的轉換工做由編譯器來完成。` `//在Java 5 中能夠直接這麼作` `Integer value = new Integer(10);` `int i = value;`
自動裝箱與自動拆箱爲程序員提供了很大的方便,而在實際的應用中,自動裝箱與拆箱也是使用最普遍的特性之一。自動裝箱和自動拆箱實際上是Java編譯器提供的一顆語法糖(語法糖是指在計算機語言中添加的某種語法,這種語法對語言的功能並無影響,可是更方便程序員使用。經過可提升開發效率,增長代碼可讀性,增長代碼的安全性)。
在八種包裝類型中,每一種包裝類型都提供了兩個方法:
靜態方法valueOf(基本類型):將給定的基本類型轉換成對應的包裝類型;
實例方法xxxValue():將具體的包裝類型對象轉換成基本類型; 下面咱們以int和Integer爲例,說明Java中自動裝箱與自動拆箱的實現機制。看以下代碼:
class Auto //code1 { public static void main(String[] args) { //自動裝箱 Integer inte = 10; //自動拆箱 int i = inte; //再double和Double來驗證一下 Double doub = 12.40; double d = doub; } }
上面的代碼先將int型轉爲Integer對象,再講Integer對象轉換爲int型,毫無疑問,這是能夠正確運行的。但是,這種轉換是怎麼進行的呢?使用反編譯工具,將生成的Class文件在反編譯爲Java文件,讓咱們看看發生了什麼:
class Auto//code2 { public static void main(String[] paramArrayOfString) { Integer localInteger = Integer.valueOf(10);
int i = localInteger.intValue();
Double localDouble = Double.valueOf(12.4D); double d = localDouble.doubleValue();
} }
咱們能夠看到通過javac編譯以後,code1的代碼被轉換成了code2,實際運行時,虛擬機運行的就是code2的代碼。也就是說,虛擬機根本不知道有自動拆箱和自動裝箱這回事;在將Java源文件編譯爲class文件的過程當中,javac編譯器在自動裝箱的時候,調用了Integer.valueOf()方法,在自動拆箱時,又調用了intValue()方法。咱們能夠看到,double和Double也是如此。 實現總結:其實自動裝箱和自動封箱是編譯器爲咱們提供的一顆語法糖。在自動裝箱時,編譯器調用包裝類型的valueOf()方法;在自動拆箱時,編譯器調用了相應的xxxValue()方法。
在使用自動裝箱與自動拆箱時,要注意一些陷阱,爲了不這些陷阱,咱們有必要去看一下各類包裝類型的源碼。
Integer源碼
public final class Integer extends Number implements Comparable<Integer> { private final int value; /*Integer的構造方法,接受一個整型參數,Integer對象表示的int值,保存在value中*/ public Integer(int value) { this.value = value; } /*equals()方法判斷的是:所表明的int型的值是否相等*/ public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; } /*返回這個Integer對象表明的int值,也就是保存在value中的值*/ public int intValue() { return value; } /** * 首先會判斷i是否在[IntegerCache.low,Integer.high]之間 * 若是是,直接返回Integer.cache中相應的元素 * 不然,調用構造方法,建立一個新的Integer對象 */ public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } /** * 靜態內部類,緩存了從[low,high]對應的Integer對象 * low -128這個值不會被改變 * high 默認是127,能夠改變,最大不超過:Integer.MAX_VALUE - (-low) -1 * cache 保存從[low,high]對象的Integer對象 */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} }
以上是Oracle(Sun)公司JDK 1.7中Integer源碼的一部分,經過分析上面的代碼,獲得:
1)Integer有一個實例域value,它保存了這個Integer所表明的int型的值,且它是final的,也就是說這個Integer對象一經構造完成,它所表明的值就不能再被改變。
2)Integer重寫了equals()方法,它經過比較兩個Integer對象的value,來判斷是否相等。
3)重點是靜態內部類IntegerCache,經過類名就能夠發現:它是用來緩存數據的。它有一個數組,裏面保存的是連續的Integer對象。 (a) low:表明緩存數據中最小的值,固定是-128。
(b) high:表明緩存數據中最大的值,它能夠被該改變,默認是127。high最小是127,最大是Integer.MAX_VALUE-(-low)-1,若是high超過了這個值,那麼cache[ ]的長度就超過Integer.MAX_VALUE了,也就溢出了。
(c) cache[]:裏面保存着從[low,high]所對應的Integer對象,長度是high-low+1(由於有元素0,因此要加1)。
4)調用valueOf(inti)方法時,首先判斷i是否在[low,high]之間,若是是,則複用Integer.cache[i-low]。好比,若是Integer.valueOf(3),直接返回Integer.cache[131];若是i不在這個範圍,則調用構造方法,構造出一個新的Integer對象。
5)調用intValue(),直接返回value的值。 經過3)和4)能夠發現,默認狀況下,在使用自動裝箱時,VM會複用[-128,127]之間的Integer對象。
Integer a1 = 1; Integer a2 = 1; Integer a3 = new Integer(1); //會打印true,由於a1和a2是同一個對象,都是Integer.cache[129] System.out.println(a1 == a2); //false,a3構造了一個新的對象,不一樣於a1,a2 System.out.println(a1 == a3);
//基本數據類型的常量池是-128到127之間。 // 在這個範圍中的基本數據類的包裝類能夠自動拆箱,比較時直接比較數值大小。 public static void main(String[] args) { //int的自動拆箱和裝箱只在-128到127範圍中進行,超過該範圍的兩個integer的 == 判斷是會返回false的。 Integer a1 = 128; Integer a2 = -128; Integer a3 = -128; Integer a4 = 128; System.out.println(a1 == a4); System.out.println(a2 == a3); Byte b1 = 127; Byte b2 = 127; Byte b3 = -128; Byte b4 = -128; //byte都是相等的,由於範圍就在-128到127之間 System.out.println(b1 == b2); System.out.println(b3 == b4); // Long c1 = 128L; Long c2 = 128L; Long c3 = -128L; Long c4 = -128L; System.out.println(c1 == c2); System.out.println(c3 == c4); //char沒有負值 //發現char也是在0到127之間自動拆箱 Character d1 = 128; Character d2 = 128; Character d3 = 127; Character d4 = 127; System.out.println(d1 == d2); System.out.println(d3 == d4); `結果` `false` `true` `true` `true` `false` `true` `false` `true` Integer i = 10; Byte b = 10; //比較Byte和Integer.兩個對象沒法直接比較,報錯 //System.out.println(i == b); System.out.println("i == b " + i.equals(b)); //答案是false,由於包裝類的比較時先比較是不是同一個類,不是的話直接返回false. int ii = 128; short ss = 128; long ll = 128; char cc = 128; System.out.println("ii == bb " + (ii == ss)); System.out.println("ii == ll " + (ii == ll)); System.out.println("ii == cc " + (ii == cc)); 結果 i == b false ii == bb true ii == ll true ii == cc true //這時候都是true,由於基本數據類型直接比較值,值同樣就能夠。
經過上面的代碼,咱們分析一下自動裝箱與拆箱發生的時機:
(1)當須要一個對象的時候會自動裝箱,好比Integer a = 10;equals(Object o)方法的參數是Object對象,因此須要裝箱。
(2)當須要一個基本類型時會自動拆箱,好比int a = new Integer(10);算術運算是在基本類型間進行的,因此當遇到算術運算時會自動拆箱,好比代碼中的 c == (a + b);
(3) 包裝類型 == 基本類型時,包裝類型自動拆箱;
須要注意的是:「==」在沒遇到算術運算時,不會自動拆箱;基本類型只會自動裝箱爲對應的包裝類型,代碼中最後一條說明的內容。
在JDK 1.5中提供了自動裝箱與自動拆箱,這實際上是Java 編譯器的語法糖,編譯器經過調用包裝類型的valueOf()方法實現自動裝箱,調用xxxValue()方法自動拆箱。自動裝箱和拆箱會有一些陷阱,那就是包裝類型複用了某些對象。
(1)Integer默認複用了[-128,127]這些對象,其中高位置能夠修改;
(2)Byte複用了所有256個對象[-128,127];
(3)Short服用了[-128,127]這些對象;
(4)Long服用了[-128,127];
(5)Character複用了[0,127],Charater不能表示負數;
Double和Float是連續不可數的,因此無法複用對象,也就不存在自動裝箱複用陷阱。
Boolean沒有自動裝箱與拆箱,它也複用了Boolean.TRUE和Boolean.FALSE,經過Boolean.valueOf(boolean b)返回的Blooean對象要麼是TRUE,要麼是FALSE,這點也要注意。
本文介紹了「真實的」自動裝箱與拆箱,爲了不寫出錯誤的代碼,又從包裝類型的源碼入手,指出了各類包裝類型在自動裝箱和拆箱時存在的陷阱,同時指出了自動裝箱與拆箱發生的時機。
上面自動拆箱和裝箱的原理其實與常量池有關。
public void(int a) { int i = 1; int j = 1; } 方法中的i 存在虛擬機棧的局部變量表裏,i是一個引用,j也是一個引用,它們都指向局部變量表裏的整型值 1. int a是傳值引用,因此a也會存在局部變量表。
class A{ int i = 1; A a = new A(); } i是類的成員變量。類實例化的對象存在堆中,因此成員變量也存在堆中,引用a存的是對象的地址,引用i存的是值,這個值1也會存在堆中。能夠理解爲引用i指向了這個值1。也能夠理解爲i就是1.
3 包裝類對象怎麼存 其實咱們說的常量池也能夠叫對象池。 好比String a= new String("a").intern()時會先在常量池找是否有「a"對象若是有的話直接返回「a"對象在常量池的地址,即讓引用a指向常量」a"對象的內存地址。 public native String intern(); Integer也是同理。
下圖是Integer類型在常量池中查找同值對象的方法。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
因此基本數據類型的包裝類型能夠在常量池查找對應值的對象,找不到就會自動在常量池建立該值的對象。
而String類型能夠經過intern來完成這個操做。
JDK1.7後,常量池被放入到堆空間中,這致使intern()函數的功能不一樣,具體怎麼個不一樣法,且看看下面代碼,這個例子是網上流傳較廣的一個例子,分析圖也是直接粘貼過來的,這裏我會用本身的理解去解釋這個例子:
[java] view plain copy String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); 輸出結果爲: [java] view plain copy JDK1.6以及如下:false false JDK1.7以及以上:false true
JDK1.6查找到常量池存在相同值的對象時會直接返回該對象的地址。
JDK 1.7後,intern方法仍是會先去查詢常量池中是否有已經存在,若是存在,則返回常量池中的引用,這一點與以前沒有區別,區別在於,若是在常量池找不到對應的字符串,則不會再將字符串拷貝到常量池,而只是在常量池中生成一個對原字符串的引用。
那麼其餘字符串在常量池找值時就會返回另外一個堆中對象的地址。
下一節詳細介紹String以及相關包裝類。
具體請見:https://blog.csdn.net/a724888/article/details/80042298
關於Java面向對象三大特性,請參考:
https://blog.csdn.net/a724888/article/details/80033043
https://www.runoob.com/java/java-basic-datatypes.html
https://www.cnblogs.com/zch1126/p/5335139.html
http://www.javashuo.com/article/p-ebwsdkky-ba.html
https://blog.csdn.net/yuhongye111/article/details/31850779
若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人公衆號【Java技術江湖】一位阿里 Java 工程師的技術小站,做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!
Java工程師必備學習資源: 一些Java工程師經常使用學習資源,關注公衆號後,後臺回覆關鍵字 「Java」 便可免費無套路獲取。
做者是 985 碩士,螞蟻金服 JAVA 工程師,專一於 JAVA 後端技術棧:SpringBoot、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,偶爾講點算法和計算機理論基礎,堅持學習和寫做,相信終身學習的力量!
程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取。