怎麼又出錯了?盤點java中最容易出現的錯誤。

現現在,java已經普遍應用各類軟件開發領域。基於面向對象的設計,java屏蔽了諸如C,C++等語言的一些複雜性,提供了垃圾回收機制,平臺無關的虛擬機技術,Java創造了一種史無前例的開發方式。因此,java對比其餘程序語言更加受歡迎。所以,Java中的異常也是隨時發生,下面我就列出了我認爲的Java開發最容易出現的10個錯誤。java

一、重複造輪子
一個明顯的錯誤就是Java程序員習慣性的忽略已經存在的大量的庫。在你決定造一個輪子之間,我建議你試着先搜一下是否有已經存在庫。例如日誌方面,有logback,新log4j,網絡方面,有Netty或者Akka。有一些庫,已經逐步變成了標準,好比Java8中加入的Joda-Time。
下面講述的是我上一個項目中的我的經歷。有一部分用於HTML轉義的代碼是一個開發本身完成的。這個代碼正常工做了多年,可是又一次遇到了一個用戶輸入,代碼陷入了死循環。這個用戶發現應用沒有反應,又從新輸入了一遍,服務器由於這個死循環掛了。若是這個開發使用已有的HTML轉義工具,好比Google Guava項目提供的HtmlEscaper,這個嚴重的問題可能就不會出現。而且如今市面上流行的大部分的開源庫,背後都有團隊和社區在支持,相似這樣的錯誤,都可以及時的被修復。
二、在Switch-Case中錯誤的使用break
這是一個很尷尬的問題,可是仍然在實際開發中常常出現。瀑布特性在switch語句中有時會很是有用,可是必要的break關鍵字的缺失,有時會帶來災難性的後果。好比在下面的代碼中,若是在case 0中忘記放一個break關鍵字,代碼會繼續向下執行,就會在Zero以後再輸出一個One:
public static void switchCasePrimer() {
int caseIndex = 0;
switch (caseIndex) {
case 0:
System.out.println("Zero");
case 1:
System.out.println("One");
break;
case 2:
System.out.println("Two");
break;
default:
System.out.println("Default");
}
}程序員

最好的解決辦法是使用多態,並把不一樣的處理代碼放到子類中。固然,相似這樣的錯誤,也能夠經過相似FindBugs或者PMD這樣的工具檢查出來。
三、忘記釋放資源
一旦打開一個文件,或者創建一個網絡鏈接,一個很是重要的習慣是記得關閉資源。而且必定記得,若是在使用相似這樣的資源過程當中出現了錯誤,在異常處理中,也須要作對應的關閉操做。可能有人會說,FileInputStream對象在GC的時候,Java終結器(finalizer)會自動調用其close()方法,可是咱們知道,咱們沒法預知GC在何時開始,因此咱們沒法預知在執行GC以前,會有多少資源沒法及時關閉。爲了不這種狀況,Java7推出的try-with-resources語法,是值得每一個開發使用的。
private static void printFileJava7() throws IOException {
try(FileInputStream input = new FileInputStream("file.txt")) {
int data = input.read();
while(data != -1){
System.out.print((char) data);
data = input.read();
}
}
}編程

try-with-resources語法適用於全部實現了AutoClosable接口的類。它能保證每個資源及時的關閉。
四、內存泄露
Java使用自動內存管理,因此大部分時間,咱們都不會去關心內存的分配和釋放,可是,這並不意味着Java開發人員須要忽略內存。在Java應用中,內存的問題也常常出現。咱們知道,對象若是沒有被引用了,這個對象就會被釋放,可是並不意味着,就不會出現內存泄露的問題。在Java中,形成內存泄露的緣由有不少,但最容易出現的狀況就是對象引用沒法釋放,由於GC在回收堆內存的時候,若是一個對象仍然被其餘對象引用,這個對象空間是不會被回收的,舉個例子,若是在類中,有一個靜態字段引用到一個集合,假如咱們沒有手動的在使用完成這個集合以後,將他設置爲null,那麼這個集合及這個集合中的對象,是永遠不會被回收的,由於類靜態字段是不會被GC的。
好比還有一種形成內存泄露的緣由,就是一組對象互相引用對方,就是咱們常常說的循環引用,由於循環引用,因此GC不能肯定這些互相引用的對象是否還有繼續存活的必要。還有一種狀況,就是使用JNI時的非堆內存泄露。
一個典型的內存泄露例子:
final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);final Deque numbers = new LinkedBlockingDeque<>();final BigDecimal divisor = new BigDecimal(51);服務器

scheduledExecutorService.scheduleAtFixedRate(() -> {
BigDecimal number = numbers.peekLast();
if (number != null && number.remainder(divisor).byteValue() == 0) {
System.out.println("Number: " + number);
System.out.println("Deque size: " + numbers.size());
}
}, 10, 10, TimeUnit.MILLISECONDS);markdown

scheduledExecutorService.scheduleAtFixedRate(() -> {
    numbers.add(new BigDecimal(System.currentTimeMillis()));
}, 10, 10, TimeUnit.MILLISECONDS);

try {
scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}網絡

在上面的例子中,咱們建立了兩個定時任務。第一個定時任務,從deque中獲取了最後的一個數字」numbers」並判斷,若是這個數字能被51整除,則打印該數字和deque的大小。第二個定時任務,不斷的向deque中添加數據。兩個任務都間隔10ms執行。若是這個代碼執行,你會發現,deque的大小會持續的增長,直到deque中的數據佔滿整個堆空間。爲了阻止這種狀況的發生,咱們可使用pollLast方法來代替peekLast方法,由於pollLast方法會在拿到最後一個元素以後,把這個元素從deque中移除。
五、過分產生垃圾數據
過分產生垃圾數據的意思,是程序運行中大量產生短聲明週期的對象。這回致使GC頻繁的執行,從內存中回收空間,GC的執行是須要完成堆掃描的,這對系統的性能影響是很是大的。下面是一個小例子:
String oneMillionHello = "";for (int i = 0; i < 1000000; i++) {
oneMillionHello = oneMillionHello + "Hello!";
}
System.out.println(oneMillionHello.substring(0, 6));app

在Java中,字符串是不可變的,因此每一次循環都會建立一個新的字符串對象。爲了改進這種代碼,咱們可使用StringBuilder來代替:
StringBuilder oneMillionHelloSB = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
oneMillionHelloSB.append("Hello!");
}
System.out.println(oneMillionHelloSB.toString().substring(0, 6));ide

第二個版本的代碼,在執行的時候會提升很多的性能。工具

可能這些問題在你們的編程過程當中常常出現,可是卻沒有總結起來,它們其實都是很重要的。將這些問題所有解決,對你的編程能力的提高必定是顯而易見的。接下來,咱們會繼續總結出你們容易出現的其餘問題,助你成爲java大神。性能

相關文章
相關標籤/搜索