本文根據java開發人員在編碼過程中容易忽視或經常出錯的地方進行了整理,總結了十個比較常見的低級錯誤點,方便大家學習。
1、不能用“==”比較兩個字符串內容相等。
2、 對list做foreach循環時,循環代碼中不能修改list的結構。
3、 日志和實際情況不一致;捕獲異常后沒有在日志中記錄異常棧。
4、 魔鬼數字。
5、 空指針異常。
6、 數組下標越界。
7、 將字符串轉換為數字時沒有捕獲numberformatexception異常。
8、 對文件、io、數據庫等資源進行操作后沒有及時、正確進行釋放。
9、 循環體編碼時不考慮性能,循環體中包含不需要的重復邏輯。
10、數據類沒有重載tostring()方法。
1不能用“==”比較兩個字符串內容相等。
解讀
兩個字符串在比較內容是否相等的時候,如果使用“==”,當兩個字符串不是指向內存中同一地址,那么即使這兩個字符串內容一樣,但是用“==”比較出來的結果也是false。所以兩個字符串在比較內容是否相等的時候一定要使用“equals”方法。
示例
下面就是一個字符串比較的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
publicclass test { publicstaticvoid main(string[] args) { string a = new string( "a" ); string a2 = "a" ; if (a == a2) { system.out.println( "a == a2return true." ); } else { system.out.println( "a == a2 returnfalse." ); } if (a.equals(a2)) { system.out.println( "a.equals(a2)return true." ); } else { system.out.println( "a.equals(a2)return false." ); } } } |
最終輸出的結果為:
a == a2 return false. a.equals(a2) return true.
2 不能在foreach循環中修改list結構
解讀
在jdk1.5版以上的foreach循環寫法中,不能在循環代碼中對正在循環的list的結構進行修改,即對list做add、remove等操作,如果做了這些操作,必須立即退出循環,否則會拋出異常。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
publicclass test { publicstaticvoid main(string[] args) { list<person> list = new arraylist<person>(); person p1 = new person( "張三" , 23 ); person p2 = new person( "李四" , 26 ); person p3 = new person( "王五" , 34 ); person p4 = new person( "劉二" , 15 ); person p5 = new person( "朱六" , 40 ); list.add(p1); list.add(p2); list.add(p3); list.add(p4); list.add(p5); for (person p : list) { if ( "王五" .equals(p.getname())) { list.remove(p); // 不能在此時刪除對象。 } elseif( "李四" .equals(p.getname())) { list.remove(p); // 不能在此時刪除對象。 } } system.out.println(list.size()); } } class person { private string name; privateintage; public person(string name, int age) { this .name = name; this .age = age; } public string getname() { returnname; } publicvoid setname(string name) { this .name = name; } publicint getage() { returnage; } publicvoid setage( int age) { this .age = age; } } |
解決上面代碼紅色部分的問題,可以通過循環取出對象,然后再循環結束后再進行刪除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
list<person> list = new arraylist<person>(); person p1 = new person( new string( "張三" ), 23 ); person p2 = new person( new string( "李四" ), 26 ); person p3 = new person( new string( "王五" ), 34 ); person p4 = new person( new string( "劉二" ), 15 ); person p5 = new person( new string( "朱六" ), 40 ); list.add(p1); list.add(p2); list.add(p3); list.add(p4); list.add(p5); person wangwu = null ; person lisi = null ; for (person p : list) { if ( "王五" .equals(p.getname())) { wangwu = p; } elseif( "李四" .equals(p.getname())) { lisi = p; } } list.remove(wangwu); list.remove(lisi); |
3 日志規范性
解讀
日志是定位問題時最重要的依據,業務流程中缺少必要的日志會給定位問題帶來很多麻煩,甚至可能造成問題完全無法定位。
異常產生后,必須在日志中以error或以上級別記錄異常棧,否則會導致異常棧丟失,無法確認異常產生的位置。并不需要在每次捕獲異常時都記錄異常日志,這樣可能導致異常被多次重復記錄,影響問題的定位。但異常發生后其異常棧必須至少被記錄一次。
和注釋一樣,日志也不是越多越好。無用的冗余日志不但不能幫助定位問題,還會干擾問題的定位。而錯誤的日志更是會誤導問題,必須杜絕。
示例
下面的例子雖然打印了很多日志,但基本上都是無用的日志,難以幫助定位問題。甚至還有錯誤的日志會干擾問題的定位:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public voidsaveproduct1(productservicestruct product) { log.debug( "enter method: addproduct()" ); log.debug( "check product status" ); if (product.getproduct().getproductstatus() != productfieldenum.productstatus.release) { thrownew pmsexception(pmserrorcode.product.add_error); } log.debug( "check tariff" ); booleanresult result =checktariff(product.gettariffs()); if (!result.getresult()) { thrownewpmsexception(pmserrorcode.product.add_error); } log.debug( "before add product" ); productservice prodsrv = (productservice)servicelocator.findservice(productservice. class ); try { prodsrv.addproduct(product); } catch (bmeexception e) { // 未記錄異常棧,無法定位問題根源 } log.debug( "after add product" ); log.debug( "exit method: updateproduct()" ); // 錯誤的日志 } |
而下面的例子日志打印的不多,但都是關鍵信息,可以很好的幫助定位問題:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public voidsaveproduct2(productservicestruct product) { if (product.getproduct().getproductstatus() != productfieldenum.productstatus.release) { log.error( "productstatus " +product.getproduct().getproductstatus() + "error, expect " + productfieldenum.productstatus.release); thrownewpmsexception(pmserrorcode.product.add_error); } booleanresult result =checktariff(product.gettariffs()); if (!result.getresult()) { log.error( "checkproduct tariff error " + result.getresultcode() + ":" + result.getresultdesc()); thrownewpmsexception(pmserrorcode.product.add_error); } productservice prodsrv = (productservice)servicelocator.findservice(productservice. class ); try { prodsrv.addproduct(product); } catch (bmeexception e) { log.error( "add product error" , e); thrownewpmsexception(pmserrorcode.product.add_error,e); } } |
4 魔鬼數字
解讀
在代碼中使用魔鬼數字(沒有具體含義的數字、字符串等)將會導致代碼難以理解,應該將數字定義為名稱有意義的常量。
將數字定義為常量的最終目的是為了使代碼更容易理解,所以并不是只要將數字定義為常量就不是魔鬼數字了。如果常量的名稱沒有意義,無法幫助理解代碼,同樣是一種魔鬼數字。
在個別特殊情況下,將數字定義為常量反而會導致代碼更難以理解,此時就不應該強求將數字定義為常量。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public void addproduct(productservicestruct product) { // 魔鬼數字,無法理解3具體代表產品的什么狀態 if (product.getproduct().getproductstatus() != 3 ) { thrownewpmsexception(pmserrorcode.product.add_error); } booleanresult result =checktariff(product.gettariffs()); if (!result.getresult()) { thrownew pmsexception(pmserrorcode.product.add_error); } } /** *產品未激活狀態 */ privatestaticfinalintunactivated = 0 ; /** *產品已激活狀態 */ privatestaticfinalintactivated = 1 ; public voidaddproduct2(productservicestruct product) { if (product.getproduct().getproductstatus() != activated) { thrownewpmsexception(pmserrorcode.product.add_error); } booleanresult result =checktariff(product.gettariffs()); if (!result.getresult()) { thrownewpmsexception(pmserrorcode.product.add_error); } } |
5 空指針異常
解讀
空指針異常是編碼過程中最常見的異常,在使用一個對象的時候,如果對象可能為空,并且使用次對象可能會造成空指針異常,那么需要先判斷對象是否為空,再使用這個對象。
在進行常量和變量的相等判斷時,建議將常量定義為java對象封裝類型(如將int類型的常量定義為integer類型),這樣在比較時可以將常量放在左邊,調用equals方法進行比較,可以省去不必要的判空。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public classnullpointer { staticfinal integer result_code_ok = 0 ; staticfinal result result_ok = newresult(); publicvoid printresult(integer resultcode) { result result = getresult(resultcode); // result可能為null,造成空指針異常 if (result.isvalid()) { print(result); } } publicresult getresult(integer resultcode) { // 即使resultcode為null,仍然可以正確執行,減少額外的判空語句 if (result_code_ok.equals(resultcode)) { returnresult_ok; } returnnull; } publicvoid print(result result) { ... } } |
6 下標越界
解讀
訪問數組、list等容器內的元素時,必須首先檢查下標是否越界,杜絕下標越界異常的發生。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
publicclass arrayover { publicvoid checkarray(string name) { // 獲取一個數組對象 string[] cids = contentservice.querybyname(name); if ( null != cids) { // 只是考慮到cids有可能為null的情況,但是cids完全有可能是個0長度的數組,因此cids[0]有可能數組下標越界 string cid=cids[ 0 ]; cid.tochararray(); } } } |
7 字符串轉數字
解讀
調用java方法將字符串轉換為數字時,如果字符串的格式非法,會拋出運行時異常numberformatexception。
示例
錯誤例子:
1
2
3
4
5
|
public integer getinteger1(string number) { // 如果number格式非法,會拋出numberformatexception returninteger.valueof(number); } |
正確的處理方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public integer getinteger2(string number) { try { returninteger.valueof(number); } catch (numberformatexception e) { ... //記錄日志異常信息 returnnull; } } |
注意:在捕獲異常后一定要記錄日志。
8 資源釋放
解讀
在使用文件、io流、數據庫連接等不會自動釋放的資源時,應該在使用完畢后馬上將其關閉。關閉資源的代碼應該在try...catch...finally的finally內執行,否則可能造成資源無法釋放。
示例
錯誤案例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public voidwriteproduct1(productservicestruct product) { try { filewriter filewriter = new filewriter( "" ); filewriter.append(product.tostring()); // 如果append()拋出異常,close()方法就不會執行,造成io流長時間無法釋放 filewriter.close(); } catch (ioexception e) { ... } } |
關閉io流的正確方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public voidwriteproduct2(productservicestruct product) { filewriter filewriter = null ; try { filewriter = new filewriter( "" ); filewriter.append(product.tostring()); } catch (ioexception e) { ... //記錄日志 } finally { // 不管前面是否發生異常,finally中的代碼一定會執行 if (filewriter != null ) { try { filewriter.close(); } catch (ioexception e) { ... //記錄日志 } } } } |
注意:在捕獲異常后一定要記錄日志。
9 循環體性能
解讀
循環體是軟件中最容易造成性能問題的地方,所以在進行循環體編碼時務必考慮性能問題。
在循環體內重復使用且不會變化的資源(如變量、文件對象、數據庫連接等),應該在循環體開始前構造并初始化,避免在循環體內重復和構造初始化造成cpu資源的浪費。
除非業務場景需要,避免在循環體內構造try...catch塊,因為每次進入、退出try...catch塊都會消耗一定的cpu資源,將try...catch塊放在循環體之外可以節省大量的執行時間。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public voidaddproducts(list<productservicestruct> prodlist) { for (productservicestruct product : prodlist) { // prodsrv在每次循環時都會重新獲取,造成不必要的資源消耗 productservice prodsrv =(productservice) servicelocator.findservice(productservice. class ); // 避免在循環體內try...catch,放在循環體之外可以節省執行時間 try { prodsrv.addproduct(product); } catch (bmeexception e) { ... //記錄日志 } } } |
在循環體中遇到字符串相加,一定要使用stringbuffer這個類。
10 數據類重載tostring()方法
解讀
數據類如果沒有重載tostring()方法,在記錄日志的時候會無法記錄數據對象的屬性值,給定位問題帶來困難。
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public classmdspproductext { privatestring key; privatestring value; publicstring getkey() { returnkey; } publicvoid setkey(string key) { this .key = key; } publicstring getvalue() { returnvalue; } publicvoid setvalue(string value) { this .value = value; } } class businessprocess { privatedebuglog log = logfactory.getdebuglog(businessprocess. class ); publicvoid dobusiness(mdspproductextprodext) { try { ... } catch (pmsexception e) { // mdspproductext未重載tostring()方法,日志中無法記錄對象內屬性的值,只能記錄對象地址 log.error( "error while process prodext " +prodext); } } } |
總結
以上所述是小編給大家介紹的java程序員容易犯的10大低級錯誤,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:http://developer.51cto.com/art/201809/584387.htm