Java開發程式設計師必知的Java程式設計的10種錯誤
作為程式設計師在程式開發的過程中難免的要出現一些不是自己水平問題二出現的一些常見的錯誤。本文就為大家介紹一些常見在Java開發過程中遇見的一些常見的錯誤。
一、常見錯誤1:多次拷貝字串
測試所不能發現的一個錯誤是生成不可變(immutable)物件的多份拷貝。不可變物件是不可改變的,因此不需要拷貝它。最常用的不可變物件是String。
如果你必須改變一個String物件的內容,你應該使用StringBuffer。下面的程式碼會正常工作:
- String s = new String ("Text here");
但是,這段程式碼效能差,而且沒有必要這麼複雜。你還可以用以下的方式來重寫上面的程式碼:
- String temp = "Text here";
- String s = new String (temp);
但是這段程式碼包含額外的String,並非完全必要。更好的程式碼為:
- String s = "Text here";
二、常見錯誤2:沒有克隆(clone)返回的物件
封裝(encapsulation)是面向物件程式設計的重要概念。不幸的是,Java為不小心打破封裝提供了方便——Java允許返回私有資料的引用(reference)。下面的程式碼揭示了這一點:
- import java.awt.Dimension;
- /** *//***Example class.The x and y values should never*be negative.*/
- publicclass Example...{
- private Dimension d = new Dimension (0, 0);
- public Example ()...{ }
- /** *//*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/
- publicsynchronizedvoid setValues (int height,int width) throws IllegalArgumentException...{
- if (height <
- thrownew IllegalArgumentException();
- d.height = height;
- d.width = width;
- }
- publicsynchronized Dimension getValues()...{
- // Ooops! Breaks encapsulation
- return d;
- }
- }
Example類保證了它所儲存的height和width值永遠非負數,試圖使用setValues()方法來設定負值會觸發異常。不幸的是,由於getValues()返回d的引用,而不是d的拷貝,你可以編寫如下的破壞性程式碼:
- Example ex = new Example();
- Dimension d = ex.getValues();
- d.height = -5;
- d.width = -10;
現在,Example物件擁有負值了!如果getValues() 的呼叫者永遠也不設定返回的Dimension物件的width 和height值,那麼僅憑測試是不可能檢測到這類的錯誤。
不幸的是,隨著時間的推移,客戶程式碼可能會改變返回的Dimension物件的值,這個時候,追尋錯誤的根源是件枯燥且費時的事情,尤其是在多執行緒環境中。
更好的方式是讓getValues()返回拷貝:
- publicsynchronized Dimension getValues()...{
- returnnew Dimension (d.x, d.y);
- }
現在,Example物件的內部狀態就安全了。呼叫者可以根據需要改變它所得到的拷貝的狀態,但是要修改Example物件的內部狀態,必須通過setValues()才可以。
三、常見錯誤3:不必要的克隆
我們現在知道了get方法應該返回內部資料物件的拷貝,而不是引用。但是,事情沒有絕對:
- /** *//*** Example class.The value should never * be negative.*/
- publicclass Example...{
- private Integer i = new Integer (0);
- public Example ()...{ }
- /** *//*** Set x. x must be nonnegative* or an exception will be thrown*/
- publicsynchronizedvoid setValues (int x) throws IllegalArgumentException...{
- if (x <0)
- thrownew IllegalArgumentException();
- i = new Integer (x);
- }
- publicsynchronized Integer getValue()...{
- // We can’t clone Integers so we makea copy this way.
- returnnew Integer (i.intValue());
- }
- }
這段程式碼是安全的,但是就象在錯誤1#那樣,又作了多餘的工作。Integer物件,就象String物件那樣,一旦被建立就是不可變的。因此,返回內部Integer物件,而不是它的拷貝,也是安全的。
方法getValue()應該被寫為:
- publicsynchronized Integer getValue()...{
- // ’i’ is immutable, so it is safe to return it instead of a copy.
- return i;
- }
Java程式比C++程式包含更多的不可變物件。JDK 所提供的若干不可變類包括:
- ·Boolean
- ·Byte
- ·Character
- ·Class
- ·Double
- ·Float
- ·Integer
- ·Long
- ·Short
- ·String
- ·大部分的Exception的子類
四、常見錯誤4:自編程式碼來拷貝陣列
Java允許你克隆陣列,但是開發者通常會錯誤地編寫如下的程式碼,問題在於如下的迴圈用三行做的事情,如果採用Object的clone方法用一行就可以完成:
- publicclass Example...{
- privateint[] copy;
- /** *//*** Save a copy of ’data’. ’data’ cannot be null.*/
- publicvoid saveCopy (int[] data)...{
- copy = newint[data.length];
- for (int i = 0; i
- copy[i] = data[i];
- }
- }
這段程式碼是正確的,但卻不必要地複雜。saveCopy()的一個更好的實現是:
- void saveCopy (int[] data)...{
- try...{
- copy = (int[])data.clone();
- }catch (CloneNotSupportedException e)...{
- // Can’t get here.
- }
- }
如果你經常克隆陣列,編寫如下的一個工具方法會是個好主意:
- staticint[] cloneArray (int[] data)...{
- try...{
- return(int[])data.clone();
- }catch(CloneNotSupportedException e)...{
- // Can’t get here.
- }
- }
這樣的話,我們的saveCopy看起來就更簡潔了:
- void saveCopy (int[] data)...{
- copy = cloneArray ( data);
- }
五、常見錯誤5:拷貝錯誤的資料
有時候程式設計師知道必須返回一個拷貝,但是卻不小心拷貝了錯誤的資料。由於僅僅做了部分的資料拷貝工作,下面的程式碼與程式設計師的意圖有偏差:
- import java.awt.Dimension;
- /** *//*** Example class. The height and width values should never * be
- negative. */
- publicclass Example...{
- staticfinalpublicint TOTAL_VALUES = 10;
- private Dimension[] d = new Dimension[TOTAL_VALUES];
- public Example ()...{ }
- /** *//*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */
- publicsynchronizedvoid setValues (int index, int height, int width) throws IllegalArgumentException...{
- if (height <0 || width <0)
- thrownew IllegalArgumentException();
- if (d[index] == null)
- d[index] = new Dimension();
- d[index].height = height;
- d[index].width = width;
- }
- publicsynchronized Dimension[] getValues()
- throws CloneNotSupportedException...{
- return (Dimension[])d.clone();
- }
- }
這兒的問題在於getValues()方法僅僅克隆了陣列,而沒有克隆陣列中包含的Dimension物件,因此,雖然呼叫者無法改變內部的陣列使其元素指向不同的Dimension物件,但是呼叫者卻可以改變內部的陣列元素(也就是Dimension物件)的內容。方法getValues()的更好版本為:
- publicsynchronized Dimension[] getValues() throws CloneNotSupportedException...{
- Dimension[] copy = (Dimension[])d.clone();
- for (int i = 0; i
- // NOTE: Dimension isn’t cloneable.
- if (d != null)
- copy[i] = new Dimension (d[i].height, d[i].width);
- }
- return copy;
- }
在克隆原子型別資料的多維陣列的時候,也會犯類似的錯誤。原子型別包括int,float等。簡單的克隆int型的一維陣列是正確的,如下所示:
- publicvoid store (int[] data) throws CloneNotSupportedException...{
- this.data = (int[])data.clone();
- // OK
- }
拷貝int型的二維陣列更復雜些。Java沒有int型的二維陣列,因此一個int型的二維陣列實際上是一個這樣的一維陣列:它的型別為int[]。簡單的克隆int[][]型的陣列會犯與上面例子中getValues()方法第一版本同樣的錯誤,因此應該避免這麼做。下面的例子演示了在克隆int型二維陣列時錯誤的和正確的做法:
- publicvoid wrongStore (int[][] data) throws CloneNotSupportedException...{
- this.data = (int[][])data.clone(); // Not OK!
- }
- publicvoid rightStore (int[][] data)...{
- // OK!
- this.data = (int[][])data.clone();
- for (int i = 0; i
- if (data != null)
- this.data[i] = (int[])data[i].clone();
- }
- }
六、常見錯誤6:檢查new 操作的結果是否為null
Java程式設計新手有時候會檢查new操作的結果是否為null。可能的檢查程式碼為:
- Integer i = new Integer (400);
- if (i == null)
- thrownew NullPointerException();
檢查當然沒什麼錯誤,但卻不必要,if和throw這兩行程式碼完全是浪費,他們的唯一功用是讓整個程式更臃腫,執行更慢。
C/C++程式設計師在開始寫java程式的時候常常會這麼做,這是由於檢查C中malloc()的返回結果是必要的,不這樣做就可能產生錯誤。檢查C++中new操作的結果可能是一個好的程式設計行為,這依賴於異常是否被使能(許多編譯器允許異常被禁止,在這種情況下new操作失敗就會返回null)。在java 中,new 操作不允許返回null,如果真的返回null,很可能是虛擬機器崩潰了,這時候即便檢查返回結果也無濟於事。
七、常見錯誤7:用== 替代.equals
在Java中,有兩種方式檢查兩個資料是否相等:通過使用==操作符,或者使用所有物件都實現的.equals方法。原子型別(int, flosat, char 等)不是物件,因此他們只能使用==操作符,如下所示:
- int x = 4;
- int y = 5;
- if (x == y)
- System.out.println ("Hi");
- // This ’if’ test won’t compile.
- if (x.equals (y))
- System.out.println ("Hi");
物件更復雜些,==操作符檢查兩個引用是否指向同一個物件,而equals方法則實現更專門的相等性檢查。
更顯得混亂的是由java.lang.Object 所提供的預設的equals方法的實現使用==來簡單的判斷被比較的兩個物件是否為同一個。
許多類覆蓋了預設的equals方法以便更有用些,比如String類,它的equals方法檢查兩個String物件是否包含同樣的字串,而Integer的equals方法檢查所包含的int值是否相等。
大部分時候,在檢查兩個物件是否相等的時候你應該使用equals方法,而對於原子型別的資料,你用該使用==操作符
八、常見錯誤8:混淆原子操作和非原子操作Java保證讀和寫32位數或者更小的值是原子操作,也就是說可以在一步完成,因而不可能被打斷,因此這樣的讀和寫不需要同步。以下的程式碼是執行緒安全(thread safe)的:
- publicclass Example...{
- privateint value; // More code here...
- publicvoid set (int x)...{
- // NOTE: No synchronized keyword
- this.value = x;
- }
- }
不過,這個保證僅限於讀和寫,下面的程式碼不是執行緒安全的:
- publicvoid increment ()...{
- // This is effectively two or three instructions:
- // 1) Read current setting of ’value’.
- // 2) Increment that setting.
- // 3) Write the new setting back.
- ++this.value;
- }
在測試的時候,你可能不會捕獲到這個錯誤。首先,測試與執行緒有關的錯誤是很難的,而且很耗時間。其次,在有些機器上,這些程式碼可能會被翻譯成一條指令,因此工作正常,只有當在其它的虛擬機器上測試的時候這個錯誤才可能顯現。因此最好在開始的時候就正確地同步程式碼:
- publicsynchronizedvoid increment ()...{
- ++this.value;
- }
九、常見錯誤9:在catch 塊中作清除工作
一段在catch塊中作清除工作的程式碼如下所示:
- OutputStream os = null;
- try...{
- os = new OutputStream ();
- // Do something with os here.
- os.close();
- }catch (Exception e)...{
- if (os != null)
- os.close();
- }
儘管這段程式碼在幾個方面都是有問題的,但是在測試中很容易漏掉這個錯誤。下面列出了這段程式碼所存在的三個問題:
1.語句os.close()在兩處出現,多此一舉,而且會帶來維護方面的麻煩。
2.上面的程式碼僅僅處理了Exception,而沒有涉及到Error。但是當try塊執行出現了Error,流也應該被關閉。
3.close()可能會丟擲異常。
上面程式碼的一個更優版本為:
- OutputStream os = null;
- try...{
- os = new OutputStream ();
- // Do something with os here.
- }finally...{
- if (os != null)
- os.close();
- }
這個版本消除了上面所提到的兩個問題:程式碼不再重複,Error也可以被正確處理了。但是沒有好的方法來處理第三個問題,也許最好的方法是把close()語句單獨放在一個try/catch塊中。
十、常見錯誤10: 增加不必要的catch 塊
一些開發者聽到try/catch塊這個名字後,就會想當然的以為所有的try塊必須要有與之匹配的catch塊。
C++程式設計師尤其是會這樣想,因為在C++中不存在finally塊的概念,而且try塊存在的唯一理由只不過是為了與catch塊相配對。
增加不必要的catch塊的程式碼就象下面的樣子,捕獲到的異常又立即被丟擲:
- try...{
- // Nifty code here
- }catch(Exception e)...
- {
- throw e;
- }finally...{
- // Cleanup code here
- }
不必要的catch塊被刪除後,上面的程式碼就縮短為:
- try...{
- // Nifty code here
- }finally...{
- // Cleanup code here
- }
在本文中我為大家分享了十個常見的在Java開發中常見的易發的錯誤,希望大家有了這方面的哈東東以後多多分享。
相關推薦
Java開發程式設計師必知的Java程式設計的10種錯誤
作為程式設計師在程式開發的過程中難免的要出現一些不是自己水平問題二出現的一些常見的錯誤。本文就為大家介紹一些常見在Java開發過程中遇見的一些常見的錯誤。 一、常見錯誤1:多次拷貝字串 測試所不能發現的一個錯誤是生成不可變(immutable)物件的多份拷貝。不可變物件是不可
Java程式設計師必知的併發程式設計藝術——併發機制的底層原理實現
Java程式語言允許執行緒訪問共享變數,為了確保共享變數能被準確和一致的更新,執行緒應該確保通過排他鎖單獨獲得這個變數。 volatile藉助Java記憶體模型保證所有執行緒能夠看到最新的值。(記憶體可見性) 實現原理: 將帶有volatile變數操作的Java程式碼轉
Java程式設計師必知必會的Linux知識——Linux基礎命令(第一章)
一、什麼是Linux?什麼是Windows? 簡單的來說,Linux這個單詞指的是Linux核心(kerhal),Windows指的是Window核心。而Linux系統則指的是Linux核心+Gnu組織的軟體。Windows系統指的是Linux核心+Windows平臺釋出的軟體。 二、L
Java開發程式設計師,最常用的20%技術有哪些?
Web應用,最常見的研發語言是Java和PHP。後端服務,最常見的研發語言是Java和C/C++。大資料,最常見的研發語言是Java和Python。 基本可以說,Java是現階段中國網際網路公司中覆蓋度最廣的研發語言,掌握了Java技術體系,不管在成熟的大公司,快速發展的公司,還是創業階段的公司
Java開發程式設計師必須知道
前言 在這個行業參加工作4年到5年的攻城獅的朋友們,相信你在自己所鑽研的領域已經有了自己一定的見解,這個時候,技術上你應該已經遇到瓶頸了。這個時候不要著急提高自己的技術,已經是時候提高你的影響力了,你可以嘗試去一些知名的公司去提高你的背景,你可以發表一些文章去影響更多的人。 當然,你也可以去
程式設計師必知的10大基礎實用性演算法
轉載自:http://www.apkbus.com/portal.php?mod=view&aid=9839 演算法一:快速排序演算法
程式設計師必知--程式碼規範
首先用我之前的部落格中的程式碼舉例子 (C語言程式設計100例): 第一段程式碼: #include <stdio.h> int main(){ int i, j, n = 0, a[17] = { 0,1 }, l, r; while (n<1 || n>1
程式設計師必知單詞、語句、英文縮寫彙總
綜述:便於類,函式命名,工作文件閱讀而做的單詞積累,還是很有用的,不積跬步,無以至千里,紅色僅代表不是很熟悉 一.邏輯編碼部分 application 應用程式 應用、應用程式 application framework 應用程式框架、應用框架 應用程式框架
OneCoder翻譯 每個程式設計師必知的知識,UniCode和字符集(下)
好吧,從技術上講,可以,我相信他可以。事實上,早起的實現者想用high-endian和low-endian兩種模式儲存Unicode字元碼,不論哪種方式都是他們特定的CPU最快的處理方式。呵呵,夜以繼日,現在就有了兩種儲存Unicode的
OneCoder翻譯 每個程式設計師必知的知識,UniCode和字符集(上)
今天在處理了一個編碼的問題,激發了筆者強烈的弄清編碼問題的好奇心。遂先有了前面強烈推薦的文章:字元編碼介紹 通俗易懂 強烈推薦。下面是上篇文章中提到的延伸閱讀裡,第一篇文章的翻譯。水平有限,各位看官,勉強理解一下,錯誤之處還望指出。 你是否
程式設計師必知(一):CSRF跨站請求偽造
首先說明一下什麼是CSRF(Cross Site Request Forgery)? 跨站請求偽造是指攻擊者可以在第三方站點製造HTTP請求並以使用者在目標站點的登入態傳送到目標站點,而目標站點未校驗
程式設計師必知的六種隔離技術
為了將我們的應用部署到伺服器上,我們需要為其配置一個執行環境。從底層到頂層有這樣的執行環境及容器: 隔離硬體:虛擬機器 隔離作業系統:容器虛擬化 隔離底層:Servlet容器 隔離依賴版本:虛擬環境 隔離執行環境:語言虛擬機器
程式設計師必知的七個圖形工具
流程圖:Graphviz 說到流程圖還是再次提及一下,我們之前說到的 Graphviz 。 Graphviz (英文:Graph Visualization Software的縮寫)是一個由AT&T實驗室啟動的開源工具包,用於繪製DOT語言指令碼描述的圖形。它
Android程式設計師必知必會的網路通訊傳輸層協議——UDP和TCP
1、點評 網際網路發展至今已經高度發達,而對於網際網路應用(尤其即時通訊技術這一塊)的開發者來說,網路程式設計是基礎中的基礎,只有更好地理解相關基礎知識,對於應用層的開發才能做到遊刃有餘。 對於Android程式設計師來說,如果您覺得本文內容稍顯枯燥,可以看看即時通訊網之前整理過的一篇類似文
程式設計師必知之浮點數運算原理詳解
導讀:浮點數運算是一個非常有技術含量的話題,不太容易掌握。許多程式設計師都不清楚使用==操作符比較float/double型別的話到底出現什麼問題。 許多人使用float/double進行貨幣計算時經常會犯錯。這篇文章是這一系列中的精華,所有的軟體開發人員都應該讀一下。
程式設計師必知的10大基礎實用演算法
演算法一:快速排序演算法
「乾貨總結」程式設計師必知必會的十大排序演算法
> 首發公眾號:**bigsai** 轉載需聯絡 新人求支援 > 文章已收錄在 [bigsai-algorithm](https://github.com/javasmall/bigsai-algorithm) 長期維護 ## 緒論 身為程式設計師,十大排序是是所有合格程式設計師所必備和掌握的
java程式設計思想重點筆記(java程式設計師必看)
Java中的多型性理解(注意與C++區分) Java中除了static方法和final方法(private方法本質上屬於final方法,因為不能被子類訪問)之外,其它所有的方法都是動態繫結,這意味著通常情況下,我們不必判定是否應該進行動態繫結—它會自動發生。 fin
【木木與呆呆的專欄】Java程式設計師,專注Java程式設計技術,開發以後臺為主前臺為輔,目前投身Hadoop大資料以及Ranger安全開發,活躍在大資料開源社群,同時推薦多種高效便捷的開發工具,分享自己的各種經驗技巧總結。
Java程式設計師,專注Java程式設計技術,開發以後臺為主前臺為輔,目前投身Hadoop大資料以及Ranger安全開發,活躍在大資料開源社群,同時推薦多種高效便捷的開發工具,分享自己的各種經驗技巧總結。...
Java程式設計師必看的15本書的電子版下載地址
Java程式設計師必看的15本書的電子版下載地址 作為Java程式設計師來說,最痛苦的事情莫過於可以選擇的範圍太廣,可以讀的書太多,往往容易無所適從。我想就我自己讀過的技術書籍中挑選出來一些,按照學習的先後順序,推薦給大家,特別是那些想不斷提高自己技術水平的Java程式設計