99%的Java程序員都會(huì)寫這樣的垃圾代碼
在軟件開發(fā)領(lǐng)域,編程經(jīng)驗(yàn)往往與設(shè)計(jì)、編碼、重構(gòu)和測(cè)試的能力相輔相成。隨著時(shí)間的推移,這些技能的提升使您能夠在日常工作中脫穎而出。然而,有時(shí)候我們可能陷入固定的編程模式,導(dǎo)致編碼習(xí)慣停滯不前。在這篇文章中,我們將介紹10個(gè)Java編程習(xí)慣,它們可以幫助您提高編碼技能,寫出更加干凈、健壯的Java代碼。
1、調(diào)用equals()方法時(shí)使用String字面值或已知對(duì)象
這條肯定中!很多人以為由于equals()方法是對(duì)稱的,因此調(diào)用 a.equals(b) 與調(diào)用b.equals(a)相同的,所以習(xí)慣性的這樣寫:
if (givenString.equals("YES")){
// 執(zhí)行一些操作
}
盡管這種寫法在可讀性上有優(yōu)勢(shì),但它并不安全。如果givenString為null,那么這段代碼將拋出NullPointerException。為了避免這種情況,我們應(yīng)該將equals方法調(diào)用放在已知對(duì)象的一側(cè),如下所示:
"YES".equals(givenString)
這樣,如果givenString為null,它將返回false,而不會(huì)拋出異常。這是一種更加安全和健壯的編碼習(xí)慣,同時(shí)也是避免NullPointerException的一種流行方式。
2、使用entrySet遍歷HashMap
在遍歷HashMap時(shí),我們通常會(huì)使用鍵集合(key set)來獲取鍵,并通過鍵獲取對(duì)應(yīng)的值。例如:
Set<Key> keySet = map.keySet();
for (Key k : keySet){
Value v = map.get(k);
System.out.println(k + ": " + v);
}
然而,這種方法需要進(jìn)行兩次查找操作,可能會(huì)導(dǎo)致性能下降。如果我們需要同時(shí)訪問鍵和值,更好的方式是使用entrySet,如下:
Set<Map.Entry<Key, Value>> entrySet = map.entrySet();
for (Map.Entry<Key, Value> entry : entrySet){
Key k = entry.getKey();
Value v = entry.getValue();
System.out.println(k + ": " + v);
}
這種方式效率更高,因?yàn)樗苯訌膃ntry對(duì)象中獲取值,而不需要再次查找。
3、使用枚舉作為單例
想象一下,您需要?jiǎng)?chuàng)建一個(gè)線程安全的單例模式。以前,這可能需要大量的代碼和同步操作。但現(xiàn)在,您可以使用Java的枚舉類型,僅需一行代碼即可創(chuàng)建一個(gè)線程安全的單例:
public enum Singleton{
INSTANCE;
}
這個(gè)枚舉實(shí)例在多線程環(huán)境下也能保證只有一個(gè)實(shí)例存在,即使在序列化和反序列化過程中也是如此。這是一種簡潔而強(qiáng)大的單例模式的實(shí)現(xiàn)方式。
4、使用Arrays.asList()或List.of()初始化集合
在Java中,初始化集合時(shí),我們通常會(huì)逐個(gè)添加元素。例如:
List<String> listOfCurrencies = new ArrayList<>();
listOfCurrencies.add("USD/AUD");
listOfCurrencies.add("USD/JPY");
listOfCurrencies.add("USD/INR");
這種方法雖然有效,但相對(duì)繁瑣。您可以使用Arrays.asList()方法以更簡潔的方式初始化集合,如下:
List<String> listOfPairs = new ArrayList<>(Arrays.asList("USD/AUD", "USD/JPY", "USD/INR"));
此外,從Java 9開始,您還可以使用List.of()和Set.of()方法來創(chuàng)建不可變的列表和集合。這些方法提供了更好的不可變性保證。
List<String> newList = List.of("One", "Two", "Infinity");
5、在循環(huán)中檢查wait()條件
當(dāng)我們使用wait()、notify()和notifyAll()方法進(jìn)行多線程通信時(shí),通常會(huì)在if語句中檢查等待條件,然后調(diào)用wait()。例如:
synchronized(queue) {
if(queue.isFull()){
queue.wait();
}
}
然而,這種寫法存在一個(gè)問題,即可能會(huì)發(fā)生虛假通知(spurious notification)。為了解決這個(gè)問題,我們應(yīng)該將檢查等待條件的操作放在一個(gè)while循環(huán)內(nèi),如下:
synchronized(queue) {
while(queue.isFull()){
queue.wait();
}
}
這樣,即使在通知之前等待條件再次被滿足,我們的代碼也可以正確地工作。
6、捕獲CloneNotSupportedException并返回子類實(shí)例
在Java中,對(duì)象克隆的實(shí)現(xiàn)機(jī)制常常受到批評(píng),因?yàn)樗男阅懿患?。如果您需要?shí)現(xiàn)clone()方法,可以使用以下習(xí)慣來減輕這種痛苦:
public Course clone() {
Course c = null;
try {
c = (Course)super.clone();
} catch (CloneNotSupportedException e) {} // 不會(huì)發(fā)生
return c;
}
這個(gè)習(xí)慣利用了clone()方法實(shí)際上不會(huì)拋出CloneNotSupportedException的事實(shí),只要類實(shí)現(xiàn)了Cloneable接口。這種方式返回了子類的實(shí)例,被稱為協(xié)變方法覆蓋(covariant method overriding),從Java 5開始支持。這可以減少客戶端代碼中的類型強(qiáng)制轉(zhuǎn)換,使代碼更加清晰。
7、在可能的情況下使用接口
在定義方法的返回類型、變量類型或方法參數(shù)類型時(shí),應(yīng)盡量使用接口而不是具體
的類。例如,不要這樣寫:
ArrayList<Integer> listOfNumbers = new ArrayList<>();
public ArrayList<Integer> getNumbers(){
return listOfNumbers;
}
public void setNumbers(ArrayList<Integer> numbers){
listOfNumbers = numbers;
}
而應(yīng)該這樣寫:
List<Integer> listOfNumbers;
public List<Integer> getNumbers(){
return listOfNumbers;
}
public void setNumbers(List<Integer> numbers){
listOfNumbers = numbers;
}
這種方式提供了更大的靈活性,可以傳遞不同的集合實(shí)現(xiàn)。您還可以使用泛型中的通配符擴(kuò)展功能,進(jìn)一步提高靈活性。
public void processList(List<? extends Number> numbers){
// 執(zhí)行操作
}
8、使用迭代器遍歷List
在Java中,有多種遍歷List的方法,包括使用索引的for循環(huán)、增強(qiáng)的for循環(huán)和迭代器。最佳實(shí)踐是使用迭代器,因?yàn)樗且环N安全且能夠防止不可預(yù)測(cè)行為的方法:
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String name = iterator.next();
// 執(zhí)行操作
}
使用迭代器的好處包括能夠遍歷不同實(shí)現(xiàn)的List,例如ArrayList和LinkedList,同時(shí)避免了多線程環(huán)境下的問題。
9、在編寫代碼時(shí)考慮依賴注入
在以前的編程實(shí)踐中,我們常常會(huì)硬編碼依賴關(guān)系,例如:
public Game {
private HighScoreService service = HighScoreService.getInstance();
public void showLeaderBoard(){
List<Player> listOfTopPlayers = service.getLeaderBoard();
System.out.println(listOfTopPlayers);
}
}
這種方式使Game類與HighScoreService類緊密耦合,不容易進(jìn)行單元測(cè)試,因?yàn)楸仨毷褂肏ighScoreService的實(shí)際實(shí)現(xiàn)。為了避免這個(gè)問題,我們應(yīng)該使用依賴注入,將依賴作為構(gòu)造函數(shù)參數(shù)傳遞:
public Game {
private HighScoreService service;
public Game(HighScoreService service){
this.service = service;
}
public void showLeaderBoard(){
List<Player> listOfTopPlayers = service.getLeaderBoard();
System.out.println(listOfTopPlayers);
}
}
這種方式使代碼更加可測(cè)試,可以輕松地使用模擬對(duì)象進(jìn)行測(cè)試。
10、在它們自己的try塊中關(guān)閉流
在處理輸入流和輸出流時(shí),我們經(jīng)常需要進(jìn)行異常處理和關(guān)閉操作。以前,我們可能會(huì)這樣寫:
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream("application.json");
os = new FileOutputStream("application.log");
} catch (IOException io) {
} finally {
is.close();
os.close();
}
然而,這種方式存在一個(gè)問題,如果第一個(gè)流的操作拋出異常,第二個(gè)流將永遠(yuǎn)不會(huì)被關(guān)閉。為了解決這個(gè)問題,我們可以使用Java 7引入的try-with-resources語法,更加簡潔地處理流的關(guān)閉操作:
try (InputStream is = new FileInputStream("application.json");
OutputStream os = new FileOutputStream("application.log")) {
// 讀取輸入流并寫入輸出流的操作
} catch (IOException e) {
// 異常處理代碼
}
使用try-with-resources后,不需要再手動(dòng)關(guān)閉流,它們會(huì)在try塊結(jié)束時(shí)自動(dòng)關(guān)閉。這樣的寫法更加簡潔和安全。
結(jié)語
這些Java編程習(xí)慣可以幫助您寫出更加高效、健壯的Java代碼,提高編碼技能。如果您是剛剛開始學(xué)習(xí)Java或已經(jīng)有一到兩年的經(jīng)驗(yàn),這些習(xí)慣將為您打開Java編程的新視角。隨著Java版本的不斷更新,一些習(xí)慣可能會(huì)被更好的API方法所取代,但掌握它們?nèi)匀槐炔徽莆崭?。如果您還知道或遵循其他Java編程習(xí)慣,歡迎在評(píng)論中分享,我們期待從有經(jīng)驗(yàn)的讀者那里學(xué)習(xí)。希望這些習(xí)慣對(duì)您的Java編程之旅有所幫助。