更新時間:2022-03-17 16:35:08 來源:動力節點 瀏覽1729次
Java是一種復雜的編程語言,在很長一段時間內一直主導著許多生態系統。可移植性、自動垃圾收集及其溫和的學習曲線使其成為軟件開發中的絕佳選擇。但是,與任何其他編程語言一樣,它仍然容易受到開發人員錯誤的影響。本文探討了Java開發人員最常犯的幾個錯誤以及避免這些錯誤的一些方法。
1:忽略現有庫
對于Java開發人員來說,忽略無數用Java編寫的庫絕對是一個錯誤。在重新發明輪子之前,請嘗試搜索可用的庫——其中許多庫在它們存在的多年中已經過完善并且可以免費使用。這些可能是日志庫,如logback和Log4j,或網絡相關庫,如Netty或Akka。一些庫,例如Joda-Time,已經成為事實上的標準。
以下是我之前的一個項目的個人經驗。負責HTML轉義的代碼部分是從頭開始編寫的。它多年來一直運行良好,但最終它遇到了用戶輸入,導致它陷入無限循環。用戶發現服務沒有響應,嘗試使用相同的輸入重試。最終,服務器上為這個應用程序分配的所有CPU都被這個無限循環占用了。如果這個簡單的HTML轉義工具的作者決定使用可用于HTML轉義的知名庫之一,例如來自Google Guava的HtmlEscapers,這可能不會發生。至少,對于大多數有社區支持的流行庫來說,錯誤會被社區更早地發現并修復。
2:在Switch-Case塊中缺少“break”關鍵字
這些Java問題可能非常令人尷尬,有時直到在生產環境中運行才被發現。switch語句中的失敗行為通常很有用;但是,如果不希望出現這種行為,則缺少“break”關鍵字可能會導致災難性的結果。如果你在下面的代碼示例中忘記在“case 0”中添加“break”,程序將寫“Zero”后跟“One”,因為這里的控制流會遍歷整個“switch”語句,直到它達到了“休息”。例如:
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)檢測諸如此類的Java錯誤。
3:忘記釋放資源
每次程序打開文件或網絡連接時,Java初學者在完成使用后釋放資源非常重要。如果在對此類資源的操作期間引發任何異常,則應采取類似的謹慎態度。有人可能會爭辯說FileInputStream有一個終結器,它在垃圾收集事件上調用close()方法。但是,由于我們無法確定垃圾回收周期何時開始,輸入流可能會無限期地消耗計算機資源。事實上,Java 7中特別針對這種情況引入了一個非常有用且簡潔的語句,稱為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();
}
}
}
此語句可用于任何實現AutoClosable接口的對象。它確保在語句結束時關閉每個資源。
4:內存泄漏
Java使用自動內存管理,雖然忘記手動分配和釋放內存是一種解脫,但這并不意味著Java初學者不應該知道內存在應用程序中是如何使用的。內存分配問題仍然存在。只要程序創建了對不再需要的對象的引用,它就不會被釋放。在某種程度上,我們仍然可以稱之為內存泄漏。Java中的內存泄漏可能以多種方式發生,但最常見的原因是持久的對象引用,因為垃圾收集器無法在仍然存在對它們的引用時從堆中刪除對象。可以通過使用包含一些對象集合的靜態字段定義類來創建這樣的引用,并且在不再需要該集合后忘記將該靜態字段設置為null。
這種內存泄漏背后的另一個潛在原因是一組對象相互引用,導致循環依賴,因此垃圾收集器無法決定是否需要這些具有交叉依賴引用的對象。另一個問題是使用JNI時非堆內存中的泄漏。
原始泄漏示例可能如下所示:
final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
final Deque<BigDecimal> 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);
scheduledExecutorService.scheduleAtFixedRate(() -> {
numbers.add(new BigDecimal(System.currentTimeMillis()));
}, 10, 10, TimeUnit.MILLISECONDS);
try {
scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
此示例創建兩個計劃任務。第一個任務從名為“numbers”的雙端隊列中獲取最后一個數字,并打印數字和雙端隊列大小,以防該數字可被51整除。第二個任務將數字放入雙端隊列。這兩個任務都以固定的速率安排,每10毫秒運行一次。如果代碼被執行,你會看到雙端隊列的大小一直在增加。這最終將導致雙端隊列被消耗所有可用堆內存的對象填充。為了在保留該程序的語義的同時防止這種情況,我們可以使用不同的方法從雙端隊列中獲取數字:“pollLast”。與“peekLast”方法相反,“pollLast”返回元素并將其從雙端隊列中刪除,而“peekLast”只返回最后一個元素。
以上就是動力節點小編介紹的"零基礎Java學習教程中常見的四條大錯",希望對大家有幫助,如有疑問,請在線咨詢,有專業老師隨時為您服務。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習