- 相關(guān)推薦
Java中最常見的錯誤盤點
在編程時,開發(fā)者經(jīng)常會遭遇各式各樣莫名錯誤。近日,Sushil Das在 Geek On Java上列舉了 Java 開發(fā)中常見的 5 個錯誤,一起跟yjbys小編來看看吧!
Java中最常見的錯誤盤點 1
1、Null 的過度使用
避免過度使用 null 值是一個最佳實踐。例如,更好的做法是讓方法返回空的 array 或者 collection 而不是 null 值,因為這樣可以防止程序拋出 NullPointerException。下面代碼片段會從另一個方法獲得一個集合:
ListaccountIds = person.getAccountIds();
for (String accountId : accountIds) {
processAccount(accountId);
}
當(dāng)一個 person 沒有 account 的時候,getAccountIds() 將返回 null 值,程序就會拋出 NullPointerException 異常。因此需要加入空檢查來解決這個問題。如果將返回的 null 值替換成一個空的 list,那么 NullPointerException 也不會出現(xiàn)。而且,因為我們不再需要對變量 accountId 做空檢查,代碼將變得更加簡潔。
當(dāng)你想避免 null 值的時候,不同場景可能采取不同做法。其中一個方法就是使用 Optional 類型,它既可以是一個空對象,也可以是一些值的封裝。
OptionaloptionalString = Optional.ofNullable(nullableString);
if(optionalString.isPresent()) {
System.out.println(optionalString.get());
}
事實上,Java8 提供了一個更簡潔的方法:
OptionaloptionalString = Optional.ofNullable(nullableString);
optionalString.ifPresent(System.out::println);
Java 是從 Java8 版本開始支持 Optional 類型,但是它在函數(shù)式編程世界早已廣為人知。在此之前,它已經(jīng)在 Google Guava 中針對 Java 的早期版本被使用。
2、忽視異常
我們經(jīng)常對異常置之不理。然而,針對初學(xué)者和有經(jīng)驗的 Java 程序員,最佳實踐仍是處理它們。異常拋出通常是帶有目的性的,因此在大多數(shù)情況下需要記錄引起異常的事件。別小看這件事,如果必要的話,你可以重新拋出它,在一個對話框中將錯誤信息展示給用戶或者將錯誤信息記錄在日志中。至少,為了讓其它開發(fā)者知曉前因后果,你應(yīng)該解釋為什么沒有處理這個異常。
selfie = person.shootASelfie();
try {
selfie.show();
} catch (NullPointerException e) {
// Maybe, invisible man. Who cares, anyway?
}
強(qiáng)調(diào)某個異常不重要的一個簡便途徑就是將此信息作為異常的變量名,像這樣:
try { selfie.(); } catch (NullPointerException unimportant) { }
3、并發(fā)修改異常
這種異常發(fā)生在集合對象被修改,同時又沒有使用 iterator 對象提供的方法去更新集合中的內(nèi)容。例如,這里有一個 hats 列表,并想刪除其中所有含 ear flaps 的值:
Listhats = 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 將會被拋出,因為代碼在遍歷這個集合的同時對其進(jìn)行修改。當(dāng)多個進(jìn)程作用于同一列表,在其中一個進(jìn)程遍歷列表時,另一個進(jìn)程試圖修改列表內(nèi)容,同樣的異常也可能會出現(xiàn)。
在多線程中并發(fā)修改集合內(nèi)容是非常常見的,因此需要使用并發(fā)編程中常用的方法進(jìn)行處理,例如同步鎖、對于并發(fā)修改采用特殊的集合等等。Java 在單線程和多線程情況下解決這個問題有微小的差別。
收集對象并在另一個循環(huán)中刪除它們
直接的解決方案是將帶有 ear flaps 的 hats 放進(jìn)一個 list,之后用另一個循環(huán)刪除它。不過這需要一個額外的集合來存放將要被刪除的 hats。
ListhatsToRemove = new LinkedList<>();
for (IHat hat : hats) {
if (hat.hasEarFlaps()) {
hatsToRemove.add(hat);
}
}
for (IHat hat : hatsToRemove) {
hats.remove(hat);
}
使用Iterator.remove方法
這個方法更簡單,同時并不需要創(chuàng)建額外的集合:
IteratorhatIterator = hats.iterator();
while (hatIterator.hasNext()) {
IHat hat = hatIterator.next();
if (hat.hasEarFlaps()) {
hatIterator.remove();
}
}
使用ListIterator的方法
當(dāng)需要修改的集合實現(xiàn)了 List 接口時,list iterator 是非常合適的選擇。實現(xiàn) ListIterator 接口的 iterator 不僅支持刪除操作,還支持add和set操作。ListIterator 接口實現(xiàn)了 Iterator 接口,因此這個例子看起來和Iterator的remove方法很像。唯一的區(qū)別是 hat iterator 的類型和我們獲得 iterator 的方式——使用listIterator()方法。下面的片段展示了如何使用 ListIterator.remove和ListIterator.add方法將帶有 ear flaps 的 hat 替換成帶有sombreros 的。
IHat sombrero = new Sombrero();
ListIteratorhatIterator = hats.listIterator();
while (hatIterator.hasNext()) {
IHat hat = hatIterator.next();
if (hat.hasEarFlaps()) {
hatIterator.remove();
hatIterator.add(sombrero);
}
}
使用 ListIterator,調(diào)用remove和add方法可替換為只調(diào)用一個set方法:
IHat sombrero = new Sombrero();
ListIteratorhatIterator = 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 中,開發(fā)人員可以將一個 collection 轉(zhuǎn)換為 stream,并且根據(jù)一些條件過濾 stream。這個例子講述了 stream api 是如何過濾 hats 和避免ConcurrentModificationException。 hats = hats.stream().filter((hat -> !hat.hasEarFlaps()))
.collect(Collectors.toCollection(ArrayList::new));
Collectors.toCollection方法將會創(chuàng)建一個新的 ArrayList,它負(fù)責(zé)存放被過濾掉的 hats 值。如果過濾條件過濾掉了大量條目,這里將會產(chǎn)生一個很大的 ArrayList。因此,需要謹(jǐn)慎使用。
使用 Java 8 中的List.removeIf 方法
可以使用 Java 8 中另一個更簡潔明了的方法—— removeIf方法:
hats.removeIf(IHat::hasEarFlaps);
在底層,它使用 Iterator.remove來完成這個操作。
使用特殊的集合
如果在一開始就決定使用CopyOnWriteArrayList而不是ArrayList,那就不會出現(xiàn)問題。因為 CopyOnWriteArrayList提供了修改的方法(例如 set,add,remove),它不會去改變原始集合數(shù)組,而是創(chuàng)建了一個新的修改版本。這就允許遍歷原來版本集合的同時進(jìn)行修改,從而不會拋出 ConcurrentModificationException異常。這種集合的缺點也非常明顯——針對每次修改都產(chǎn)生一個新的集合。
還有其他適用于不同場景的集合,比如 CopyOnWriteSet和ConcurrentHashMap。
關(guān)于另一個可能可能在并發(fā)修改集合時產(chǎn)生的錯誤是,從一個 collection 創(chuàng)建了一個 stream,在遍歷 stream 的時候,同時修改后端的 collection。針對 stream 的一般準(zhǔn)則是,在查詢 stream 的時候,避免修改后端的 collection。接下來的例子將展示如何正確地處理 stream:
ListfilteredHats = hats.stream().peek(hat -> {
if (hat.hasEarFlaps()) {
hats.remove(hat);
}
}).collect(Collectors.toCollection(ArrayList::new));
peek方法收集所有的元素,并對每一個元素執(zhí)行既定動作。在這里,動作即為嘗試從一個基礎(chǔ)列表中刪除數(shù)據(jù),這顯然是錯誤的。為避免這樣的操作,可以嘗試一些上面講解的方法。
4、違約
有時候,為了更好地協(xié)作,由標(biāo)準(zhǔn)庫或者第三方提供的代碼必須遵守共同的依賴準(zhǔn)則。例如,必須遵守 hashCode和equals的共同約定,從而保證 Java 集合框架中的一系列集合類和其它使用hashCode和equals方法的類能夠正常工作。不遵守約定并不會產(chǎn)生 exception 或者破壞代碼編譯之類的錯誤;它很陰險,因為它隨時可能在毫無危險提示的情況下更改應(yīng)用程序行為。
錯誤代碼可能潛入生產(chǎn)環(huán)境,從而造成一大堆不良影響。這包括較差的 UI 體驗、錯誤的數(shù)據(jù)報告、較差的應(yīng)用性能、數(shù)據(jù)丟失或者更多。慶幸的是,這些災(zāi)難性的`錯誤不會經(jīng)常發(fā)生。在之前已經(jīng)提及了 hashCode 和equals 約定,它出現(xiàn)的場景可能是:集合依賴于將對象進(jìn)行哈;蛘弑容^,就像 HashMap 和 HashSet。簡單來說,這個約定有兩個準(zhǔn)則:
如果兩個對象相等,那么 hash code 必須相等。
如果兩個對象有相同的 hash code,那么它們可能相等也可能不相等。
破壞約定的第一條準(zhǔn)則,當(dāng)你試圖從一個 hashmap 中檢索數(shù)據(jù)的時候?qū)䦟?dǎo)致錯誤。第二個準(zhǔn)則意味著擁有相同hash code的對象不一定相等。
下面看一下破壞第一條準(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);
}
}
正如你所見,Boat 類重寫了equals和hashCode方法。然而,它破壞了約定,因為 hashCode 針對每次調(diào)用的相同對象返回了隨機(jī)值。下面的代碼很可能在 hashset 中找不到一個名為Enterprise的boat,盡管事實上我們提前加入了這種類型的 boat:
public static void main(String[] args) {
Setboats = new HashSet<>();
boats.add(new Boat("Enterprise"));
System.out.printf("We have a boat named Enterprise : %b/n", boats.contains(new Boat("Enterprise")));
}
另一個約定的例子是finalize 方法。這里是官方 Java 文檔關(guān)于它功能描述的引用:
finalize的常規(guī)約定是:當(dāng) JavaTM 虛擬機(jī)確定任何線程都無法再通過任何方式訪問指定對象時,這個方法會被調(diào)用,此后這個對象只能在某個其他(準(zhǔn)備終止的)對象或類終結(jié)時被作為某個行為的結(jié)果。finalize方法有多個功能,其中包括再次使此對象對其他線程可用;不過finalize的主要目的是在不可撤消地丟棄對象之前執(zhí)行清除操作。例如,表示輸入/輸出連接對象的finalize方法可執(zhí)行顯式 I/O 事務(wù),以便在永久丟棄對象之前中斷連接。
你可以決定在諸如文件處理器中使用finalize方法來釋放資源,但是這種用法是很糟糕的。由于它是在垃圾回收期間被調(diào)用的,而 GC 的時間并不確定,因此finalize被調(diào)用的時間將無法保證。
5、使用原始類型而不是參數(shù)化的
根據(jù) Java 文檔描述:原始類型要么是非參數(shù)化的,要么是類 R 的(同時也是非繼承 R 父類或者父接口的)非靜態(tài)成員。在 Java 泛型被引入之前,并沒有原始類型的替代類型。Java 從1.5版本開始支持泛型編程,毫無疑問這是一個重要的功能提升。然而,由于向后兼容的原因,這里存在一個陷阱可能會破壞整個類型系統(tǒng)。著眼下例:
List listOfNumbers = new ArrayList();
listOfNumbers.add(10);
listOfNumbers.add("Twenty");
listOfNumbers.forEach(n -> System.out.println((int) n * 2));
這是一個由數(shù)字組成的列表被定義為原始的 ArrayList。由于它并沒有指定類型參數(shù),因此可以給它添加任何對象。但是最后一行將其包含的元素映射為 int 類型并乘以 2,打印出翻倍之后的數(shù)據(jù)到標(biāo)準(zhǔn)輸出。
此代碼編譯時不會出錯,但是一旦運(yùn)行就會拋出運(yùn)行時錯誤,因為這里試圖將字符類型映射為整形。很顯然,如果隱藏了必要信息,類型系統(tǒng)將不能幫助寫出安全代碼。
為了解決這個問題,需要為存入集合中的對象指定具體類型:
ListlistOfNumbers = new ArrayList<>();
listOfNumbers.add(10);
listOfNumbers.add("Twenty");
listOfNumbers.forEach(n -> System.out.println((int) n * 2));
與之前代碼的唯一差別即是定義集合的那一行:
ListlistOfNumbers = new ArrayList<>();
修改之后的代碼編譯不可能被通過,因為這里試圖向只期望存儲整形的集合中添加字符串。編譯器將會顯示錯誤信息,并指向試圖向列表中添加Twenty字符的那一行。參數(shù)化泛型類型是個不錯的主意。這樣的話,編譯器就能夠檢查所有可能的類型,從而由于類型不一致而導(dǎo)致的運(yùn)行時異常幾率將大大降低。
Java中最常見的錯誤盤點 2
1 異常 javax.servlet.jsp.JspException: Cannot retrieve mapping for action /Login (/Login是你的action名字)
可能原因:action沒有再struts-config.xml 中定義,或沒有找到匹配的action,例如在JSP文件中使用 處理:如果出現(xiàn)上述異常,請查看struts-config.xml中的定義部分,有時可能是打錯了字符或者是某些不符合規(guī)則,可以使用struts console工具來檢查。
2 異常 org.apache.jasper.JasperException: Cannot retrieve definition for form bean null
可能原因: 這個異常是因為Struts根據(jù)struts-config.xml中的mapping沒有找到action期望的form bean。大部分的情況可能是因為在form-bean中設(shè)置的name屬性和action中設(shè)置的name屬性不匹配所致。換句話說,action和form都應(yīng)該各自有一個name屬性,并且要精確匹配,包括大小寫。這個錯誤當(dāng)沒有name屬性和action關(guān)聯(lián)時也會發(fā)生,如果沒有在action中指定name屬性,那么就沒有name屬性和action相關(guān)聯(lián)。當(dāng)然當(dāng)action制作某些控制時,譬如根據(jù)參數(shù)值跳轉(zhuǎn)到相應(yīng)的jsp頁面,而不是處理表單數(shù)據(jù),這是就不用name屬性,這也是action的使用方法之一。
3 異常 No action instance for path /xxxx could be created
可能原因
特別提示:因為有很多中情況會導(dǎo)致這個錯誤的發(fā)生,所以推薦大家調(diào)高你的web服務(wù)器的日志/調(diào)試級別,這樣可以從更多的信息中看到潛在的、在試圖創(chuàng)建action類時發(fā)生的錯誤,這個action類你已經(jīng)在struts-config.xml中設(shè)置了關(guān)聯(lián)(即添加了標(biāo)簽)。
在struts-config.xml中通過action標(biāo)簽的class屬性指定的action類不能被找到有很多種原因,例如:
定位編譯后的class文件失敗。Failure to place compiled .class file for the action in the classpath (在web開發(fā)中,class的的位置在r WEB-INF/classes,所以你的action class必須要在這個目錄下。例如你的action類位于WEB-INF/classes/action/Login.class,那么在struts-config.xml中設(shè)置action的屬性type時就是action.Login).
拼寫錯誤,這個也時有發(fā)生,并且不易找到,特別注意第一個字母的大小寫和包的名稱。
在struts-config.xml中指定的action類沒有繼承自Stuts的Action類,或者你自定義的`Action類沒有繼承自Struts提供的Action類。
你的action類必須繼承自Struts提供的Action類。
你的classpath的問題。例如web server沒有發(fā)現(xiàn)你的資源文件,資源文件必須在WEB-INF/classes/目錄下。
4 異常 javax.servlet.jsp.JspException: No getter method for property username of bean org.apache.struts.taglib.html.BEAN
可能原因
沒有位form bean中的某個變量定義getter 方法
這個錯誤主要發(fā)生在表單提交的FormBean中,用struts標(biāo)記時,在FormBean中必須有一個getUsername()方法。注意字母“U”。
5 Exception javax.servlet.jsp.JspException: Cannot find ActionMappings or ActionFormBeans collection
可能原因
不是標(biāo)識Struts actionServlet的標(biāo)記就是映射.do擴(kuò)展名的標(biāo)記或者兩者都沒有在web.xml中聲明。
在struts-config.xml中的打字或者拼寫錯誤也可導(dǎo)致這個異常的發(fā)生。例如缺少一個標(biāo)記的關(guān)閉符號/>。最好使用struts console工具檢查一下。
另外,load-on-startup必須在web.xml中聲明,這要么是一個空標(biāo)記,要么指定一個數(shù)值,這個數(shù)值用來表servlet運(yùn)行的優(yōu)先級,數(shù)值越大優(yōu)先級越低。
還有一個和使用load-on-startup有關(guān)的是使用Struts預(yù)編譯JSP文件時也可能導(dǎo)致這個異常。
6 Exception
javax.servlet.jsp.JspException: Cannot find bean org.apache.struts.taglib.html.BEAN in any scope
Probable Causes
試圖在Struts的form標(biāo)記外使用form的子元素。這常常發(fā)生在你在后面使用Struts的html標(biāo)記。
另外要注意可能你不經(jīng)意使用的無主體的標(biāo)記,如,這樣web 服務(wù)器解析時就當(dāng)作一個無主體的標(biāo)記,隨后使用的所有標(biāo)記都被認(rèn)為是在這個標(biāo)記之外的,如又使用了
還有就是在使用taglib引入HTML標(biāo)記庫時,你使用的prefix的值不是html
【Java中最常見的錯誤盤點】相關(guān)文章:
盤點excel函數(shù)常見錯誤函數(shù)10-08
2018專四寫作常見錯誤盤點10-05
盤點最經(jīng)典SAT語法改錯題中的常見錯誤09-22
錯誤的洗頭方式盤點08-21
英語常見錯誤07-26
化妝常見的錯誤06-29