編寫約玩原始碼不能忽視的細節,拒絕Bug找上門
約玩原始碼能夠穩定和高效的執行,對在市場中的發展至關重要。在約玩原始碼的日常編寫過沖中,存在著很多容易被忽略的細節,這些細節可能會導致程式出現各種Bug,下面就對這些細節進行一些總結,希望能夠對大家有幫助。
1 相等判斷中的==和equals
在約玩原始碼的很多場景中,我們都需要判斷兩個物件是否相等,一般來說,判定兩個物件的是否相等,都是依據其值是否相等,如兩個字串a和b的值都為"java",則我們認為二者相等。在Java中,有兩個操作可以判斷是否相當,即==和equals,但二者是有區別的,不可混用。下面給出示例:
String a = "java"; String b = new String("java"); System.out.println(a == b);//false System.out.println(a.equals(b));//true
字串a和b的字面值都為"java",用a==b判斷則輸出false,即不相等,而a.equals(b)則輸出true,即相等。這是為什麼呢?在約玩原始碼的Java中,String是一個不可變的型別,一般來說,如果兩個String的值相等,預設情況下,會指定同一個記憶體地址,但這裡字串String b用new String方法強制生成一個新的String物件,因此,二者記憶體地址不一致。由於 == 需要判斷物件的記憶體地址是否一致,因此返回false,而equals預設(override後可能不一定)是根據字面值來判斷,即相等。
下面再給出一個示例:
//integer -128 to 127 Integer i1 = 100; Integer i2 = 100; System.out.println(i1 == i2);//true i1 = 300; i2 = 300; System.out.println(i1 == i2);//false System.out.println(i1.equals(i2));//true
這是由於約玩原始碼的Java中的Integer數值的範圍為-128到127,因此在這範圍內的物件的記憶體地址是一致的,而超過這個範圍的數值物件的記憶體地址是不一致的,因此300這個數值在 == 比較下,返回false,但在equals比較下返回true。
2 switch語句中丟失了break
在約玩原始碼很多場景中,我們需要根據輸入引數的範圍來分別進行處理,這裡除了可以使用if … else …語句外,還可以使用switch語句。在switch語句中,會羅列出多個分支條件,並進行分別處理,但如果稍有不注意,就可能丟失關鍵字break語句,從而出現預期外的值。下面給出示例:
//缺少break關鍵字 public static void switchBugs(int v ) { switch (v) { case 0: System.out.println("0"); //break case 1: System.out.println("1"); break; case 2: System.out.println("2"); break; default: System.out.println("other"); } }
如果我們使用如下語句進行呼叫:
switchBugs(0);
則我們預期約玩原始碼返回"0",但是卻返回"0" “1”。這是由於case 0 分支下缺少break關鍵字,則雖然程式匹配了此分支,但是卻能穿透到下一個分支,即case 1分支,然後遇到break後返回值。
3 大量的垃圾回收,效率低下
約玩原始碼字串的拼接操作,是非常高頻的操作,但是如果涉及的拼接量很大,則如果直接用 + 符號進行字串拼接,則效率非常低下,程式執行的速度很慢。下面給出示例:
private static void stringWhile(){ //獲取開始時間 long start = System.currentTimeMillis(); String strV = ""; for (int i = 0; i < 100000; i++) { strV = strV + "$"; } //strings are immutable. So, on each iteration a new string is created. // To address this we should use a mutable StringBuilder: System.out.println(strV.length()); long end = System.currentTimeMillis(); //獲取結束時間 System.out.println("程式執行時間: "+(end-start)+"ms"); start = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 100000; i++) { sb.append("$"); } System.out.println(strV.length()); end = System.currentTimeMillis(); System.out.println("程式執行時間: "+(end-start)+"ms"); }
上述示例分別在迴圈體中用 + 和 StringBuilder進行字串拼接,並統計了執行的時間(毫秒),下面給出模擬電腦上的執行結果:
//+ 操作 100000 程式執行時間: 6078ms StringBuilder操作 100000 程式執行時間: 2ms
由此可見,使用StringBuilder構建約玩原始碼的字串速度相比於 + 拼接,效率上高出太多。究其原因,就是因為Java語言中的字串型別是不可變的,因此 + 操作後會建立一個新的字串,這樣會涉及到大量的物件建立工作,也涉及到垃圾回收機制的介入,因此非常耗時。
4 迴圈時刪除元素
在約玩原始碼的有些情況下,我們需要從一個集合物件中刪除掉特定的元素,如從一個程式語言列表中刪除java語言,則就會涉及到此種場景,但是如果處理不當,則會丟擲ConcurrentModificationException異常。下面給出示例:
private static void removeList() { List<String> lists = new ArrayList<>(); lists.add("java"); lists.add("csharp"); lists.add("fsharp"); for (String item : lists) { if (item.contains("java")) { lists.remove(item); } } }
執行上述方法,會丟擲錯誤,此時可以用如下方法進行解決,即用迭代器iterator,具體如下所示:
private static void removeListOk() { List<String> lists = new ArrayList<>(); lists.add("java"); lists.add("csharp"); lists.add("fsharp"); Iterator<String> hatIterator = lists.iterator(); while (hatIterator.hasNext()) { String item = hatIterator.next(); if (item.contains("java")) { hatIterator.remove(); } } System.out.println(lists);//[csharp, fsharp] }
5 null引用
在約玩原始碼的方法中,首先應該對引數的合法性進行驗證,第一需要驗證引數是否為null,然後再判斷引數是否是預期範圍的值。如果不首先進行null判斷,直接進行引數的比較或者方法的呼叫,則可能出現null引用的異常。下面給出示例:
private static void nullref(String words) { //NullPointerException if (words.equals("java")){ System.out.println("java"); }else{ System.out.println("not java"); } }
如果此時我們用如下方法進行呼叫,則丟擲異常:
nullref(null)
這是由於假設了words不為null,則可以呼叫String物件的equals方法。下面可以稍微進行一些修改,如下所示:
private static void nullref2(String words) { if ("java".equals(words)){ System.out.println("java"); }else{ System.out.println("not java"); } }
則此時執行則可以正確執行:
nullref2(null)
6 hashCode對equals的影響
前面提到,equals方法可以從字面值上來判斷兩個物件是否相等。一般來說,如果兩個物件相等,則其hash code相等,但是如果hash code相等,則兩個物件可能相等,也可能不相等。這是由於Object的equals方法和hashCode方法可以被Override。下面給出示例:
package com.jyd; import java.util.Objects; public class MySchool { private String name; MySchool(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MySchool _obj = (MySchool) o; return Objects.equals(name, _obj.name); } @Override public int hashCode() { int code = this.name.hashCode(); System.out.println(code); //return code; //true //隨機數 return (int) (Math.random() * 1000);//false } } Set<MySchool> mysets = new HashSet<>(); mysets.add(new MySchool("CUMT")); MySchool obj = new MySchool("CUMT"); System.out.println(mysets.contains(obj));
執行上述程式碼,由於hashCode方法被Override,每次返回隨機的hash Code值,則意味著兩個物件的hash code不一致,那麼equals判斷則返回false,雖然二者的字面值都為"CUMT"。
7 記憶體洩漏
我們知道,計算機的記憶體是有限的,如果在約玩原始碼中Java建立的物件一直不能進行釋放,則新建立的物件會不斷佔用剩餘的記憶體空間,最終導致記憶體空間不足,丟擲記憶體溢位的異常。記憶體異常基本的單元測試不容易發現,往往都是上線執行一定時間後才發現的。下面給出示例:
package com.jyd; import java.util.Properties; //記憶體洩漏模擬 public class MemoryLeakDemo { public final String key; public MemoryLeakDemo(String key) { this.key =key; } public static void main(String args[]) { try { Properties properties = System.getProperties(); for(;;) { properties.put(new MemoryLeakDemo("key"), "value"); } } catch(Exception e) { e.printStackTrace(); } } /* @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MemoryLeakDemo that = (MemoryLeakDemo) o; return Objects.equals(key, that.key); } @Override public int hashCode() { return Objects.hash(key); } */ }
此示例中,有一個for無限迴圈,它會一直建立一個物件,並新增到properties容器中,如果MemoryLeakDemo類未給出自己的equals方法和hashCode方法,那麼這個物件會被一直新增到properties容器中,最終記憶體洩漏。但是如果定義了自己的equals方法和hashCode方法(被註釋的部分),那麼新建立的MemoryLeakDemo例項,由於key值一致,則判定為已存在,則不會重複新增,此時則不會出現記憶體溢位。
最後,在約玩原始碼的編寫過程中需要注意的細節還有很多,比如不同精度的數值的賦值和轉換問題,會導致資料精度的丟失等,這裡不再贅述,希望以上內容能帶給大家一些幫助。
本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理
原文連結:https://bbs.huaweicloud.com/blogs/273066?utm_source=oschina&utm_medium=bbs-ex&utm_campaign=other&utm_content=content
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2844957/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 從應用開發到營收 10個不能忽視的細節營收
- 那些容易被忽視的 JavaScript 細節總結JavaScript
- 細節決定成敗!APP設計不容忽視的20個細節APP
- 不要忽視Web程式設計中的小細節Web程式設計
- 網頁設計中那些不容忽視的細節網頁
- 遊戲陪玩原始碼前端開發,不容忽視的五個要點遊戲原始碼前端
- Python 元組列表排序:初學者可能忽視的細節Python排序
- 細節決定成敗,不容忽視的10道Node面試題面試題
- ArrayList原始碼閱讀,拒絕人云亦云~原始碼
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- 為什麼我拒絕用Kotlin編寫安卓應用?Kotlin安卓
- 為提升約玩原始碼的系統效能,需要做好節流和防抖原始碼
- 約玩原始碼中執行緒的呈現狀態,死鎖程式碼如何寫?原始碼執行緒
- 為什麼說.gitignore不能忽視Git
- 解謎遊戲不可忽視的細節——《COCOON》的隱性引導設計遊戲
- 編譯FFMPEG原始碼的指令碼編寫案例編譯原始碼指令碼
- 忽視細節導致簡單問題的複雜化 關於PUPBLD.SQLSQL
- 新手不能忽視的MFC程式設計之CStringC程式程式設計
- CMO祕笈:不能忽視的6大品牌策略
- 容易忽視的細節:Log4j 配置導致的零點介面嚴重超時
- 杜絕安全隱患 容易忽視的Oracle安全問題(轉)Oracle
- NSACE|網路資訊保安技術,你不能忽視的存在
- 編寫可閱讀的程式碼--基本規約
- 程式被拒絕的原因
- 原始碼閱讀之ArrayList實現細節原始碼
- 特斯拉遭自動駕駛技術公司解約 拒絕背鍋自動駕駛
- 編寫智慧合約
- 你可能還不知道 golang 的高效編碼細節Golang
- win10 程式不能結束訪問被拒絕怎麼辦Win10
- 拒絕遊戲創業遊戲創業
- 原始碼閱讀之LinkedList實現細節原始碼
- Python入門細節Python
- 【J2EE】--讓你不能拒絕的J2EE13核心規範
- 記錄曾經拒絕的話
- HDFS 原始碼解讀:HadoopRPC 實現細節的探究原始碼HadoopRPC
- 拒絕蠻力,高效檢視Linux日誌檔案!Linux
- 拒絕使用 rm -rf 命令 ?
- scala入門之編寫scala指令碼指令碼