Java日常開發的21個坑,你踩過幾個?
阿新 • • 發佈:2020-12-27
### 前言
最近看了極客時間的《Java業務開發常見錯誤100例》,再結合平時踩的一些程式碼坑,寫寫總結,希望對大家有幫助,感謝閱讀~
### 1. 六類典型空指標問題
- 包裝型別的空指標問題
- 級聯呼叫的空指標問題
- Equals方法左邊的空指標問題
- ConcurrentHashMap 這樣的容器不支援 Key 和 Value 為 null。
- 集合,陣列直接獲取元素
- 物件直接獲取屬性
#### 1.1包裝型別的空指標問題
```
public class NullPointTest {
public static void main(String[] args) throws InterruptedException {
System.out.println(testInteger(null));
}
private static Integer testInteger(Integer i) {
return i + 1; //包裝型別,傳參可能為null,直接計算,則會導致空指標問題
}
}
```
#### 1.2 級聯呼叫的空指標問題
```
public class NullPointTest {
public static void main(String[] args) {
//fruitService.getAppleService() 可能為空,會導致空指標問題
fruitService.getAppleService().getWeight().equals("OK");
}
}
```
#### 1.3 Equals方法左邊的空指標問題
```
public class NullPointTest {
public static void main(String[] args) {
String s = null;
if (s.equals("666")) { //s可能為空,會導致空指標問題
System.out.println("公眾號:撿田螺的小男孩,666");
}
}
}
```
#### 1.4 ConcurrentHashMap 這樣的容器不支援 Key,Value 為 null。
```
public class NullPointTest {
public static void main(String[] args) {
Map map = new ConcurrentHashMap<>();
String key = null;
String value = null;
map.put(key, value);
}
}
```
#### 1.5 集合,陣列直接獲取元素
```
public class NullPointTest {
public static void main(String[] args) {
int [] array=null;
List list = null;
System.out.println(array[0]); //空指標異常
System.out.println(list.get(0)); //空指標一場
}
}
```
#### 1.6 物件直接獲取屬性
```
public class NullPointTest {
public static void main(String[] args) {
User user=null;
System.out.println(user.getAge()); //空指標異常
}
}
```
### 2. 日期YYYY格式設定的坑
日常開發,經常需要對日期格式化,但是呢,年份設定為YYYY大寫的時候,是有坑的哦。
反例:
```
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("2019-12-31 轉 YYYY-MM-dd 格式後 " + dtf.format(testDate));
```
執行結果:
```
2019-12-31 轉 YYYY-MM-dd 格式後 2020-12-31
```
**解析:**
為什麼明明是2019年12月31號,就轉了一下格式,就變成了2020年12月31號了?因為YYYY是基於周來計算年的,它指向當天所在周屬於的年份,一週從週日開始算起,週六結束,只要本週跨年,那麼這一週就算下一年的了。正確姿勢是使用yyyy格式。
![](https://imgkr2.cn-bj.ufileos.com/f9d23f03-215c-4d30-a19d-08bd5feb1c12.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=HuC%252F%252BLpyl4nGfl%252Bg%252BlAsxAfWvwM%253D&Expires=1609139925)
正例:
```
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("2019-12-31 轉 yyyy-MM-dd 格式後 " + dtf.format(testDate));
```
### 3.金額數值計算精度的坑
看下這個浮點數計算的例子吧:
```
public class DoubleTest {
public static void main(String[] args) {
System.out.println(0.1+0.2);
System.out.println(1.0-0.8);
System.out.println(4.015*100);
System.out.println(123.3/100);
double amount1 = 3.15;
double amount2 = 2.10;
if (amount1 - amount2 == 1.05){
System.out.println("OK");
}
}
}
```
執行結果:
```
0.30000000000000004
0.19999999999999996
401.49999999999994
1.2329999999999999
```
可以發現,結算結果跟我們預期不一致,其實是因為計算機是以二進位制儲存數值的,對於浮點數也是。對於計算機而言,0.1無法精確表達,這就是為什麼浮點數會導致精確度缺失的。因此,**金額計算,一般都是用BigDecimal 型別**
對於以上例子,我們改為BigDecimal,再看看執行效果:
```
System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
System.out.println(new BigDecimal(1.0).subtract(new BigDecimal(0.8)));
System.out.println(new BigDecimal(4.015).multiply(new BigDecimal(100)));
System.out.println(new BigDecimal(123.3).divide(new BigDecimal(100)));
```
執行結果:
```
0.3000000000000000166533453693773481063544750213623046875
0.1999999999999999555910790149937383830547332763671875
401.49999999999996802557689079549163579940795898437500
1.232999999999999971578290569595992565155029296875
```
發現結果還是不對,**其實**,使用 BigDecimal 表示和計算浮點數,必須使用**字串的構造方法**來初始化 BigDecimal,正例如下:
```
public class DoubleTest {
public static void main(String[] args) {
System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
System.out.println(new BigDecimal("1.0").subtract(new BigDecimal("0.8")));
System.out.println(new BigDecimal("4.015").multiply(new BigDecimal("100")));
System.out.println(new BigDecimal("123.3").divide(new BigDecimal("100")));
}
}
```
在進行金額計算,使用BigDecimal的時候,我們還需要**注意BigDecimal的幾位小數點,還有它的八種舍入模式哈**。
### 4. FileReader預設編碼導致亂碼問題
看下這個例子:
```
public class FileReaderTest {
public static void main(String[] args) throws IOException {
Files.deleteIfExists(Paths.get("jay.txt"));
Files.write(Paths.get("jay.txt"), "你好,撿田螺的小男孩".getBytes(Charset.forName("GBK")));
System.out.println("系統預設編碼:"+Charset.defaultCharset());
char[] chars = new char[10];
String content = "";
try (FileReader fileReader = new FileReader("jay.txt")) {
int count;
while ((count = fileReader.read(chars)) != -1) {
content += new String(chars, 0, count);
}
}
System.out.println(content);
}
}
```
執行結果:
```
系統預設編碼:UTF-8
���,�����ݵ�С�к�
```
從執行結果,可以知道,系統預設編碼是utf8,demo中讀取出來,出現亂碼了。為什麼呢?
> FileReader 是以當**前機器的預設字符集**來讀取檔案的,如果希望指定字符集的話,需要直接使用 InputStreamReader 和 FileInputStream。
正例如下:
```
public class FileReaderTest {
public static void main(String[] args) throws IOException {
Files.deleteIfExists(Paths.get("jay.txt"));
Files.write(Paths.get("jay.txt"), "你好,撿田螺的小男孩".getBytes(Charset.forName("GBK")));
System.out.println("系統預設編碼:"+Charset.defaultCharset());
char[] chars = new char[10];
String content = "";
try (FileInputStream fileInputStream = new FileInputStream("jay.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, Charset.forName("GBK"))) {
int count;
while ((count = inputStreamReader.read(chars)) != -1) {
content += new String(chars, 0, count);
}
}
System.out.println(content);
}
}
```
### 5. Integer快取的坑
```
public class IntegerTest {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println("a==b:"+ (a == b));
Integer c = 128;
Integer d = 128;
System.out.println("c==d:"+ (c == d));
}
}
```
執行結果:
```
a==b:true
c==d:false
```
為什麼Integer值如果是128就不相等了呢?**編譯器會把 Integer a = 127 轉換為 Integer.valueOf(127)。** 我們看下原始碼。
```
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
```
可以發現,i在一定範圍內,是會返回快取的。
> 預設情況下呢,這個快取區間就是[-128, 127],所以我們業務日常開發中,如果涉及Integer值的比較,需要注意這個坑哈。還有呢,設定 JVM 引數加上 -XX:AutoBoxCacheMax=1000,是可以調整這個區間引數的,大家可以自己試一下哈
### 6. static靜態變數依賴spring例項化變數,可能導致初始化出錯
之前看到過類似的程式碼。靜態變數依賴於spring容器的bean。
```
private static SmsService smsService = SpringContextUtils.getBean(SmsService.class);
```
這個靜態的smsService有可能獲取不到的,因為類載入順序不是確定的,正確的寫法可以這樣,如下:
```
private static SmsService smsService =null;
//使用到的時候採取獲取
public static SmsService getSmsService(){
if(smsService==null){
smsService = SpringContextUtils.getBean(SmsService.class);
}
return smsService;
}
```
### 7. 使用ThreadLocal,執行緒重用導致資訊錯亂的坑
使用ThreadLocal快取資訊,有可能出現資訊錯亂的情況。看下下面這個例子吧。
```
private static final Thr