熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Java編程:常見問題匯總

2022-06-13   來源: Java核心技術 

  字符串連接誤用

  錯誤的寫法


  


    String s = ;  
    for (Person p : persons) {  
        s +=   + pgetName();  
    }  
    s = ssubstring(); //remove first comma 

  正確的寫法


  


    StringBuilder sb = new StringBuilder(personssize() * ); // well estimated buffer  
    for (Person p : persons) {  
        if (sblength() > ) sbappend( );  
        sbappend(pgetName);  

  錯誤的使用StringBuffer

  錯誤的寫法


  


    StringBuffer sb = new StringBuffer();  
    sbappend(Name: );  
    sbappend(name + \n);  
    sbappend(!);  
     
    String s = sbtoString(); 

  問題在第三行append char比String性能要好另外就是初始化StringBuffer沒有指定size導致中間append時可能重新調整內部數組大小如果是JDK最好用StringBuilder取代StringBuffer除非有線程安全的要求還有一種方式就是可以直接連接字符串缺點就是無法初始化時指定長度

  正確的寫法


  


    StringBuilder sb = new StringBuilder();  
    sbappend(Name: );  
    sbappend(name);  
    sbappend(\n!);  
    String s = sbtoString(); 

  或者這樣寫


  


    String s = Name:  + name + \n!

  測試字符串相等性

  錯誤的寫法


  


    if (pareTo(John) ==   
    if (name == John  
    if (nameequals(John))   
    if (equals(name))  

  上面的代碼沒有錯但是不夠好compareTo不夠簡潔==原義是比較兩個對象是否一樣另外比較字符是否為空最好判斷它的長度

  正確的寫法


  


    if (Johnequals(name))   
    if (namelength() ==   
    if (nameisEmpty())  

  數字轉換成字符串

  錯誤的寫法


  


     + setsize()  
    new Integer(setsize())toString()  

  正確的寫法


  


    StringvalueOf(setsize()) 

  利用不可變對象(Immutable)

  錯誤的寫法


  


    zero = new Integer();  
    return BooleanvalueOf(true); 

  正確的寫法


  


    zero = IntegervalueOf();  
    return BooleanTRUE; 

  請使用XML解析器

  錯誤的寫法


  


    int start = xmlindexOf(<name>) + <name>length();  
    int end = xmlindexOf(</name>);  
    String name = xmlsubstring(start end); 

  正確的寫法


  


    SAXBuilder builder = new SAXBuilder(false);  
    Document doc = doc = builderbuild(new StringReader(xml));  
    String name = docgetRootElement()getChild(name)getText(); 

  請使用JDom組裝XML

  錯誤的寫法


  


    String name =   
    String attribute =   
    String xml = <root> 
                +<name att=\+ attribute +\>+ name +</name> 
                +</root>

  正確的寫法


  


    Element root = new Element(root);  
    rootsetAttribute(att attribute);  
    rootsetText(name);  
    Document doc = new Documet();  
    docsetRootElement(root);  
    XmlOutputter out = new XmlOutputter(FormatgetPrettyFormat());  
    String xml = outoutputString(root); 

  XML編碼陷阱

  錯誤的寫法


  


    String xml = FileUtilsreadTextFile(myxml); 

  因為xml的編碼在文件中指定的而在讀文件的時候必須指定編碼另外一個問題不能一次就將一個xml文件用String保存這樣對內存會造成不必要的浪費正確的做法用InputStream來邊讀取邊處理為了解決編碼的問題 最好使用XML解析器來處理

  未指定字符編碼

  錯誤的寫法


  


    Reader r = new FileReader(file);  
    Writer w = new FileWriter(file);  
    Reader r = new InputStreamReader(inputStream);  
    Writer w = new OutputStreamWriter(outputStream);  
    String s = new String(byteArray); // byteArray is a byte[]  
    byte[] a = stringgetBytes(); 

  這樣的代碼主要不具有跨平台可移植性因為不同的平台可能使用的是不同的默認字符編碼

  正確的寫法


  


    Reader r = new InputStreamReader(new FileInputStream(file) ISO);  
    Writer w = new OutputStreamWriter(new FileOutputStream(file) ISO);  
    Reader r = new InputStreamReader(inputStream UTF);  
    Writer w = new OutputStreamWriter(outputStream UTF);  
    String s = new String(byteArray ASCII);  
    byte[] a = stringgetBytes(ASCII); 

  未對數據流進行緩存

  錯誤的寫法


  


    InputStream in = new FileInputStream(file);   
    int b;   
    while ((b = inread()) != ) {   
       
    }  

  上面的代碼是一個byte一個byte的讀取導致頻繁的本地JNI文件系統訪問非常低效因為調用本地方法是非常耗時的最好用BufferedInputStream包裝一下曾經做過一個測試從/dev/zero下讀取MB大概花了s而用BufferedInputStream包裝之後只需要ms性能提高了%! 這個也適用於output stream操作以及socket操作

  正確的寫法


  


    InputStream in = new BufferedInputStream(new FileInputStream(file));  

  無限使用heap內存

  錯誤的寫法


  


    byte[] pdf = toPdf(file);  

  這裡有一個前提就是文件大小不能講JVM的heap撐爆否則就等著OOM吧尤其是在高並發的服務器端代碼最好的做法是采用Stream的方式邊讀取邊存儲(本地文件或database)

  正確的寫法


  


    File pdf = toPdf(file);  

  另外對於服務器端代碼來說為了系統的安全至少需要對文件的大小進行限制

  不指定超時時間

  錯誤的代碼


  


    Socket socket =    
    nnect(remote);   
    InputStream in = socketgetInputStream();   
    int i = inread();  

  這種情況在工作中已經碰到不止一次了個人經驗一般超時不要超過s這裡有一個問題connect可以指定超時時間但是read無法指定超時時間但是可以設置阻塞(block)時間

  正確的寫法


  


    Socket socket =    
    nnect(remote ); // fail after s   
    InputStream in = socketgetInputStream();   
    socketsetSoTimeout();   
    int i = inread();  

  另外文件的讀取(FileInputStream FileChannel FileDescriptor File)沒法指定超時時間 而且IO操作均涉及到本地方法調用 這個更操作了JVM的控制范圍在分布式文件系統中對IO的操作內部實際上是網絡調用一般情況下操作s的操作都可以認為已經超時了為了解決這些問題一般采用緩存和異步/消息隊列處理

  頻繁使用計時器

  錯誤代碼


  


    for () {   
    long t = SystemcurrentTimeMillis();   
    long t = SystemnanoTime();   
    Date d = new Date();   
    Calendar c = new GregorianCalendar();   
    }  

  每次new一個Date或Calendar都會涉及一次本地調用來獲取當前時間(盡管這個本地調用相對其他本地方法調用要快)
如果對時間不是特別敏感這裡使用了clone方法來新建一個Date實例這樣相對直接new要高效一些

  正確的寫法


  


    Date d = new Date();   
    for (E entity : entities) {   
    entitydoSomething();   
    entitysetUpdated((Date) dclone());   
    }  

  如果循環操作耗時較長(超過幾ms)那麼可以采用下面的方法立即創建一個Timer然後定期根據當前時間更新時間戳在我的系統上比直接new一個時間對象快


  


    private volatile long time;   
    Timer timer = new Timer(true);   
    try {   
    time = SystemcurrentTimeMillis();   
    timerscheduleAtFixedRate(new TimerTask() {   
    public void run() {   
    time = SystemcurrentTimeMillis();   
    }   
    } L L); // granularity ms   
    for (E entity : entities) {   
    entitydoSomething();   
    entitysetUpdated(new Date(time));   
    }   
    } finally {   
    timercancel();   
    }  

  捕獲所有的異常

  錯誤的寫法


  


    Query q =    
    Person p;   
    try {   
    p = (Person) qgetSingleResult();   
    } catch(Exception e) {   
    p = null;   
    }  

  這是EJB的一個查詢操作可能出現異常的原因是結果不唯一沒有結果數據庫無法訪問而捕獲所有的異常設置為null將掩蓋各種異常情況

  正確的寫法


  


    Query q =    
    Person p;   
    try {   
    p = (Person) qgetSingleResult();   
    } catch(NoResultException e) {   
    p = null;   
    }  

  忽略所有異常

  錯誤的寫法


  


    try {   
    doStuff();   
    } catch(Exception e) {   
    logfatal(Could not do stuff);   
    }   
    doMoreStuff();  

  這個代碼有兩個問題 一個是沒有告訴調用者 系統調用出錯了 第二個是日志沒有出錯原因 很難跟蹤定位問題

  正確的寫法


  


    try {   
    doStuff();   
    } catch(Exception e) {   
    throw new MyRuntimeException(Could not do stuff because: + egetMessage e);   
    }  

  重復包裝RuntimeException

  錯誤的寫法


  


    try {   
    doStuff();   
    } catch(Exception e) {   
    throw new RuntimeException(e);   
    }  

  正確的寫法



    try {   
    doStuff();   
    } catch(RuntimeException e) {   
    throw e;   
    } catch(Exception e) {   
    throw new RuntimeException(egetMessage() e);   
    }   
    try {   
    doStuff();   
    } catch(IOException e) {   
    throw new RuntimeException(egetMessage() e);   
    } catch(NamingException e) {   
    throw new RuntimeException(egetMessage() e);   

  不正確的傳播異常

  錯誤的寫法


  


    try {   
    } catch(ParseException e) {   
    throw new RuntimeException();   
    throw new RuntimeException(etoString());   
    throw new RuntimeException(egetMessage());   
    throw new RuntimeException(e);   
    }  

  主要是沒有正確的將內部的錯誤信息傳遞給調用者 第一個完全丟掉了內部錯誤信息 第二個錯誤信息依賴toString方法 如果沒有包含最終的嵌套錯誤信息 也會出現丟失 而且可讀性差 第三個稍微好一些 第四個跟第二個一樣

  正確的寫法


  


    try {   
    } catch(ParseException e) {   
    throw new RuntimeException(egetMessage() e);   
    }  

  用日志記錄異常

  錯誤的寫法


  


    try {   
       
    } catch(ExceptionA e) {   
    logerror(egetMessage() e);   
    throw e;   
    } catch(ExceptionB e) {   
    logerror(egetMessage() e);   
    throw e;   
    }  

  一般情況下在日志中記錄異常是不必要的 除非調用方沒有記錄日志

  異常處理不徹底

  錯誤的寫法


  


    try {   
    is = new FileInputStream(inFile);   
    os = new FileOutputStream(outFile);   
    } finally {   
    try {   
    isclose();   
    osclose();   
    } catch(IOException e) {   
    /* we cant do anything */   
    }   
    }  

  is可能close失敗 導致os沒有close

  正確的寫法


  


    try {   
    is = new FileInputStream(inFile);   
    os = new FileOutputStream(outFile);   
    } finally {   
    try { if (is != null) isclose(); } catch(IOException e) {/* we cant do anything */}   
    try { if (os != null) osclose(); } catch(IOException e) {/* we cant do anything */}   
    }  

  捕獲不可能出現的異常

  錯誤的寫法


  


    try {   
     do risky stuff    
    } catch(SomeException e) {   
    // never happens   
    }   
     do some more   

  正確的寫法


  


    try {   
     do risky stuff    
    } catch(SomeException e) {   
    // never happens hopefully   
    throw new IllegalStateException(egetMessage() e); // crash early passing all information   
    }   
     do some more   

  transient的誤用

  錯誤的寫法


  


    public class A implements Serializable {   
    private String someState;   
    private transient Log log = LogFactorygetLog(getClass());   
     
    public void f() {   
    logdebug(enter f);   
       
    }   
    }  

  這裡的本意是不希望Log對象被序列化 不過這裡在反序列化時 會因為log未初始化 導致f()方法拋空指針 正確的做法是將log定義為靜態變量或者定位為具備變量

  正確的寫法


  


    public class A implements Serializable {   
    private String someState;   
    private static final Log log = LogFactorygetLog(Aclass);   
     
    public void f() {   
    logdebug(enter f);   
       
    }   
    }   
    public class A implements Serializable {   
    private String someState;   
     
    public void f() {   
    Log log = LogFactorygetLog(getClass());   
    logdebug(enter f);   
       
    }   
    }  

  不必要的初始化

  錯誤的寫法


  


    public class B {   
    private int count = ;   
    private String name = null;   
    private boolean important = false;   
    }  

  這裡的變量會在初始化時使用默認值: null false 因此上面的寫法有些多此一舉

  正確的寫法


  


    public class B {   
    private int count;   
    private String name;   
    private boolean important;   
    }  

  最好用靜態final定義Log變量


  


    private static final Log log = LogFactorygetLog(MyClassclass);  

  這樣做的好處有三



可以保證線程安全
靜態或非靜態代碼都可用
不會影響對象序列化

  選擇錯誤的類加載器

  錯誤的代碼

  


    Class clazz = ClassforName(name);   
    Class clazz = getClass()getClassLoader()loadClass(name);  

  這裡本意是希望用當前類來加載希望的對象 但是這裡的getClass()可能拋出異常 特別在一些受管理的環境中 比如應用服務器 web容器 Java WebStart環境中 最好的做法是使用當前應用上下文的類加載器來加載

  正確的寫法

  


    ClassLoader cl = ThreadcurrentThread()getContextClassLoader();   
    if (cl == null) cl = MyClassclassgetClassLoader(); // fallback   
    Class clazz = clloadClass(name);  

  反射使用不當

  錯誤的寫法

  


    Class beanClass =    
    if (beanClassnewInstance() instanceof TestBean)   

  這裡的本意是檢查beanClass是否是TestBean或是其子類 但是創建一個類實例可能沒那麼簡單 首先實例化一個對象會帶來一定的消耗 另外有可能類沒有定義默認構造函數 正確的做法是用ClassisAssignableFrom(Class) 方法

  正確的寫法

  


    Class beanClass =    
    if (TestBeanclassisAssignableFrom(beanClass))   

  不必要的同步

  錯誤的寫法

  


    Collection l = new Vector();   
    for () {   
    ladd(object);   
    }  

  Vector是ArrayList同步版本

  正確的寫法

  


    Collection l = new ArrayList();   
    for () {   
    ladd(object);   
    }  

  錯誤的選擇List類型

  根據下面的表格數據來進行選擇

  ArrayList LinkedList add (append) O() or ~O(log(n)) if growing O() insert (middle) O(n) or ~O(n*log(n)) if growing O(n) remove (middle) O(n) (always performs complete copy) O(n) iterate O(n) O(n) get by index O() O(n)

  HashMap size陷阱

  錯誤的寫法

  


    Map map = new HashMap(collectionsize());  
    for (Object o : collection) {  
      mapput(okey ovalue);  

  這裡可以參考guava的MapsnewHashMapWithExpectedSize的實現 用戶的本意是希望給HashMap設置初始值 避免擴容(resize)的開銷 但是沒有考慮當添加的元素數量達到HashMap容量的%時將出現resize

  正確的寫法

  


    Map map = new HashMap( + (int) (collectionsize() / )); 

  對Hashtable HashMap 和 HashSet了解不夠

  這裡主要需要了解HashMap和Hashtable的內部實現上 它們都使用Entry包裝來封裝key/value Entry內部除了要保存Key/Value的引用 還需要保存hash桶中next Entry的應用 因此對內存會有不小的開銷 而HashSet內部實現其實就是一個HashMap 有時候IdentityHashMap可以作為一個不錯的替代方案 它在內存使用上更有效(沒有用Entry封裝 內部采用Object[]) 不過需要小心使用 它的實現違背了Map接口的定義 有時候也可以用ArrayList來替換HashSet

  這一切的根源都是由於JDK內部沒有提供一套高效的Map和Set實現

  對List的誤用

  建議下列場景用Array來替代List:


list長度固定比如一周中的每一天
對list頻繁的遍歷比如超過w次
需要對數字進行包裝(主要JDK沒有提供基本類型的List)

  比如下面的代碼

  錯誤的寫法

  


    List<Integer> codes = new ArrayList<Integer>();  
    codesadd(IntegervalueOf());  
    codesadd(IntegervalueOf());  
    codesadd(IntegervalueOf());  
    codesadd(IntegervalueOf()); 

  正確的寫法

  


    int[] codes = {     }; 

  錯誤的寫法

  


    // horribly slow and a memory waster if l has a few thousand elements (try it yourself!)  
    List<Mergeable> l = ;  
    for (int i=; i < lsize(); i++) {  
        Mergeable one = lget(i);  
        Iterator<Mergeable> j = erator(i+); // memory allocation!  
        while (jhasNext()) {  
            Mergeable other = lnext();  
            if (onecanMergeWith(other)) {  
                rge(other);  
                otherremove();  
            }  
        }  

  正確的寫法

  


    // quite fast and no memory allocation  
    Mergeable[] l = ;  
    for (int i=; i < llength; i++) {  
        Mergeable one = l[i];  
        for (int j=i+; j < llength; j++) {  
            Mergeable other = l[j];  
            if (onecanMergeWith(other)) {  
                rge(other);  
                l[j] = null;  
            }  
        }  

  實際上Sun也意識到這一點 因此在JDK中 Collectionssort()就是將一個List拷貝到一個數組中然後調用Arrayssort方法來執行排序

  用數組來描述一個結構

  錯誤用法

  


    /**   
    * @returns []: Location []: Customer []: Incident   
    */   
    Object[] getDetails(int id) {  

  這裡用數組+文檔的方式來描述一個方法的返回值 雖然很簡單 但是很容易誤用 正確的做法應該是定義個類

  正確的寫法

  


    Details getDetails(int id) {}   
    private class Details {   
    public Location location;   
    public Customer customer;   
    public Incident incident;   
    }  

  對方法過度限制

  錯誤用法

  


    public void notify(Person p) {   
       
    sendMail(pgetName() pgetFirstName() pgetEmail());   
       
    }   
    class PhoneBook {   
    String lookup(String employeeId) {   
    Employee emp =    
    return empgetPhone();   
    }   
    }  

  第一個例子是對方法參數做了過多的限制 第二個例子對方法的返回值做了太多的限制

  正確的寫法

  


    public void notify(Person p) {   
       
    sendMail(p);   
       
    }   
    class EmployeeDirectory {   
    Employee lookup(String employeeId) {   
    Employee emp =    
    return emp;   
    }   

  對POJO的setter方法畫蛇添足

  錯誤的寫法

  


    private String name;   
    public void setName(String name) {   
    thisname = nametrim();   
    }   
    public void String getName() {   
    return thisname;   
    }  

  有時候我們很討厭字符串首尾出現空格 所以在setter方法中進行了trim處理 但是這樣做的結果帶來的副作用會使getter方法的返回值和setter方法不一致 如果只是將JavaBean當做一個數據容器 那麼最好不要包含任何業務邏輯 而將業務邏輯放到專門的業務層或者控制層中處理

  正確的做法

  


    personsetName(textInputgetText()trim());  

  日歷對象(Calendar)誤用

  錯誤的寫法

  


    Calendar cal = new GregorianCalender(TimeZonegetTimeZone(Europe/Zurich));   
    calsetTime(date);   
    caladd(CalendarHOUR_OF_DAY );   
    date = calgetTime();  

  這裡主要是對date time calendar和time zone不了解導致 而在一個時間上增加小時 跟time zone沒有任何關系 所以沒有必要使用Calendar 直接用Date對象即可 而如果是增加天數的話 則需要使用Calendar 因為采用不同的時令制可能一天的小時數是不同的(比如有些DST是或者個小時)

  正確的寫法

  


    date = new Date(dategetTime() + L * L * L); // add  hrs  

  TimeZone的誤用

  錯誤的寫法

  


    Calendar cal = new GregorianCalendar();   
    calsetTime(date);   
    calset(CalendarHOUR_OF_DAY );   
    calset(CalendarMINUTE );   
    calset(CalendarSECOND );   
    Date startOfDay = calgetTime();  

  這裡有兩個錯誤 一個是沒有沒有將毫秒歸零 不過最大的錯誤是沒有指定TimeZone 不過一般的桌面應用沒有問題 但是如果是服務器端應用則會有一些問題 比如同一時刻在上海和倫敦就不一樣 因此需要指定的TimeZone

  正確的寫法

  


    Calendar cal = new GregorianCalendar(usergetTimeZone());   
    calsetTime(date);   
    calset(CalendarHOUR_OF_DAY );   
    calset(CalendarMINUTE );   
    calset(CalendarSECOND );   
    calset(CalendarMILLISECOND );   
    Date startOfDay = calgetTime();  

  時區(Time Zone)調整的誤用

  錯誤的寫法


    public static Date convertTz(Date date TimeZone tz) {   
    Calendar cal = CalendargetInstance();   
    calsetTimeZone(TimeZonegetTimeZone(UTC));   
    calsetTime(date);   
    calsetTimeZone(tz);   
    return calgetTime();   
    }  

      這個方法實際上沒有改變時間 輸入和輸出是一樣的 關於時間的問題可以參考這篇文章: 這裡主要的問題是Date對象並不包含Time Zone信息 它總是使用UTC(世界統一時間) 而調用Calendar的getTime/setTime方法會自動在當前時區和UTC之間做轉換

      CalendargetInstance()的誤用

      錯誤的寫法

      


      Calendar c = CalendargetInstance();   
      cset( CalendarJANUARY );  

      CalendargetInstance()依賴local來選擇一個Calendar實現 不同實現的年是不同的 比如有些Calendar實現就沒有January月份

      正確的寫法

      


      Calendar c = new GregorianCalendar(timeZone);   
      cset( CalendarJANUARY );  

      DatesetTime()的誤用

      錯誤的寫法

      


      accountchangePassword(oldPass newPass);   
      Date lastmod = accountgetLastModified();   
      lastmodsetTime(SystemcurrentTimeMillis());  

      在更新密碼之後 修改一下最後更新時間 這裡的用法沒有錯但是有更好的做法: 直接傳Date對象 因為Date是Value Object 不可變的 如果更新了Date的值 實際上是生成一個新的Date實例 這樣其他地方用到的實際上不在是原來的對象 這樣可能出現不可預知的異常 當然這裡又涉及到另外一個OO設計的問題 對外暴露Date實例本身就是不好的做法(一般的做法是在setter方法中設置Date引用參數的clone對象) 另外一種比較好的做法就是直接保存long類型的毫秒數

      正確的做法

      


      accountchangePassword(oldPass newPass);   
      accountsetLastModified(new Date());  

      SimpleDateFormat非線程安全誤用

      錯誤的寫法

      


      public class Constants {   
      public static final SimpleDateFormat date = new SimpleDateFormat(ddMMyyyy);   
      }  

      SimpleDateFormat不是線程安全的 在多線程並行處理的情況下 會得到非預期的值 這個錯誤非常普遍! 如果真要在多線程環境下公用同一個SimpleDateFormat 那麼做好做好同步(cache flush lock contention) 但是這樣會搞得更復雜 還不如直接new一個實在

      使用全局參數配置常量類/接口

      


      public interface Constants {   
      String version = ;   
      String dateFormat = ddMMyyyy;   
      String configFile = apprc;   
      int maxNameLength = ;   
      String someQuery = SELECT * FROM ;   
      }  

      很多應用都會定義這樣一個全局常量類或接口 但是為什麼這種做法不推薦? 因為這些常量之間基本沒有任何關聯 只是因為公用才定義在一起 但是如果其他組件需要使用這些全局變量 則必須對該常量類產生依賴 特別是存在server和遠程client調用的場景

      比較好的做法是將這些常量定義在組件內部 或者局限在一個類庫內部

      忽略造型溢出(cast overflow)

      錯誤的寫法

      


      public int getFileSize(File f) {   
      long l = flength();   
      return (int) l;   
      }  

      這個方法的本意是不支持傳遞超過GB的文件 最好的做法是對長度進行檢查 溢出時拋出異常

      正確的寫法

      


      public int getFileSize(File f) {   
      long l = flength();   
      if (l > IntegerMAX_VALUE) throw new IllegalStateException(int overflow);   
      return (int) l;   
      }  

      另一個溢出bug是cast的對象不對 比如下面第一個println 正確的應該是下面的那個

      


      long a = SystemcurrentTimeMillis();   
      long b = a + ;   
      Systemoutprintln((int) ba);   
      Systemoutprintln((int) (ba));  

      對float和double使用==操作

      錯誤的寫法

      


      for (float f = f; f!=; f=) {   
      Systemoutprintln(f);   
      }  

      上面的浮點數遞減只會無限接近而不會等於 這樣會導致上面的for進入死循環 通常絕不要對float和double使用==操作 而采用大於和小於操作 如果java編譯器能針對這種情況給出警告 或者在java語言規范中不支持浮點數類型的==操作就最好了

      正確的寫法

      


      for (float f = f; f>; f=) {   
      Systemoutprintln(f);   
      }  

      用浮點數來保存money

      錯誤的寫法

      


      float total = f;   
      for (OrderLine line : lines) {   
      total += lineprice * unt;   
      }   
      double a =  * ; //  將表示為    
      Systemoutprintln(Mathround(a)); // 輸出值為   
      BigDecimal d = new BigDecimal(); //造成精度丟失  

      這個也是一個老生常談的錯誤 比如計算筆訂單 每筆 最終的計算結果是 如果將float類型改為double類型 得到的結果將是 出現這種情況的原因是 人類和計算的計數方式不同 人類采用的是十進制 而計算機是二進制二進制對於計算機來說非常好使 但是對於涉及到精確計算的場景就會帶來誤差 比如銀行金融中的應用

      因此絕不要用浮點類型來保存money數據 采用浮點數得到的計算結果是不精確的 即使與int類型做乘法運算也會產生一個不精確的結果那是因為在用二進制存儲一個浮點數時已經出現了精度丟失 最好的做法就是用一個string或者固定點數來表示 為了精確 這種表示方式需要指定相應的精度值
    BigDecimal就滿足了上面所說的需求 如果在計算的過程中精度的丟失超出了給定的范圍 將拋出runtime exception

      正確的寫法

      


      BigDecimal total = BigDecimalZERO;   
      for (OrderLine line : lines) {   
      BigDecimal price = new BigDecimal(lineprice);   
      BigDecimal count = new BigDecimal(unt);   
      total = totaladd(pricemultiply(count)); // BigDecimal is immutable!   
      }   
      total = totalsetScale( RoundingModeHALF_UP);   
      BigDecimal a = (new BigDecimal())multiply(new BigDecimal()); //  exact   
      a = asetScale( RoundingModeHALF_UP); //    
      Systemoutprintln(a); // correct output:    
      BigDecimal a = new BigDecimal();  

      不使用finally塊釋放資源

      錯誤的寫法

      


      public void save(File f) throws IOException {   
      OutputStream out = new BufferedOutputStream(new FileOutputStream(f));   
      outwrite();   
      outclose();   
      }   
      public void load(File f) throws IOException {   
      InputStream in = new BufferedInputStream(new FileInputStream(f));   
      inread();   
      inclose();   
      }  

      上面的代碼打開一個文件輸出流 操作系統為其分配一個文件句柄 但是文件句柄是一種非常稀缺的資源 必須通過調用相應的close方法來被正確的釋放回收 而為了保證在異常情況下資源依然能被正確回收 必須將其放在finally block中 上面的代碼中使用了BufferedInputStream將file stream包裝成了一個buffer stream 這樣將導致在調用close方法時才會將buffer stream寫入磁盤 如果在close的時候失敗 將導致寫入數據不完全 而對於FileInputStream在finally block的close操作這裡將直接忽略

      如果BufferedOutputStreamclose()方法執行順利則萬事大吉 如果失敗這裡有一個潛在的bug(_bugdo?bug_id=): 在close方法內部調用flush操作的時候 如果出現異常 將直接忽略 因此為了盡量減少數據丟失 在執行close之前顯式的調用flush操作

      下面的代碼有一個小小的瑕疵: 如果分配file stream成功 但是分配buffer stream失敗(OOM這種場景) 將導致文件句柄未被正確釋放 不過這種情況一般不用擔心 因為JVM的gc將幫助我們做清理

      


      // code for your cookbook   
      public void save() throws IOException {   
      File f =    
      OutputStream out = new BufferedOutputStream(new FileOutputStream(f));   
      try {   
      outwrite();   
      outflush(); // dont lose exception by implicit flush on close   
      } finally {   
      outclose();   
      }   
      }   
      public void load(File f) throws IOException {   
      InputStream in = new BufferedInputStream(new FileInputStream(f));   
      try {   
      inread();   
      } finally {   
      try { inclose(); } catch (IOException e) { }   
      }   
      }  

      數據庫訪問也涉及到類似的情況

      


      Car getCar(DataSource ds String plate) throws SQLException {   
      Car car = null;   
      Connection c = null;   
      PreparedStatement s = null;   
      ResultSet rs = null;   
      try {   
      c = dsgetConnection();   
      s = cprepareStatement(select make color from cars where plate=?);   
      ssetString( plate);   
      rs = sexecuteQuery();   
      if (rsnext()) {   
      car = new Car();   
      carmake = rsgetString();   
      lor = rsgetString();   
      }   
      } finally {   
      if (rs != null) try { rsclose(); } catch (SQLException e) { }   
      if (s != null) try { sclose(); } catch (SQLException e) { }   
      if (c != null) try { cclose(); } catch (SQLException e) { }   
      }   
      return car;   
      }  

      finalize方法誤用

      錯誤的寫法

      


      public class FileBackedCache {   
      private File backingStore;   
       
         
       
      protected void finalize() throws IOException {   
      if (backingStore != null) {   
      backingStoreclose();   
      backingStore = null;   
      }   
      }   
      }  

      這個問題Effective Java這本書有詳細的說明 主要是finalize方法依賴於GC的調用 其調用時機可能是立馬也可能是幾天以後 所以是不可預知的 而JDK的API文檔中對這一點有誤導建議在該方法中來釋放I/O資源

      正確的做法是定義一個close方法 然後由外部的容器來負責調用釋放資源

      


      public class FileBackedCache {   
      private File backingStore;   
       
         
       
      public void close() throws IOException {   
      if (backingStore != null) {   
      backingStoreclose();   
      backingStore = null;   
      }   
      }   
      }  

      在JDK (Java )中已經引入了一個AutoClosable接口 當變量(不是對象)超出了trycatch的資源使用范圍 將自動調用close方法

      


      try (Writer w = new FileWriter(f)) { // implements Closable   
      wwrite(abc);   
      // w goes out of scope here: wclose() is called automatically in ANY case   
      } catch (IOException e) {   
      throw new RuntimeException(egetMessage() e);   
      }  

      Threadinterrupted方法誤用

      錯誤的寫法

      


      try {   
      Threadsleep();   
      } catch (InterruptedException e) {   
      // ok   
      }   
      or   
      while (true) {   
      if (Threadinterrupted()) break;   
      }  

      這裡主要是interrupted靜態方法除了返回當前線程的中斷狀態 還會將當前線程狀態復位

      正確的寫法

      


      try {   
      Threadsleep();   
      } catch (InterruptedException e) {   
      ThreadcurrentThread()interrupt();   
      }   
      or   
      while (true) {   
      if (ThreadcurrentThread()isInterrupted()) break;   
      }  

      在靜態變量初始化時創建線程

      錯誤的寫法

      


      class Cache {   
      private static final Timer evictor = new Timer();   
      }  

      Timer構造器內部會new一個thread 而該thread會從它的父線程(即當前線程)中繼承各種屬性比如context classloader threadlocal以及其他的安全屬性(訪問權限) 而加載當前類的線程可能是不確定的比如一個線程池中隨機的一個線程如果你需要控制線程的屬性最好的做法就是將其初始化操作放在一個靜態方法中這樣初始化將由它的調用者來決定

      正確的做法

      


      class Cache {   
      private static Timer evictor;   
      public static setupEvictor() {   
      evictor = new Timer();   
      }   
      }  

      已取消的定時器任務依然持有狀態

      錯誤的寫法

      


      final MyClass callback = this;   
      TimerTask task = new TimerTask() {   
      public void run() {   
      callbacktimeout();   
      }   
      };   
      timerschedule(task L);   
      try {   
      doSomething();   
      } finally {   
      taskcancel();   
      }  

      上面的task內部包含一個對外部類實例的應用 這將導致該引用可能不會被GC立即回收 因為Timer將保留TimerTask在指定的時間之後才被釋放 因此task對應的外部類實例將在分鐘後被回收

      正確的寫法

      


      TimerTask task = new Job(this);   
      timerschedule(task L);   
      try {   
      doSomething();   
      } finally {   
      taskcancel();   
      }   
       
      static class Job extends TimerTask {   
      private MyClass callback;   
      public Job(MyClass callback) {   
      thiscallback = callback;   
      }   
      public boolean cancel() {   
      callback = null;   
      return supercancel();   
      }   
      public void run() {   
      if (callback == null) return;   
      callbacktimeout();   
      }   
      }  


    From:http://tw.wingwit.com/Article/program/Java/hx/201311/26536.html
      推薦文章
      Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.