1. 程式人生 > >Java日常開發的21個坑,你踩過幾個?

Java日常開發的21個坑,你踩過幾個?

### 前言 最近看了極客時間的《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