轉角遇到愛_1,你不知道的Java 閒聊

閒聊

一時無聊,在簡書開一個文集,專門記錄一些技術上的偏門知識,少用但有用的知識。也不知道該取什麼名字,就叫轉角遇到愛吧。

有豐富開發經驗的人一定都有體會,真正在項目開發中,實現功能的時間其實並不長,大量的時間是浪費在瞭解決一些稀奇古怪的問題上,有很多冷門、少見的技術我們不知道,知道後才發現這麼神奇,就拿Android開發來說,你用300行代碼實現一個效果,可能一個神奇的屬性就搞定了。而這些冷門的知識我們不常用,用過一次之後很快又忘記了,下次遇到同樣的問題又要Google很久。這就是我決定寫這個系列的原因,把一些冷門的知識彙集起來,方便自己和大家查詢。

1、魔鬼死循環

boolean flag = false;
for (int i = Integer.MIN_VALUE; i <= Integer.MAX_VALUE; i++) {
    flag = !flag;
}
System.out.println(flag);

大多數人第一反應就是:flag初始爲false,執行奇數次就是true,偶數次就是false,所以問題就是for循環執行奇數次還是偶數次。

如果你是這麼想的,那你就上當了,這段代碼的執行結果是死循環。因爲Integer.MAX_VALUE + 1 = Integer.MIN_VALUE。Java中Integer類型值域範圍一旦超過就會回頭,變成最小值,是不是恍然大悟。

System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.MAX_VALUE + 1);

// 執行結果
-2147483648
-2147483648

2、Java7.0的try-with-resources

在 Java7之前,可以使用 finally 塊來確保資源被關閉,不管 try 語句正常地完成或是發生意外。

BufferedReader br = new BufferedReader(new FileReader(path));
try {
    return br.readLine();
} finally {
    if (br != null) br.close();
}

Java7之後,使用try-with-resources語句,當try退出時,會自動調用res.close()方法。不管代碼塊如何退出,只要之前已經被創建出來,它們都會被關閉。那什麼樣的資源會被關閉呢?任何實現了 java.lang.AutoCloseable的對象, 包括所有實現了 java.io.Closeable的對象, 都可以用作一個資源。

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
}

可以在一個 try-with-resources 語句中聲明一個或多個資源。資源的 close 方法調用順序與它們的創建順序相反。

try (
  File file = new File("");
  FileReader fr = new FileReader(file);
  BufferedReader br = new BufferedReader(fr);
) {
    // do someting
}

3、數字下劃線分割

我們在顯示大數金額時,通常會用逗號分隔符,便於清晰讀出數值。Java7之後,對於數字也支持下劃線分割。

long one_million = 1_000_000_000;
System.out.println(one_million);

// 輸出結果
1000000000

4、Math.abs()絕對值

你猜下面的代碼打印結果是什麼?

int i = Math.abs(Integer.MIN_VALUE);  
System.out.println(i)

結果還是Integer.MIN_VALUE,喫驚吧!

Math.abs()並不是一定會給你返回正數,原因很簡單,就是Integer的值域問題,Integer的最大值是2147483647,最小值是-2147483648,絕對值就是2147483648,還記得前面的那個魔鬼死循環嗎?
Integer.MAX_VALUE + 1 = Integer.MIN_VALUE;就是這麼無語。

5、ArrayList擴容

正常創建一個ArrayList你會這麼寫:

ArrayList a = new ArrayList();

對應ArrayList的無參構造函數:

public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

這裏有一個細節,ArrayList 底層採用 Object 類型的數組實現,當使用無參構造方法時,ArrayList 底層會生成一個長度爲 10 的 Object 類型數組,當向 ArrayList 添加對象時,計數加 1,並計算容量是否適當,當存儲的元素個數超過容量時,就會新建一個數組,新數組的長度是原來的1.5倍,然後把原來數組的內容拷貝大新數組。

注意,這個複製操作是非常傷性能的,如果 ArrayList 很大,執行數百次擴容,那麼就會進行更多次數的新數組分配操作,以及更多次數的舊數組回收操作。於是你就會發現性能越來越差,但是又不知道爲什麼。

正因爲如此,所以纔有了第二個構造函數,傳入一個指定值,作爲初始數組的大小。

public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    this.elementData = new Object[initialCapacity];
}

比如,你預期你的ArrayList至少會保存100個對象,那麼你就使用第二個構造函數,傳入100。這樣,前100次添加都不會有數組的拷貝操作。

ArrayList a = new ArrayList(100);

總結:如果你能預期ArrayList會保存大量的數據,那麼請使用第二個構造函數,傳入一個合適的值作爲初始容量,儘可能避免大量的性能消耗。

6、單例模式

說起單例模式,相信大多數人都能徒手寫出來,畢竟是最簡單的設計模式,所以我主要講一下4種單例寫法的遞進關係。

1、基礎形式
這是最簡單的形式,申明靜態實例的時候直接創建對象。

public class Singleton {
 
    private static Singleton instance = new Singleton();
 
    private Singleton(){}
 
    public static Singleton getInstance(){
        return instance;
    }
}

這中寫法的缺點很明顯,在初始化類的時候就創建了對象,如果我們沒有用到這個單例,那就是一種浪費。所以我們需要改進。

2、懶惰形式
比起基礎形式,這種形式的好處就是可以在需要的時候初始化實例。

public class Singleton {
 
    private Singleton(){}
 
    private static Singleton instance = null;
 
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

雖然解決了延遲初始化的問題,但是還有個明顯的問題就是線程不安全,所以還需要繼續優化。

3、方法鎖形式
在方法上使用synchronized關鍵字就可以處理多個線程同時訪問的問題。每個類實例對應一個線程鎖, synchronized 修飾的方法必須獲得調用該方法的類實例的鎖方能執行, 否則所屬線程阻塞。方法一旦執行, 就獨佔該鎖,直到從該方法返回時纔將鎖釋放。此後被阻塞的線程方能獲得該鎖, 重新進入可執行狀態。

public class Singleton {
 
    private Singleton(){}
 
    private static Singleton instance = null;
 
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

4、Class鎖形式
上面的寫法雖然是線程安全的,但是每次調用 getInstance() 方法都需要進行線程鎖定判斷,在多線程高併發訪問環境中,將會導致系統性能下降。事實上,不僅效率很低,99%情況下不需要線程鎖定判斷。這個時候,我們可以通過雙重校驗鎖的方式進行處理。換句話說,利用雙重校驗鎖,第一次檢查是否實例已經創建,如果還沒創建,再進行同步的方式創建單例對象。

public class Singleton {
 
    private Singleton(){}
 
    private static Singleton instance = null;
 
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }    
        }
        return instance;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章