自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java 中最常見(jiàn)的五個(gè)錯(cuò)誤

開(kāi)發(fā) 后端
在編程時(shí),開(kāi)發(fā)者經(jīng)常會(huì)遭遇各式各樣莫名錯(cuò)誤。近日,Sushil Das在 Geek On Java上列舉了 Java 開(kāi)發(fā)中常見(jiàn)的 5 個(gè)錯(cuò)誤,與君共「免」。

在編程時(shí),開(kāi)發(fā)者經(jīng)常會(huì)遭遇各式各樣莫名錯(cuò)誤。近日,Sushil Das在 Geek On Java上列舉了 Java 開(kāi)發(fā)中常見(jiàn)的 5 個(gè)錯(cuò)誤,與君共「免」。

[[142660]]

1、Null 的過(guò)度使用

避免過(guò)度使用 null 值是一個(gè)最佳實(shí)踐。例如,更好的做法是讓方法返回空的 array 或者 collection 而不是 null 值,因?yàn)檫@樣可以防止程序拋出 NullPointerException。下面代碼片段會(huì)從另一個(gè)方法獲得一個(gè)集合:

List<String> accountIds = person.getAccountIds(); 
for (String accountId : accountIds) { 
    processAccount(accountId);
}

當(dāng)一個(gè) person 沒(méi)有 account 的時(shí)候,getAccountIds() 將返回 null 值,程序就會(huì)拋出 NullPointerException 異常。因此需要加入空檢查來(lái)解決這個(gè)問(wèn)題。如果將返回的 null 值替換成一個(gè)空的 list,那么 NullPointerException 也不會(huì)出現(xiàn)。而且,因?yàn)槲覀儾辉傩枰獙?duì)變量 accountId 做空檢查,代碼將變得更加簡(jiǎn)潔。

當(dāng)你想避免 null 值的時(shí)候,不同場(chǎng)景可能采取不同做法。其中一個(gè)方法就是使用 Optional 類(lèi)型,它既可以是一個(gè)空對(duì)象,也可以是一些值的封裝。

Optional<String> optionalString = Optional.ofNullable(nullableString); 
if(optionalString.isPresent()) { 
    System.out.println(optionalString.get());
}

事實(shí)上,Java8 提供了一個(gè)更簡(jiǎn)潔的方法:

Optional<String> optionalString = Optional.ofNullable(nullableString); 
optionalString.ifPresent(System.out::println);

Java 是從 Java8 版本開(kāi)始支持 Optional 類(lèi)型,但是它在函數(shù)式編程世界早已廣為人知。在此之前,它已經(jīng)在 Google Guava 中針對(duì) Java 的早期版本被使用。

2、忽視異常

我們經(jīng)常對(duì)異常置之不理。然而,針對(duì)初學(xué)者和有經(jīng)驗(yàn)的 Java 程序員, 最佳實(shí)踐仍是處理它們。異常拋出通常是帶有目的性的,因此在大多數(shù)情況下需要記錄引起異常的事件。別小看這件事,如果必要的話,你可以重新拋出它,在一個(gè) 對(duì)話框中將錯(cuò)誤信息展示給用戶或者將錯(cuò)誤信息記錄在日志中。至少,為了讓其它開(kāi)發(fā)者知曉前因后果,你應(yīng)該解釋為什么沒(méi)有處理這個(gè)異常。

selfie = person.shootASelfie(); 
try { 
    selfie.show();
} catch (NullPointerException e) {
    // Maybe, invisible man. Who cares, anyway?
}

強(qiáng)調(diào)某個(gè)異常不重要的一個(gè)簡(jiǎn)便途徑就是將此信息作為異常的變量名,像這樣:

try { selfie.delete(); } catch (NullPointerException unimportant) {  }

3、并發(fā)修改異常

這種異常發(fā)生在集合對(duì)象被修改,同時(shí)又沒(méi)有使用 iterator 對(duì)象提供的方法去更新集合中的內(nèi)容。例如,這里有一個(gè) hats 列表,并想刪除其中所有含 ear flaps 的值:

List<IHat> hats = new ArrayList<>(); 
hats.add(new Ushanka()); // that one has ear flaps 
hats.add(new Fedora()); 
hats.add(new Sombrero()); 
for (IHat hat : hats) { 
    if (hat.hasEarFlaps()) {
        hats.remove(hat);
    }
}

如果運(yùn)行此代碼,ConcurrentModificationException 將會(huì)被拋出,因?yàn)榇a在遍歷這個(gè)集合的同時(shí)對(duì)其進(jìn)行修改。當(dāng)多個(gè)進(jìn)程作用于同一列表,在其中一個(gè)進(jìn)程遍歷列表時(shí),另一個(gè)進(jìn)程試圖修改列表內(nèi)容,同樣的異常也可能會(huì)出現(xiàn)。

在多線程中并發(fā)修改集合內(nèi)容是非常常見(jiàn)的,因此需要使用并發(fā)編程中常用的方法進(jìn)行處理,例如同步鎖、對(duì)于并發(fā)修改采用特殊的集合等等。Java 在單線程和多線程情況下解決這個(gè)問(wèn)題有微小的差別。

收集對(duì)象并在另一個(gè)循環(huán)中刪除它們

直接的解決方案是將帶有 ear flaps 的 hats 放進(jìn)一個(gè) list,之后用另一個(gè)循環(huán)刪除它。不過(guò)這需要一個(gè)額外的集合來(lái)存放將要被刪除的 hats。

List<IHat> hatsToRemove = new LinkedList<>(); 
for (IHat hat : hats) { 
    if (hat.hasEarFlaps()) {
        hatsToRemove.add(hat);
    }
}
for (IHat hat : hatsToRemove) { 
    hats.remove(hat);
}

使用Iterator.remove方法

這個(gè)方法更簡(jiǎn)單,同時(shí)并不需要?jiǎng)?chuàng)建額外的集合:

Iterator<IHat> hatIterator = hats.iterator(); 
while (hatIterator.hasNext()) { 
    IHat hat = hatIterator.next();
    if (hat.hasEarFlaps()) {
        hatIterator.remove();
    }
}

使用ListIterator的方法

當(dāng)需要修改的集合實(shí)現(xiàn)了 List 接口時(shí),list iterator 是非常合適的選擇。實(shí)現(xiàn) ListIterator 接口的 iterator 不僅支持刪除操作,還支持add和set操作。ListIterator 接口實(shí)現(xiàn)了 Iterator 接口,因此這個(gè)例子看起來(lái)和Iterator的remove方法很像。唯一的區(qū)別是 hat iterator 的類(lèi)型和我們獲得 iterator 的方式——使用listIterator()方法。下面的片段展示了如何使用 ListIterator.remove和 ListIterator.add方法將帶有 ear flaps 的 hat 替換成帶有sombreros 的。

IHat sombrero = new Sombrero(); 
ListIterator<IHat> hatIterator = hats.listIterator(); 
while (hatIterator.hasNext()) { 
    IHat hat = hatIterator.next();
    if (hat.hasEarFlaps()) {
        hatIterator.remove();
        hatIterator.add(sombrero);
    }
}

使用 ListIterator,調(diào)用remove和add方法可替換為只調(diào)用一個(gè)set方法:

IHat sombrero = new Sombrero(); 
ListIterator<IHat> hatIterator = hats.listIterator(); 
while (hatIterator.hasNext()) { 
    IHat hat = hatIterator.next();
    if (hat.hasEarFlaps()) {
        hatIterator.set(sombrero); // set instead of remove and add
    }
}

使用Java 8中的stream方法

在 Java8 中,開(kāi)發(fā)人員可以將一個(gè) collection 轉(zhuǎn)換為 stream,并且根據(jù)一些條件過(guò)濾 stream。這個(gè)例子講述了 stream api 是如何過(guò)濾 hats 和避免ConcurrentModificationException。 hats = hats.stream().filter((hat -> !hat.hasEarFlaps()))

.collect(Collectors.toCollection(ArrayList::new));

Collectors.toCollection方法將會(huì)創(chuàng)建一個(gè)新的 ArrayList,它負(fù)責(zé)存放被過(guò)濾掉的 hats 值。如果過(guò)濾條件過(guò)濾掉了大量條目,這里將會(huì)產(chǎn)生一個(gè)很大的 ArrayList。因此,需要謹(jǐn)慎使用。

使用 Java 8 中的List.removeIf 方法

可以使用 Java 8 中另一個(gè)更簡(jiǎn)潔明了的方法—— removeIf方法:

hats.removeIf(IHat::hasEarFlaps);

在底層,它使用 Iterator.remove來(lái)完成這個(gè)操作。

使用特殊的集合

如果在一開(kāi)始就決定使用CopyOnWriteArrayList而不是ArrayList,那就不會(huì)出現(xiàn)問(wèn)題。因 為 CopyOnWriteArrayList提供了修改的方法(例如 set,add,remove),它不會(huì)去改變?cè)技蠑?shù)組,而是創(chuàng)建了一個(gè)新的修改版本。這就允許遍歷原來(lái)版本集合的同時(shí)進(jìn)行修改,從而不會(huì)拋 出 ConcurrentModificationException異常。這種集合的缺點(diǎn)也非常明顯——針對(duì)每次修改都產(chǎn)生一個(gè)新的集合。

還有其他適用于不同場(chǎng)景的集合,比如 CopyOnWriteSet和ConcurrentHashMap。

關(guān)于另一個(gè)可能可能在并發(fā)修改集合時(shí)產(chǎn)生的錯(cuò)誤是,從一個(gè) collection 創(chuàng)建了一個(gè) stream,在遍歷 stream 的時(shí)候,同時(shí)修改后端的 collection。針對(duì) stream 的一般準(zhǔn)則是,在查詢 stream 的時(shí)候,避免修改后端的 collection。接下來(lái)的例子將展示如何正確地處理 stream:

List<IHat> filteredHats = hats.stream().peek(hat -> { 
    if (hat.hasEarFlaps()) {
        hats.remove(hat);
    }
}).collect(Collectors.toCollection(ArrayList::new));

peek方法收集所有的元素,并對(duì)每一個(gè)元素執(zhí)行既定動(dòng)作。在這里,動(dòng)作即為嘗試從一個(gè)基礎(chǔ)列表中刪除數(shù)據(jù),這顯然是錯(cuò)誤的。為避免這樣的操作,可以嘗試一些上面講解的方法。

4、違約

有時(shí)候,為了更好地協(xié)作,由標(biāo)準(zhǔn)庫(kù)或者第三方提供的代碼必須遵守共同的依賴準(zhǔn)則。例如,必須遵守 hashCode和equals的共同約定,從而 保證 Java 集合框架中的一系列集合類(lèi)和其它使用hashCode和equals方法的類(lèi)能夠正常工作。不遵守約定并不會(huì)產(chǎn)生 exception 或者破壞代碼編譯之類(lèi)的錯(cuò)誤;它很陰險(xiǎn),因?yàn)樗S時(shí)可能在毫無(wú)危險(xiǎn)提示的情況下更改應(yīng)用程序行為。

錯(cuò)誤代碼可能潛入生產(chǎn)環(huán)境,從而造成一大堆不良影響。這包括較差的 UI 體驗(yàn)、錯(cuò)誤的數(shù)據(jù)報(bào)告、較差的應(yīng)用性能、數(shù)據(jù)丟失或者更多。慶幸的是,這些災(zāi)難性的錯(cuò)誤不會(huì)經(jīng)常發(fā)生。在之前已經(jīng)提及了 hashCode 和equals 約定,它出現(xiàn)的場(chǎng)景可能是:集合依賴于將對(duì)象進(jìn)行哈?;蛘弑容^,就像 HashMap 和 HashSet。簡(jiǎn)單來(lái)說(shuō),這個(gè)約定有兩個(gè)準(zhǔn)則:

  • 如果兩個(gè)對(duì)象相等,那么 hash code 必須相等。

  • 如果兩個(gè)對(duì)象有相同的 hash code,那么它們可能相等也可能不相等。

破壞約定的第一條準(zhǔn)則,當(dāng)你試圖從一個(gè) hashmap 中檢索數(shù)據(jù)的時(shí)候?qū)?huì)導(dǎo)致錯(cuò)誤。第二個(gè)準(zhǔn)則意味著擁有相同hash code的對(duì)象不一定相等。

下面看一下破壞第一條準(zhǔn)則的后果:

public static class Boat { 
    private String name;

    Boat(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Boat boat = (Boat) o;

        return !(name != null ? !name.equals(boat.name) : boat.name != null);
    }

    @Override
    public int hashCode() {
        return (int) (Math.random() * 5000);
    }
}

正如你所見(jiàn),Boat 類(lèi)重寫(xiě)了equals和hashCode方法。然而,它破壞了約定,因?yàn)?hashCode 針對(duì)每次調(diào)用的相同對(duì)象返回了隨機(jī)值。下面的代碼很可能在 hashset 中找不到一個(gè)名為Enterprise的boat,盡管事實(shí)上我們提前加入了這種類(lèi)型的 boat:

public static void main(String[] args) { 
    Set<Boat> boats = new HashSet<>();
    boats.add(new Boat("Enterprise"));

    System.out.printf("We have a boat named 'Enterprise' : %b/n", boats.contains(new Boat("Enterprise")));
}

另一個(gè)約定的例子是finalize 方法。這里是官方 Java 文檔關(guān)于它功能描述的引用:

finalize的常規(guī)約定是:當(dāng) JavaTM 虛擬機(jī)確定任何線程都無(wú)法再通過(guò)任何方式訪問(wèn)指定對(duì)象時(shí),這個(gè)方法會(huì)被調(diào)用,此后這個(gè)對(duì)象只能在某個(gè)其他(準(zhǔn)備終止的)對(duì)象或類(lèi)終結(jié)時(shí)被作為某個(gè)行為的結(jié) 果。finalize方法有多個(gè)功能,其中包括再次使此對(duì)象對(duì)其他線程可用;不過(guò)finalize的主要目的是在不可撤消地丟棄對(duì)象之前執(zhí)行清除操作。例 如,表示輸入/輸出連接對(duì)象的finalize方法可執(zhí)行顯式 I/O 事務(wù),以便在永久丟棄對(duì)象之前中斷連接。

你可以決定在諸如文件處理器中使用finalize方法來(lái)釋放資源,但是這種用法是很糟糕的。由于它是在垃圾回收期間被調(diào)用的,而 GC 的時(shí)間并不確定,因此finalize被調(diào)用的時(shí)間將無(wú)法保證。

5、使用原始類(lèi)型而不是參數(shù)化的

根據(jù) Java 文檔描述:原始類(lèi)型要么是非參數(shù)化的,要么是類(lèi) R 的(同時(shí)也是非繼承 R 父類(lèi)或者父接口的)非靜態(tài)成員。在 Java 泛型被引入之前,并沒(méi)有原始類(lèi)型的替代類(lèi)型。Java 從1.5版本開(kāi)始支持泛型編程,毫無(wú)疑問(wèn)這是一個(gè)重要的功能提升。然而,由于向后兼容的原因,這里存在一個(gè)陷阱可能會(huì)破壞整個(gè)類(lèi)型系統(tǒng)。著眼下例:

List listOfNumbers = new ArrayList(); 
listOfNumbers.add(10); 
listOfNumbers.add("Twenty"); 
listOfNumbers.forEach(n -> System.out.println((int) n * 2));

這是一個(gè)由數(shù)字組成的列表被定義為原始的 ArrayList。由于它并沒(méi)有指定類(lèi)型參數(shù),因此可以給它添加任何對(duì)象。但是最后一行將其包含的元素映射為 int 類(lèi)型并乘以 2,打印出翻倍之后的數(shù)據(jù)到標(biāo)準(zhǔn)輸出。

此代碼編譯時(shí)不會(huì)出錯(cuò),但是一旦運(yùn)行就會(huì)拋出運(yùn)行時(shí)錯(cuò)誤,因?yàn)檫@里試圖將字符類(lèi)型映射為整形。很顯然,如果隱藏了必要信息,類(lèi)型系統(tǒng)將不能幫助寫(xiě)出安全代碼。

為了解決這個(gè)問(wèn)題,需要為存入集合中的對(duì)象指定具體類(lèi)型:

List<Integer> listOfNumbers = new ArrayList<>();

listOfNumbers.add(10); 
listOfNumbers.add("Twenty");

listOfNumbers.forEach(n -> System.out.println((int) n * 2));

與之前代碼的唯一差別即是定義集合的那一行:

List<Integer> listOfNumbers = new ArrayList<>();

修改之后的代碼編譯不可能被通過(guò),因?yàn)檫@里試圖向只期望存儲(chǔ)整形的集合中添加字符串。編譯器將會(huì)顯示錯(cuò)誤信息,并指向試圖向列表中添加Twenty 字符的那一行。參數(shù)化泛型類(lèi)型是個(gè)不錯(cuò)的主意。這樣的話,編譯器就能夠檢查所有可能的類(lèi)型,從而由于類(lèi)型不一致而導(dǎo)致的運(yùn)行時(shí)異常幾率將大大降低。

責(zé)任編輯:王雪燕 來(lái)源: codeceo
相關(guān)推薦

2020-05-29 14:30:35

Kubernetes開(kāi)發(fā)錯(cuò)誤

2023-07-08 23:02:14

快捷鍵IntelliJIDEA

2021-06-16 15:04:06

JavaScript內(nèi)存開(kāi)發(fā)

2019-10-14 16:39:50

云計(jì)算配置錯(cuò)誤企業(yè)

2021-12-30 21:51:10

JavaScript開(kāi)發(fā)內(nèi)存

2022-01-22 00:14:05

Windows 11微軟修復(fù)

2020-09-23 22:40:31

Python 開(kāi)發(fā)編程語(yǔ)言

2017-08-18 15:40:20

Nginx面試必備

2014-11-27 10:00:42

ERP運(yùn)維

2024-03-18 08:22:15

OOM問(wèn)題java線上問(wèn)題

2009-06-10 21:58:51

Javascript常

2024-02-26 00:00:00

stage函數(shù)進(jìn)度

2012-12-24 09:46:50

RDS打印重定向

2017-12-14 08:04:21

Java面試程序

2022-05-31 15:43:15

自動(dòng)化測(cè)試

2023-04-03 11:25:43

物聯(lián)網(wǎng)智能建筑

2020-08-16 20:36:21

滲透測(cè)試漏洞網(wǎng)絡(luò)攻擊

2022-08-16 20:04:08

移動(dòng)應(yīng)用測(cè)試

2021-09-16 11:02:49

Python線程

2024-01-29 18:02:46

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)