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()&notifyAll(): 喚醒一個(所有)正處于等待狀態的線程
    • 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<Cat>卻不是List<Animal>的子類型,而在程序中往往需要表達這樣的邏輯關系。為了解決這種類似的場景,在泛型的參數類型的基礎上新增了通配符的用法。
  • <? extends T> 上界通配符
    • 上界通配符顧名思義,<? extends T>表示的是類型的上界【 包含自身】,因此通配的參數化類型可能是T或T的子類。正因為無法確定具體的類型是什么,add方法受限(可以添加null,因為null表示任何類型),但可以從列表中獲取元素后賦值給父類型。如上圖中的第一個例子,第三個add()操作會受限,原因在于List<Animal>和List<Cat>是List<? extends Animal>的子類型。
  • <? super T> 下界通配符
    • 下界通配符<? super T>表示的是參數化類型是T的超類型(包含自身),層層至上,直至Object,編譯器無從判斷get()返回的對象的類型是什么,因此get()方法受限。但是可以進行add()方法,add()方法可以添加T類型和T類型的子類型,如第二個例子中首先添加了一個Cat類型對象,然后添加了兩個Cat子類類型的對象,這種方法是可行的,但是如果添加一個Animal類型的對象,顯然將繼承的關系弄反了,是不可行的。
  • <?> 無界通配符
    • 任意類型,如果沒有明確,那么就是Object以及任意的Java類了
    • 無界通配符用<?>表示,?代表了任何的一種類型,能代表任何一種類型的只有null(Object本身也算是一種類型,但卻不能代表任何一種類型,所以List<Object>和List<null>的含義是不同的,前者類型是Object,也就是繼承樹的最上層,而后者的類型完全是未知的)。
    • 技術博客大總結

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.關于我的博客

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容