App下載

超越常見陷阱:Java開發(fā)人員五大致命錯誤

孫尚香 2023-12-08 10:06:53 瀏覽數(shù) (1269)
反饋

Java是一種廣泛使用的編程語言,它具有跨平臺、面向?qū)ο?、高性能等特點(diǎn)。但即使對于經(jīng)驗豐富的開發(fā)人員,也常常會犯一些致命的錯誤。這些錯誤可能導(dǎo)致代碼質(zhì)量下降、性能問題或安全漏洞。本文將揭示Java開發(fā)人員常犯的五大致命錯誤,并提供了寶貴的建議,助您避免陷入這些錯誤,提升代碼質(zhì)量和開發(fā)效率。

1. 使用Objects.equals比較對象

?Objects.equals?是Java 7提供的一個靜態(tài)方法,它可以用來比較兩個對象是否相等,同時避免空指針異常。這個方法看起來很方便,但是如果使用不當(dāng),可能會導(dǎo)致意想不到的結(jié)果。例如,下面的代碼中,使用?Objects.equals?比較一個Long類型的變量和一個int類型的常量,結(jié)果卻是false。

Long longValue = 123L;
System.out.println(longValue == 123); // true
System.out.println(Objects.equals(longValue, 123)); // false

?Objects.equals?方法內(nèi)部會先判斷兩個參數(shù)是否為同一對象,如果不是,再調(diào)用第一個參數(shù)的?equals?方法比較第二個參數(shù)。而Long類的?equals?方法會先判斷參數(shù)是否為Long類型,如果不是,直接返回?false?。所以,當(dāng)我們使用?Objects.equals?比較一個Long類型的變量和一個int類型的常量時,實際上是調(diào)用了Long類的?equals?方法,而這個方法會認(rèn)為兩個參數(shù)類型不同,所以返回false。要避免這個錯誤,我們需要注意以下幾點(diǎn):

  • 當(dāng)比較基本類型和包裝類型時,盡量使用?==?運(yùn)算符,因為它會自動進(jìn)行拆箱和類型轉(zhuǎn)換,而不會出現(xiàn)類型不匹配的問題。
  • 當(dāng)比較兩個包裝類型時,盡量保證它們的類型一致,或者使用相應(yīng)的?parse?方法將它們轉(zhuǎn)換為基本類型再比較。
  • 當(dāng)比較自定義類型時,盡量重寫?equals?方法和?hashCode?方法,以實現(xiàn)合理的相等判斷邏輯。

2. 日期格式錯誤

在Java中,我們經(jīng)常需要對日期進(jìn)行格式化,以便在不同的場景中顯示或存儲。為了實現(xiàn)日期格式化,我們通常會使用?DateTimeFormatter?類,它可以根據(jù)指定的模式將日期轉(zhuǎn)換為字符串,或者將字符串轉(zhuǎn)換為日期。然而,如果我們使用錯誤的模式,可能會導(dǎo)致日期格式化出現(xiàn)錯誤。例如,下面的代碼中,使用YYYY-MM-dd模式格式化一個Instant對象,結(jié)果卻得到了錯誤的年份。

Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
                                               .withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant)); // 2022-12-31 08:00:00

?DateTimeFormatter?類中的模式字母YYYY和yyyy有細(xì)微的差別。它們都表示年份,但是yyyy表示日歷年,而YYYY表示周年。日歷年是按照公歷的規(guī)則劃分的,而周年是按照ISO 8601標(biāo)準(zhǔn)劃分的,它的第一周是包含1月4日的那一周,而最后一周是包含12月28日的那一周。所以,當(dāng)我們使用YYYY-MM-dd模式格式化一個Instant對象時,實際上是使用了周年的年份,而不是日歷年的年份。而12月31日屬于下一年的第一周,所以得到了錯誤的年份。要避免這個錯誤,我們需要注意以下幾點(diǎn):

  • 當(dāng)使用?DateTimeFormatter?類格式化日期時,盡量使用yyyy表示年份,而不是YYYY,除非我們確實需要使用周年的概念。
  • 當(dāng)使用?DateTimeFormatter?類解析字符串時,盡量保證字符串的格式和模式的格式一致,否則可能會出現(xiàn)解析異常或錯誤的日期。
  • 當(dāng)使用?DateTimeFormatter?類進(jìn)行日期轉(zhuǎn)換時,盡量指定時區(qū),否則可能會出現(xiàn)時差的問題。

3. 在ThreadPool中使用ThreadLocal

?ThreadLocal?是一種特殊的變量,它可以為每個線程提供一個獨(dú)立的副本,從而實現(xiàn)線程間的隔離。使用?ThreadLocal?可以避免一些線程安全的問題,也可以提高一些性能。然而,如果我們在使用線程池的情況下使用?ThreadLocal?,就要小心了,因為這可能會導(dǎo)致一些意想不到的結(jié)果。例如,下面的代碼中,使用?ThreadLocal?保存用戶信息,然后在線程池中執(zhí)行一個任務(wù),發(fā)送郵件給用戶。

private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);
private ExecutorService executorService = Executors.newFixedThreadPool(4);

public void executor() {
    executorService.submit(() -> {
        User user = currentUser.get();
        Integer userId = user.getId();
        sendEmail(userId);
    });
}

這段代碼看起來沒有什么問題,但是實際上有一個隱藏的bug。因為我們使用了線程池,線程是可以復(fù)用的,所以在使用?ThreadLocal?獲取用戶信息的時候,很可能會誤獲取到別人的信息。這是因為?ThreadLocal?的副本是綁定在線程上的,而不是綁定在任務(wù)上的,所以當(dāng)一個線程執(zhí)行完一個任務(wù)后,它的?ThreadLocal?的副本并不會被清除,而是會被下一個任務(wù)使用。這樣就可能導(dǎo)致數(shù)據(jù)混亂或安全漏洞。要避免這個錯誤,我們需要注意以下幾點(diǎn):

  • 當(dāng)使用?ThreadLocal?時,盡量在每次使用完后調(diào)用?remove?方法,以清除線程的副本,避免對下一個任務(wù)造成影響。
  • 當(dāng)使用線程池時,盡量不要使用?ThreadLocal?,而是使用其他的方式來傳遞數(shù)據(jù),例如使用參數(shù)或返回值。
  • 當(dāng)使用線程池時,盡量使用自定義的線程工廠,以便在創(chuàng)建線程時設(shè)置一些初始化的操作,或者在回收線程時設(shè)置一些清理的操作。

4. 使用HashSet去除重復(fù)數(shù)據(jù)

在編程的時候,我們經(jīng)常會遇到去重的需求,即從一個集合中去除重復(fù)的元素,只保留唯一的元素。為了實現(xiàn)去重,我們通常會使用?HashSet?,它是一種基于哈希表的集合,它可以保證元素的唯一性,同時具有較高的查詢效率。然而,如果我們使用?HashSet?去重時不注意一些細(xì)節(jié),可能會導(dǎo)致去重失敗。例如,下面的代碼中,使用?HashSet?去重一個?User?對象的列表,結(jié)果卻沒有去除重復(fù)的對象。

User user1 = new User();
user1.setUsername("test");
User user2 = new User();
user2.setUsername("test");
List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size()); // the size is 2

?HashSet?的去重機(jī)制是基于對象的?hashCode?方法和?equals?方法的。當(dāng)我們向?HashSet?中添加一個對象時,它會先計算對象的哈希碼,然后根據(jù)哈希碼找到對應(yīng)的桶,再在桶中遍歷元素,使用?equals?方法判斷是否有相同

5.在List中循環(huán)刪除元素

這是一個很常見的錯誤,很多開發(fā)人員都會在使用?List?的?foreach?循環(huán)時,試圖在循環(huán)體中刪除元素,這樣做會導(dǎo)致?ConcurrentModificationException?異常,因為在迭代過程中修改了集合的結(jié)構(gòu)。如果要在循環(huán)中刪除元素,應(yīng)該使用迭代器的r?emove?方法,或者使用Java 8提供的?removeIf?方法,或者使用一個臨時的集合來存儲要刪除的元素,然后在循環(huán)結(jié)束后再進(jìn)行刪除。例如:

List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");

// 錯誤的做法,使用foreach循環(huán)刪除元素
for (String s : list) {
    if (s.equals("b")) {
        list.remove(s); // 拋出ConcurrentModificationException異常
}
}

// 正確的做法,使用迭代器刪除元素
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("b")) {
        it.remove(); // 安全的刪除元素
    }
}

// 正確的做法,使用Java 8的removeIf方法刪除元素
list.removeIf(s -> s.equals("b")); // 使用lambda表達(dá)式刪除元素

// 正確的做法,使用臨時集合刪除元素
List<String> temp = new ArrayList<String>();
for (String s : list) {
    if (s.equals("b")) {
        temp.add(s); // 將要刪除的元素添加到臨時集合中
    }
}

// 一次性刪除所有元素
list.removeAll(temp); 

總結(jié)

在Java開發(fā)中,避免常見錯誤是提高代碼質(zhì)量和開發(fā)效率的關(guān)鍵。本文揭示了Java開發(fā)人員常犯的五大致命錯誤,并提供了寶貴的建議。遵循良好的命名和代碼風(fēng)格,您將能夠更好地避免這些錯誤,提升代碼質(zhì)量并取得更高的開發(fā)效率。

1698630578111788

如果你對編程知識和相關(guān)職業(yè)感興趣,歡迎訪問編程獅官網(wǎng)(http://m.hgci.cn/)。在編程獅,我們提供廣泛的技術(shù)教程、文章和資源,幫助你在技術(shù)領(lǐng)域不斷成長。無論你是剛剛起步還是已經(jīng)擁有多年經(jīng)驗,我們都有適合你的內(nèi)容,助你取得成功。

0 人點(diǎn)贊