Java程序員易踩的坑及解析
作為Java程序員,在日常開發中經常會遇到一些低級錯誤或者難以理解的情況。以下2個常見的問題,涉及到Java的基礎知識,這2個基礎知識小坑90%以上的程序員都踩過
1. ==號比較的坑
在比較Integer類型的對象時,一些程序員小伙伴可能會使用==來判斷它們是否相等。然而,這種用法并不總是正確的。例如,對于Integer對象,==比較的是對象的引用而非值,因此結果可能出乎意料。我們應該養成使用equals()方法來判斷兩個Integer對象是否相等的良好習慣
Integer status1 = new Integer(1);
Integer status2 = new Integer(1);
System.out.println(status1 == status2);
思考:返回結果是什么?
答案:false
我們小伙伴會說了,Java不是中為了節省內存和提高性能,會對一定范圍內的Integer對象進行緩存。范圍默認是在 -128 到 127 之間,怎么沒有生效?
我們來看一下Integer構造方法
public Integer(int value) {
this.value = value;
}
發現在Integer構造方法中并沒有使用緩存
思考:Integer緩存在哪里使用?
在Integer類的valueOf方法中
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
如果將代碼修改為如下:
Integer status1 = 1;
Integer status2 = 1;
System.out.println(status1 == status2);
思考:返回結果是什么?
答案:true
特別說明: Integer status1 = 1 會默認轉換為Integer status1 = Integer.valueOf(1)
編碼要養成良好習慣,盡量少用==判斷兩個Integer類型數據是否相等,而應該改成使用equals方法判斷:
Integer status1 = new Integer(1);
Integer status2 = new Integer(1);
System.out.println(status1.equals(status2));
輸出結果true
2. BigDecimal的坑
在一些業務場景(比如:倉庫數量,金額)需要設置成小數,此時字段類型應該定義成BigDecimal,而不是Double,避免丟失精度問題
Double amount1 = 0.02;
Double amount2 = 0.03;
System.out.println(amount2 - amount1);
思考:輸出結果會是0.1?
答案:不是輸出結果如下:
0.009999999999999998
原因如下:Double類型的兩個參數相減會轉換成二進制,Double有效位數為16位這就會出現存儲小數位數不夠的情況,這種情況下就會出現誤差
將上面代碼進行優化
BigDecimal amount1 = new BigDecimal(0.02);
BigDecimal amount2 = new BigDecimal(0.03);
System.out.println(amount2.subtract(amount1));
思考:結果會是0.1?不是的,輸出結果如下:
0.0099999999999999984734433411404097569175064563751220703125
思考:BigDecimal為啥還是丟失精度?
查看BigDecimal構造方法,注釋說明如下
/**
* 將 a double 轉換為 a BigDecimal ,它是 的二進制浮點值的精確十進制表示 double形式。返回 BigDecimal 的小數位數是最小值,因此 (10scale × val) 是整數。
* 筆記:
* 此構造函數的結果可能有些不可預測。人們可能會假設用 Java 編寫 new BigDecimal(0.1) 會創建一個 BigDecimal 完全等于 0.1(未縮放值為 1,小數位數為 1),但實際上它等于 0.1000000000000000000055511151231257827021181583404541015625。這是因為 0.1 不能完全表示為 a double (或者,就此而言,不能表示為任何有限長度的二進制分數)。因此,傳遞 給 構造函數的值并不完全等于 0.1,盡管外觀如此。
* String另一方面,構造函數是完全可預測的:正如人們所期望的那樣,寫入new BigDecimal("0.1")會創建一個BigDecimal完全等于 0.1 的構造函數。因此,通常建議優先使用 String 構造函數而不是此構造函數。
* 當 必須將 a double 用作 的源BigDecimal時,請注意,此構造函數提供精確的轉換;它不會給出與使用Double.toString(double)方法然后使用BigDecimal(String)構造函數將 轉換為 double a String 相同的結果。若要獲得該結果,請使用該staticvalueOf(double)方法。
* 參數:
* val – double 要轉換為 BigDecimal的值。
* 拋出:
* NumberFormatException – 如果 val 是無限或 NaN。
*/
public BigDecimal(double val) {
this(val,MathContext.UNLIMITED);
}
通過構造函數說明發現,使用BigDecimal構造函數初始化對象,也會丟失精度
思考:BigDecimal如何才能不丟失精度呢?
BigDecimal amount3 = new BigDecimal(String.valueOf(0.02));
BigDecimal amount4 = new BigDecimal(String.valueOf(0.03));
System.out.println(amount4.subtract(amount3));
使用BigDecimal.valueOf方法初始化BigDecimal類型參數,也能保證精度不丟失。在新版的阿里巴巴開發手冊中,也推薦使用這種方式創建BigDecimal參數。
BigDecimal amount1 = BigDecimal.valueOf(0.02);
BigDecimal amount2 = BigDecimal.valueOf(0.03);
System.out.println(amount2.subtract(amount1));