01.Java基礎問題
目錄介紹
- 1.0.0.1 請手寫equal方法,講講具體的原理?
- 1.0.0.2 請說下String與StringBuffer區別,StringBuffer底部如何實現?String類可以被繼承嗎,為什麼?
- 1.0.0.3 String a=””和String a=new String(“”)的的關係和異同?String的建立機制?
- 1.0.0.4 static關鍵字可以修飾什麼?static使用的注意事項有哪些?static關鍵字的特點?
- 1.0.0.5 為什麼 Java 中的 String 是不可變的(Immutable)?字串設計和實現考量?String不可變的好處?
- 1.0.0.6 Hashcode與equal區別,什麼時候需要用到hashcode?講講裡面的原理。如何解決Hash衝突?
- 1.0.0.7 訪問修飾符public,private,protected,以及不寫(預設)時的區別?
- 1.0.0.8 靜態變數和例項變數的區別?成員變數與區域性變數的區別有那些?
- 1.0.0.9 如何實現物件克隆?深克隆,淺克隆分別說的是什麼意思?
- 1.0.1.0 int和Integer的區別?裝箱、拆箱什麼含義?什麼時候裝箱/拆箱?裝箱和拆箱是如何實現的?
- 1.0.1.1 Object有哪些公有方法?
- 1.0.1.2 final,finally,finalize有什麼不同?finally什麼情況下不會被執行?
- 1.0.1.3 為什麼要使用萬用字元?上界萬用字元和下界萬用字元如何理解和注意要點?什麼是無界萬用字元?
- 1.0.1.4 什麼是泛型擦除,能否通過開發中實際案例說下?如何獲取泛型的具體的型別【反射】?
- 1.0.1.5 如何驗證int型別是否執行緒安全?那些型別是執行緒安全的?舉一個執行緒安全的例子【AtomicInteger】?
- 1.0.1.6 Java序列話中如果有些欄位不想進行序列化怎麼辦?
- 1.0.1.7 有沒有可能 兩個不相等的物件有相同的 hashcode?當兩個物件 hashcode 相同怎麼辦?如何獲取值物件?
- 1.0.1.8 原始資料型別和引用型別侷限性?為何要引用基本資料包裝類?
- 1.0.1.9 new Integer(123) 與 Integer.valueOf(123)有何區別,請從底層實現分析兩者區別?
好訊息
- 部落格筆記大彙總【15年10月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計500篇[近100萬字],將會陸續發表到網上,轉載請註明出處,謝謝!
- 連結地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!所有部落格將陸續開源到GitHub!
1.0.0.1 請手寫equal方法,講講具體的原理?
-
程式碼如下所示,如果是手寫程式碼,一定要弄清楚邏輯思路!
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String) anObject; int n = count; if (n == anotherString.count) { int i = 0; while (n-- != 0) { if (charAt(i) != anotherString.charAt(i)) return false; i++; } return true; } } return false; }
1.0.0.2 請說下String與StringBuffer區別,StringBuffer底部如何實現?String類可以被繼承嗎,為什麼?
-
String類的特點
- String的特點是一旦被建立,就不能被改變。注意是地址不能改變。StringBuffer底層是可變的位元組序列……
-
String類可以被繼承嗎
- 看String原始碼可知,String類被final關鍵字修飾了,所以不能被繼承。這個地方可以說下final關鍵字作用。
-
String、StringBuffer和StringBuilder的區別?
- String是字串常量,而StringBuffer、StringBuilder都是字串變數,即String物件一建立後不可更改,而後兩者的物件是可更改的:
- StringBuffer是執行緒安全的,而StringBuilder是非執行緒安全的,這是由於StringBuffer對方法加了同步鎖或者對呼叫的方法加了同步鎖
- String更適用於少量的字串操作的情況,StringBuilder適用於單執行緒下在字元緩衝區進行大量操作的情況,StringBuffer適用於多執行緒下在字元緩衝區進行大量操作的情況
- 技術部落格大總結
1.0.0.3 String a=””和String a=new String(“”)的的關係和異同?String的建立機制?
-
區別
- 通過String a=””直接賦值的方式得到的是一個字串常量,存在於常量池;注意,相同內容的字串在常量池中只有一個,即如果池已包含內容相等的字串會返回池中的字串,反之會將該字串放入池中
- 通過new String(“”)建立的字串不是常量是例項物件,會在堆記憶體開闢空間並存放資料,且每個例項物件都有自己的地址空間
-
String的建立機制
- 由於String在Java世界中使用過於頻繁,Java為了避免在一個系統中產生大量的String物件,引入了字串常量池。其執行機制是:建立一個字串時,首先檢查池中是否有值相同的字串物件,如果有則不需要建立直接從池中剛查詢到的物件引用;如果沒有則新建字串物件,返回物件引用,並且將新建立的物件放入池中。但是,通過new方法建立的String物件是不檢查字串池的,而是直接在堆區或棧區建立一個新的物件,也不會把物件放入池中。上述原則只適用於通過直接量給String物件引用賦值的情況。
1.0.0.4 static關鍵字可以修飾什麼?static使用的注意事項有哪些?static關鍵字的特點?
-
可以用來修飾:成員變數,成員方法,程式碼塊,內部類等。具體如下所示
-
修飾成員變數和成員方法
- 被 static 修飾的成員屬於類,不屬於單個這個類的某個物件,被類中所有物件共享,可以並且建議通過類名呼叫。
- 被static 宣告的成員變數屬於靜態成員變數,靜態變數存放在Java記憶體區域的方法區。
-
靜態程式碼塊
- 靜態程式碼塊定義在類中方法外,靜態程式碼塊在非靜態程式碼塊之前執行(靜態程式碼塊—>非靜態程式碼塊—>構造方法)
- 該類不管建立多少物件,靜態程式碼塊只執行一次.
- 靜態內部類(static修飾類的話只能修飾內部類)
-
靜態內部類與非靜態內部類之間存在一個最大的區別:
- 非靜態內部類在編譯完成之後會隱含地儲存著一個引用,該引用是指向建立它的外圍內,但是靜態內部類卻沒有。沒有這個引用就意味著:1.它的建立是不需要依賴外圍類的建立。2.它不能使用任何外圍類的非static成員變數和方法。
-
靜態導包(用來匯入類中的靜態資源,1.5之後的新特性):
- 這兩個關鍵字連用可以指定匯入某個類中的指定靜態資源,並且不需要使用類名呼叫類中靜態成員,可以直接使用類中靜態成員變數和成員方法。
-
-
static使用的注意事項有哪些?
-
在靜態方法中是沒有this關鍵字的
- 靜態是隨著類的載入而載入,this是隨著物件的建立而存在。
- 靜態比物件先存在。
- 靜態方法只能訪問靜態的成員變數和靜態的成員方法【靜態只能訪問靜態,非靜態可以訪問靜態的也可以訪問非靜態的】
-
-
static關鍵字的特點?
- 隨著類的載入而載入
- 優先於物件存在
- 被類的所有物件共享
- 可以通過類名呼叫【靜態修飾的內容一般我們稱其為:與類相關的,類成員】
1.0.0.5 為什麼Java中的 String 是不可變的(Immutable)?字串設計和實現考量?String不可變的好處?
-
不可變類String的原因
- String主要的三個成員變數 char value[], int offset, int count均是private,final的,並且沒有對應的 getter/setter;
- String 物件一旦初始化完成,上述三個成員變數就不可修改;並且其所提供的介面任何對這些域的修改都將返回一個新物件;
- 技術部落格大總結
- 是典型的 Immutable 類,被宣告成為 final class,所有屬性也都是final的。也由於它的不可變,類似拼接、裁剪字串等動作,都會產生新的 String 物件。
-
字串設計和實現考量?
- String 是 Immutable 類的典型實現,原生的保證了基礎執行緒安全,因為你無法對它內部資料進行任何修改,這種便利甚至體現在拷貝建構函式中,由於不可變,Immutable 物件在拷貝時不需要額外複製資料。
- 為了實現修改字元序列的目的,StringBuffer 和 StringBuilder 底層都是利用可修改的(char,JDK 9 以後是 byte)陣列,二者都繼承了 AbstractStringBuilder,裡面包含了基本操作,區別僅在於最終的方法是否加了 synchronized。
- 這個內部陣列應該建立成多大的呢?如果太小,拼接的時候可能要重新建立足夠大的陣列;如果太大,又會浪費空間。目前的實現是,構建時初始字串長度加 16(這意味著,如果沒有構建物件時輸入最初的字串,那麼初始值就是 16)。我們如果確定拼接會發生非常多次,而且大概是可預計的,那麼就可以指定合適的大小,避免很多次擴容的開銷。擴容會產生多重開銷,因為要拋棄原有陣列,建立新的(可以簡單認為是倍數)陣列,還要進行arraycopy。
-
String不可變的好處?
-
可以快取 hash 值
- 因為 String 的 hash 值經常被使用,例如 String 用做 HashMap 的 key。不可變的特性可以使得 hash 值也不可變,因此只需要進行一次計算。
-
String Pool 的需要
- 如果一個String物件已經被建立過了,那麼就會從 String Pool 中取得引用。只有 String 是不可變的,才可能使用 String Pool。
-
安全性
- String 經常作為引數,String 不可變性可以保證引數不可變。例如在作為網路連線引數的情況下如果 String 是可變的,那麼在網路連線過程中,String 被改變,改變 String 物件的那一方以為現在連線的是其它主機,而實際情況卻不一定是。
-
執行緒安全
- String 不可變性天生具備執行緒安全,可以在多個執行緒中安全地使用。
-
1.0.0.6 Hashcode與equal區別,什麼時候需要用到hashcode?講講裡面的原理。如何解決Hash衝突?
-
Hashcode與equal區別
- equals()比較兩個物件的地址值是否相等 ;hashCode()得到的是物件的儲存位置,可能不同物件會得到相同值
- 有兩個物件,若equals()相等,則hashcode()一定相等;hashcode()不等,則equals()一定不相等;hashcode()相等,equals()可能相等、可能不等
- 使用equals()比較兩個物件是否相等效率較低,最快辦法是先用hashCode()比較,如果hashCode()不相等,則這兩個物件肯定不相等;如果hashCode()相等,此時再用equal()比較,如果equal()也相等,則這兩個物件的確相等。
-
什麼時候需要用到hashcode
- 同樣用於鑑定2個物件是否相等的,java集合中有 list 和 set 兩類,其中 set不允許元素重複實現,那個這個不允許重複實現的方法,如果用 equal 去比較的話,如果存在1000個元素,你 new 一個新的元素出來,需要去呼叫1000次 equal 去逐個和他們比較是否是同一個物件,這樣會大大降低效率。hashcode實際上是返回物件的儲存地址,如果這個位置上沒有元素,就把元素直接儲存在上面,如果這個位置上已經存在元素,這個時候才去呼叫equal方法與新元素進行比較,相同的話就不存了,雜湊到其他地址上
- 技術部落格大總結
-
如何解決Hash衝突
- 開放定址法:常見的線性探測方式,在衝突發生時,順序檢視錶中下一單元,直到找出一個空單元或查遍全表
- 鏈地址法:將有衝突陣列位置生出連結串列
- 建立公共溢位區:將雜湊表分為基本表和溢位表兩部分,和基本表發生衝突的元素一律填入溢位表
- 再雜湊法:構造多個不同的雜湊函式,有衝突使用下一個雜湊函式計算hash值
1.0.0.7 訪問修飾符public,private,protected,以及不寫(預設)時的區別?
-
類的成員不寫訪問修飾時預設為default。預設對於同一個包中的其他類相當於公開(public),對於不是同一個包中的其他類相當於私有(private)。受保護(protected)對子類相當於公開,對不是同一包中的沒有父子關係的類相當於私有。
區別如下: 作用域 當前類 同包 子類 其他 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × ×
1.0.0.8 靜態變數和例項變數的區別?成員變數與區域性變數的區別有那些?
-
靜態變數和例項變數的區別
- 靜態變數是被static修飾符修飾的變數,也稱為類變數,它屬於類,不屬於類的任何一個物件,一個類不管建立多少個物件,靜態變數在記憶體中有且僅有一個拷貝。靜態變數可以實現讓多個物件共享記憶體。在Java開發中,上下文類和工具類中通常會有大量的靜態成員。
- 例項變數必須依存於某一例項,需要先建立物件然後通過物件才能訪問到它
-
成員變數與區域性變數的區別
- 1.從語法形式上,看成員變數是屬於類的,而區域性變數是在方法中定義的變數或是方法的引數;成員變數可以被 public,private,static 等修飾符所修飾,而區域性變數不能被訪問控制修飾符及 static 所修飾;但是,成員變數和區域性變數都能被 final 所修飾;
- 2.從變數在記憶體中的儲存方式來看,成員變數是物件的一部分,而物件存在於堆記憶體,區域性變數存在於棧記憶體
- 3.從變數在記憶體中的生存時間上看,成員變數是物件的一部分,它隨著物件的建立而存在,而區域性變數隨著方法的呼叫而自動消失。
- 4.成員變數如果沒有被賦初值,則會自動以型別的預設值而賦值(一種情況例外被 final 修飾但沒有被 static 修飾的成員變數必須顯示地賦值);而區域性變數則不會自動賦值。
1.0.0.9 如何實現物件克隆?深克隆,淺克隆分別說的是什麼意思?
-
有兩種方式:
- 1.實現Cloneable介面並重寫Object類中的clone()方法;
- 2.實現Serializable介面,通過物件的序列化和反序列化實現克隆,可以實現真正的深度克隆,程式碼如下。
-
注意問題:
- 基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的物件是否支援序列化,這項檢查是編譯器完成的,不是在執行時丟擲異常,這種是方案明顯優於使用Object類的clone方法克隆物件。
-
程式碼如下所示
public class MyUtil { private MyUtil() { throw new AssertionError(); } public static <T> T clone(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); return (T) ois.readObject(); // 說明:呼叫ByteArrayInputStream或ByteArrayOutputStream物件的close方法沒有任何意義 // 這兩個基於記憶體的流只要垃圾回收器清理物件就能夠釋放資源 } } class CloneTest { public static void main(String[] args) { try { Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300)); Person p2 = MyUtil.clone(p1); // 深度克隆 p2.getCar().setBrand("BYD"); // 修改克隆的Person物件p2關聯的汽車物件的品牌屬性 // 原來的Person物件p1關聯的汽車不會受到任何影響 // 因為在克隆Person物件時其關聯的汽車物件也被克隆了 System.out.println(p1); } catch (Exception e) { e.printStackTrace(); } } }
通過反射獲得泛型的實際型別引數
- 把泛型變數當成方法的引數,利用Method類的getGenericParameterTypes方法來獲取泛型的實際型別引數
-
例子:
public class GenericTest { public static void main(String[] args) throws Exception { getParamType(); } /*利用反射獲取方法引數的實際引數型別*/ public static void getParamType() throws NoSuchMethodException{ Method method = GenericTest.class.getMethod("applyMap",Map.class); //獲取方法的泛型引數的型別 Type[] types = method.getGenericParameterTypes(); System.out.println(types[0]); //引數化的型別 ParameterizedType pType = (ParameterizedType)types[0]; //原始型別 System.out.println(pType.getRawType()); //實際型別引數 System.out.println(pType.getActualTypeArguments()[0]); System.out.println(pType.getActualTypeArguments()[1]); } /*供測試引數型別的方法*/ public static void applyMap(Map<Integer,String> map){ } }
-
輸出結果:
java.util.Map<java.lang.Integer, java.lang.String> interface java.util.Map class java.lang.Integer class java.lang.String
1.0.1.0 int和Integer的區別?裝箱、拆箱什麼含義?什麼時候裝箱/拆箱?裝箱和拆箱是如何實現的?
-
int和Integer的區別:基本資料型別、引用型別
- Integer是int的包裝類,int則是java的一種基本資料型別
- Integer變數必須例項化後才能使用,而int變數不需要
- Integer實際是物件的引用,當new一個Integer時,實際上是生成一個指標指向此物件;而int則是直接儲存資料值
- Integer的預設值是null,int的預設值是0
-
裝箱、拆箱
- 裝箱就是自動將基本資料型別轉換為包裝器型別
- 拆箱就是自動將包裝器型別轉換為基本資料型別
//拆箱 int yc = 5; //裝箱 Integer yc = 5;
-
jdk中如何操作裝箱、拆箱
- 在JDK中,裝箱過程是通過呼叫包裝器的valueOf方法實現的,而拆箱過程是通過呼叫包裝器的xxxValue方法實現的(xxx代表對應的基本資料型別)。
- Integer、Short、Byte、Character、Long 這幾個類的valueOf方法的實現是類似的,有限可列舉,共享[-128,127];
- Double、Float的valueOf方法的實現是類似的,無限不可列舉,不共享;
- Boolean的valueOf方法的實現不同於以上的整型和浮點型,只有兩個值,有限可列舉,共享;
-
什麼時候裝箱/拆箱?
- 什麼時候拆箱主要取決於:在當前場景下,你需要的是引用型別還是原生型別。若需要引用型別,但傳進來的值是原生型別,則自動裝箱(例如,使用equals方法時傳進來原生型別的值);若需要的是原生型別,但傳進來的值是引用型別,則自動拆箱(例如,使用運算子進行運算時,運算元是包裝型別)。
-
裝箱和拆箱是如何實現的
- 以Interger類為例,下面看一段程式碼來了解裝箱和拆箱的實現
public class Main { public static void main(String[] args) { Integer y = 10; int c = i; } }
-
然後來編譯一下:
- 從反編譯得到的位元組碼內容可以看出,在裝箱的時候自動呼叫的是Integer的valueOf(int)方法。而在拆箱的時候自動呼叫的是Integer的intValue方法。
- 因此可以用一句話總結裝箱和拆箱的實現過程:裝箱過程是通過呼叫包裝器的valueOf方法實現的,而拆箱過程是通過呼叫包裝器的 xxxValue方法實現的。(xxx代表對應的基本資料型別)。
1.0.1.1 Object有哪些公有方法?
-
常用方法
- equals(): 和==作用相似
- hashCode():用於雜湊查詢,重寫了equals()一般都要重寫該方法
- getClass(): 獲取Class物件
- wait():讓當前執行緒進入等待狀態,並釋放它所持有的鎖
- notify()¬ifyAll(): 喚醒一個(所有)正處於等待狀態的執行緒
- toString():轉換成字串
1.0.1.2 final,finally,finalize有什麼不同?finally什麼情況下不會被執行?
-
final可以修飾類,方法,變數
- final修飾類代表類不可以繼承擴充
- final修飾變數表示變數不可以修改
- final修飾方法表示方法不可以被重寫
-
finally則是Java保證重點程式碼一定要被執行的一種機制
- 可以使用 try-finally 或者 try-catch-finally 來進行類似關閉 JDBC連線、保證 unlock 鎖等動作。
-
finalize 是基礎類 java.lang.Object的一個方法
- 它的設計目的是保證物件在被垃圾收集前完成特定資源的回收。finalize 機制現在已經不推薦使用,並且在 JDK 9開始被標記為 deprecated。
-
final 關鍵字深入理解
- 可以將方法或者類宣告為 final,這樣就可以明確告知別人,這些行為是不許修改的。
- 如果你關注過 Java 核心類庫的定義或原始碼, 有沒有發現java.lang 包下面的很多類,相當一部分都被宣告成為final class?在第三方類庫的一些基礎類中同樣如此,這可以有效避免 API 使用者更改基礎功能,某種程度上,這是保證平臺安全的必要手段。
- 使用 final 修飾引數或者變數,也可以清楚地避免意外賦值導致的程式設計錯誤,甚至,有人明確推薦將所有方法引數、本地變數、成員變數宣告成 final。
- final 變數產生了某種程度的不可變(immutable)的效果,所以,可以用於保護只讀資料,尤其是在併發程式設計中,因為明確地不能再賦值 final 變數,有利於減少額外的同步開銷,也可以省去一些防禦性拷貝的必要。
-
在以下4種特殊情況下,finally塊不會被執行:
- 1.在finally語句塊中發生了異常。
- 2.在前面的程式碼中用了System.exit()退出程式。
- 3.程式所在的執行緒死亡。
- 4.關閉CPU。
1.0.1.3 為什麼要使用萬用字元?上界萬用字元和下界萬用字元如何理解和注意要點?什麼是無界萬用字元?
-
為什麼要使用萬用字元
- 萬用字元的設計存在一定的場景,例如在使用泛型後,首先宣告瞭一個Animal的類,而後宣告瞭一個繼承Animal類的Cat類,顯然Cat類是Animal類的子類,但是List卻不是List的子型別,而在程式中往往需要表達這樣的邏輯關係。為了解決這種類似的場景,在泛型的引數型別的基礎上新增了萬用字元的用法。
-
<? extends T> 上界萬用字元
- 上界萬用字元顧名思義,<? extends T>表示的是型別的上界【 包含自身】,因此通配的引數化型別可能是T或T的子類。正因為無法確定具體的型別是什麼,add方法受限(可以新增null,因為null表示任何型別),但可以從列表中獲取元素後賦值給父型別。如上圖中的第一個例子,第三個add()操作會受限,原因在於List和List是List<? extends Animal>的子型別。
-
<? super T> 下界萬用字元
- 下界萬用字元<? super T>表示的是引數化型別是T的超型別(包含自身),層層至上,直至Object,編譯器無從判斷get()返回的物件的型別是什麼,因此get()方法受限。但是可以進行add()方法,add()方法可以新增T型別和T型別的子型別,如第二個例子中首先新增了一個Cat型別物件,然後新增了兩個Cat子類型別的物件,這種方法是可行的,但是如果新增一個Animal型別的物件,顯然將繼承的關係弄反了,是不可行的。
-
<?> 無界萬用字元
- 任意型別,如果沒有明確,那麼就是Object以及任意的Java類了
- 無界萬用字元用<?>表示,?代表了任何的一種型別,能代表任何一種型別的只有null(Object本身也算是一種型別,但卻不能代表任何一種型別,所以List
- 技術部落格大總結
1.0.1.4 什麼是泛型擦除,能否通過開發中實際案例說下?如何獲取泛型的具體的型別【反射】?
-
開發中的泛型擦除案例
- 泛型是提供給javac編譯器使用的,限定集合的輸入型別,編譯器編譯帶型別說明的集合時會去掉“型別”資訊。
public class GenericTest { public static void main(String[] args) { new GenericTest().testType(); } public void testType(){ ArrayList<Integer> collection1 = new ArrayList<Integer>(); ArrayList<String> collection2= new ArrayList<String>(); System.out.println(collection1.getClass()==collection2.getClass()); //兩者class型別一樣,即位元組碼一致 System.out.println(collection2.getClass().getName()); //class均為java.util.ArrayList,並無實際型別引數資訊 } //輸出結果 //true //java.util.ArrayList }
-
如何獲取泛型的具體的型別?
- 使用反射可跳過編譯器,往某個泛型集合加入其它型別資料。
- 只有引用型別才能作為泛型方法的實際引數,具體案例如下所示
public class GenericTest { public static void main(String[] args) { swap(new String[]{"111","222"},0,1);//編譯通過 //swap(new int[]{1,2},0,1); //編譯不通過,因為int不是引用型別 swap(new Integer[]{1,2},0,1);//編譯通過 } /*交換陣列a 的第i個和第j個元素*/ public static <T> void swap(T[]a,int i,int j){ T temp = a[i]; a[i] = a[j]; a[j] = temp; } }
- 但注意基本型別有時可以作為實參,因為有自動裝箱和拆箱。下面例子(編譯通過了):
public class GenericTest { public static void main(String[] args) { new GenericTest().testType(); int a = biggerOne(3,5); //int 和 double,取交為Number Number b = biggerOne(3,5.5); //String和int 取交為Object Object c = biggerOne("1",2); } //從x,y中返回y public static <T> T biggerOne(T x,T y){ return y; } }
- 同時,該例還表明,當實參不一致時,T取交集,即第一個共同的父類。
- 另外,如果用
Number b = biggerOne(3,5.5);
改為String c = biggerOne(3,5.5);
則編譯報錯:
Error:(17, 29) java: 不相容的型別: 推斷型別不符合上限 推斷: java.lang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>> 上限: java.lang.String,java.lang.Object
1.0.1.5 如何驗證int型別是否執行緒安全?那些型別是執行緒安全的?舉一個執行緒安全的例子【AtomicInteger】?
-
如何驗證int型別是否執行緒安全
- 200個執行緒,每個執行緒對共享變數 count 進行 50 次 ++ 操作
- int 作為基本型別,直接儲存在記憶體棧,且對其進行+,-操作以及++,–操作都不是原子操作,都有可能被其他執行緒搶斷,所以不是執行緒安全。int 用於單執行緒變數存取,開銷小,速度快
- 技術部落格大總結
int count = 0; private void startThread() { for (int i = 0;i < 200; i++){ new Thread(new Runnable() { @Override public void run() { for (int k = 0; k < 50; k++){ count++; } } }).start(); } // 休眠10秒,以確保執行緒都已啟動 try { Thread.sleep(1000*10); } catch (InterruptedException e) { e.printStackTrace(); }finally { Log.e("列印日誌----",count+""); } } //期望輸出10000,最後輸出的是9818 //注意:列印日誌----: 9818
-
那些型別是執行緒安全的
- Java自帶的執行緒安全的基本型別包括: AtomicInteger, AtomicLong, AtomicBoolean, AtomicIntegerArray,AtomicLongArray等
-
AtomicInteger執行緒安全版
- AtomicInteger類中有有一個變數valueOffset,用來描述AtomicInteger類中value的記憶體位置 。
- 當需要變數的值改變的時候,先通過get()得到valueOffset位置的值,也即當前value的值.給該值進行增加,並賦給next
- compareAndSet()比較之前取到的value的值當前有沒有改變,若沒有改變的話,就將next的值賦給value,倘若和之前的值相比的話發生變化的話,則重新一次迴圈,直到存取成功,通過這樣的方式能夠保證該變數是執行緒安全的
- value使用了volatile關鍵字,使得多個執行緒可以共享變數,使用volatile將使得VM優化失去作用,線上程數特別大時,效率會較低。
private static AtomicInteger atomicInteger = new AtomicInteger(1); static Integer count1 = Integer.valueOf(0); private void startThread1() { for (int i = 0;i < 200; i++){ new Thread(new Runnable() { @Override public void run() { for (int k = 0; k < 50; k++){ // getAndIncrement: 先獲得值,再自增1,返回值為自增前的值 count1 = atomicInteger.getAndIncrement(); } } }).start(); } // 休眠10秒,以確保執行緒都已啟動 try { Thread.sleep(1000*10); } catch (InterruptedException e) { e.printStackTrace(); }finally { Log.e("列印日誌----",count1+""); } } //期望輸出10000,最後輸出的是10000 //注意:列印日誌----: 10000 //AtomicInteger使用了volatile關鍵字進行修飾,使得該類可以滿足執行緒安全。 private volatile int value; public AtomicInteger(int initialValue) { value = initialValue; }
1.0.1.6 Java序列話中如果有些欄位不想進行序列化怎麼辦?
- 對於不想進行序列化的變數,使用transient關鍵字修飾。
- transient關鍵字的作用是:阻止例項中那些用此關鍵字修飾的的變數序列化;當物件被反序列化時,被transient修飾的變數值不會被持久化和恢復。transient只能修飾變數,不能修飾類和方法。
1.0.1.8 原始資料型別和引用型別侷限性?為何要引用基本資料包裝類?
-
原始資料型別和引用型別侷限性
- 原始資料型別和 Java 泛型並不能配合使用
- Java 的泛型某種程度上可以算作偽泛型,它完全是一種編譯期的技巧,Java 編譯期會自動將型別轉換為對應的特定型別,這就決定了使用泛型,必須保證相應型別可以轉換為Object。
-
為何要引用基本資料包裝類
- 就比如,我們使用泛型,需要用到基本資料型別的包裝類。
- Java 的物件都是引用型別,如果是一個原始資料型別陣列,它在記憶體裡是一段連續的記憶體,而物件陣列則不然,資料儲存的是引用,物件往往是分散地儲存在堆的不同位置。這種設計雖然帶來了極大靈活性,但是也導致了資料操作的低效,尤其是無法充分利用現代 CPU 快取機制。
- Java 為物件內建了各種多型、執行緒安全等方面的支援,但這不是所有場合的需求,尤其是資料處理重要性日益提高,更加高密度的值型別是非常現實的需求。
1.0.1.9 new Integer(123) 與 Integer.valueOf(123)有何區別,請從底層實現分析兩者區別?
-
new Integer(123) 與 Integer.valueOf(123) 的區別在於:
- new Integer(123) 每次都會新建一個物件;
- Integer.valueOf(123) 會使用快取池中的物件,多次呼叫會取得同一個物件的引用。
Integer x = new Integer(123); Integer y = new Integer(123); System.out.println(x == y); // false Integer z = Integer.valueOf(123); Integer k = Integer.valueOf(123); System.out.println(z == k); // true
-
valueOf() 方法的實現比較簡單,就是先判斷值是否在快取池中,如果在的話就直接返回快取池的內容。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
-
在 Java 8 中,Integer 快取池的大小預設為 -128~127。
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; }
-
編譯器會在自動裝箱過程呼叫 valueOf() 方法,因此多個Integer例項使用自動裝箱來建立並且值相同,那麼就會引用相同的物件。
Integer m = 123; Integer n = 123; System.out.println(m == n); // true
-
基本型別對應的緩衝池如下:
- boolean values true and false
- all byte values
- short values between -128 and 127
- int values between -128 and 127
- char in the range u0000 to u007F
- 在使用這些基本型別對應的包裝型別時,就可以直接使用緩衝池中的物件。
其他介紹
01.關於部落格彙總連結
02.關於我的部落格
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 簡書:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:yangchong211@163.com
- 阿里雲部落格:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e
相關文章
- ios 基礎問題記錄iOS
- Python基礎面試題30問!Python基礎教程Python面試題
- 01.Android之基礎元件問題Android元件
- 前面基礎問題小總結(二)
- 【c++基礎】菱形繼承問題C++繼承
- 基礎問題:進位制轉換
- AI基礎:美女和野人過河問題AI
- 面試問題記錄 一 (基礎部分)面試
- 155個JavaScript基礎問題(1-5)JavaScript
- 基礎題
- 基礎問答
- 基礎學習-記憶體溢位問題記憶體溢位
- 【docker基礎知識】docker坑問題彙總Docker
- 面試題:Linux 系統基礎提問 (一)面試題Linux
- 前端基礎問題:CSS居中的幾種方式前端CSS
- 前端基礎問題:CSS vertical-align 屬性前端CSS
- AI數學基礎之:P、NP、NPC問題AI
- iOS面試中經常問的點 - 基礎問題(一)iOS面試
- 面試題-測試工程師常見的基礎問題面試題工程師
- Java基礎題Java
- python基礎題Python
- 基礎題二
- 基礎題一
- JAVA:Java基礎-敲程式碼碰到的小問題Java
- 那些經常被問的JAVA面試題(2)—— 基礎Java面試題
- BGP基礎(簡述)歡迎有問題補充
- Laravel 基礎學習 Homestead 環境 NPM err 問題LaravelNPM
- Java面試之Java基礎問題答案口述整理Java面試
- 小白愛上了Go之Go的基礎配置問題Go
- 【基礎】EM 還是 REM?這是一個問題!REM
- Spring基礎系列-Spring事務不生效的問題與迴圈依賴問題Spring
- 洛谷題單指南-數學基礎問題-P3601 簽到題
- 前端面試基礎網路問題(萬字長文)前端面試
- 問題 1462: [藍橋杯][基礎練習VIP]Huffuman樹
- 分享Java面試中的幾個重要基礎問題!Java面試
- 零基礎學UI設計會遇到哪些問題?UI
- 麵霸篇:高頻 Java 基礎問題(核心卷一)Java
- 自動化測試基礎之Python常見問題Python