記錄日常工作中一些容易被忽視的錯誤及細節,持續更新......
一、問題:HashMap<Long, String>中,用get(Integer key)取不到值
Map<Long, String> map = new HashMap<Long, String>();map.put(1L, "1");System.err.println(map.get(1));// nullSystem.err.println(map.get(1L));// 1
1.首先想到Long與Integer的hashCode方法不同,Integer-value? ?Long-(int)(value ^ (value >>> 32))
但是!!計算出的hashCode值是相同的,不是問題所在
2.查看HashMap源碼:注意加亮部分
先比較key.hash,然后first.key == key || key.equals(first.key)
/*** 先比較key.hash,然后first.key == key || key.equals(first.key)*/final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))return first;if ((e = first.next) != null) {if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;}
?先看first.key == key:"=="比較地址值,l是Long cache[]中的1,o是Integer cache[]中的1,false
Long l = 1L;Object o = 1;System.err.println(l == o);// false// 反編譯后:Long l = Long.valueOf(1L);Object o = Integer.valueOf(1);System.err.println(l == o);
然后看key.equals(first.key):先檢查類型,false
//Long的equals方法public boolean equals(Object obj) {if (obj instanceof Long) {return value == ((Long)obj).longValue();}return false;}
引發新的問題:為什么這個是true?——反編譯解決
Long l = 1L;System.err.println(l == 1);// true// 反編譯后:Long l = Long.valueOf(1L);System.err.println(l.longValue() == 1L);//編譯器直接將1轉成1L
?二、兩個值相等的Integer不“==”
Integer c = 99999;Integer d = 99999;System.out.println(c == d);// false
?Integer c = 99999;// 反編譯:Integer c = Integer.valueOf(99999);
查看Integer源碼:
-128 <= i <= 127時,直接在Integer cache[]中取;否則,new Integer(i)
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
結論:
int a = 99999;int b = 99999;System.err.println(a == b);// true Integer c = 99999;Integer d = 99999;System.out.println(c == d);// false Integer e = 127;Integer f = 127;System.out.println(e == f);// true
?三、List.remove()方法調用錯誤
注意list兩個remove方法,remove(int index)? remove(Object o)
public static void main(String[] args) {List<Integer> list = new LinkedList<Integer>();for (int i = 0; i < 9999999; i++) {list.add(i);}// remove(int index)long before = System.currentTimeMillis();int i = 8888888;list.remove(i);long after = System.currentTimeMillis();System.err.println("index=" + (after - before));// 6ms// remove(Object o)long before = System.currentTimeMillis();Integer i = 8888888;list.remove(i);long after = System.currentTimeMillis();System.err.println("Object=" + (after - before));// 96ms }
四、三目運算符與自動拆裝箱
Map<String,Boolean> map = new HashMap<String, Boolean>();Boolean b = (map!=null ? map.get("test") : false);// Exception in thread "main" java.lang.NullPointerException
查問題:
NullPointerException找不出原因
反編譯看:?((Boolean)map.get("test")) == null
HashMap map = new HashMap();Boolean boolean1 = Boolean.valueOf(map == null ? false : ((Boolean)map.get("test")).booleanValue());
結論:
三目運算符的語法規范,參見?jls-15.25。
三目運算符?當第二,第三位操作數分別為基本類型和對象時,其中的對象就會拆箱為基本類型進行操作。
以后注意:
1、保證三目運算符的第二第三位操作數都為對象類型
Map<String,Boolean> map = new HashMap<String, Boolean>();Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);
?2、自動拆裝箱問題
Integer integer = 1; // 裝箱 Integer integer=Integer.valueOf(1); new Integer()int i = integer; // 拆箱 int i=integer.intValue();
?
1)包裝對象的數值比較,不能簡單的使用==(只有-128到127之間IntegerCache內的數字可以,但是這個范圍之外還是需要使用equals
比較)。
2)自動拆箱,如果包裝類對象為null,那么自動拆箱時就有可能拋出NPE。
3)如果一個for循環中有大量裝箱操作,會浪費很多資源。
五、switch語句忘記break
本來我跟你現在想的一樣,一定不會忘,直到遇到了這個問題。
int i = 3;switch (i) {case 1:System.out.println(1);break;case 2:System.out.println(2);break;case 3:System.out.println(3);// 沒有break, 不會有問題}
當你需要在之后接著case的時候,直接復制case 3,就bug了。
(1)case完一定break,除非特別需要穿透到下一個case;
(2)復制代碼前后都要檢查是否有問題。
六、數值溢出問題
// 為了更好的展示問題,代碼舉出的是較極端的情況public void overFlow(int a) {int b = 999999 * a; // 6個9 int最大值=2147483647int limit = 999999999; // 9個9if (b < limit) {System.out.println("a*b小于limit");}}
如果a傳入一個較大的int值,a*999999之后會超過int的最大值
而默認結果是int類型,會將a*999999的結果強轉成int,導致不是想要的結果的結果
即使a*999999是大于limit的,強轉成int后,b可能會比limit小,甚至可能是負數
解決:
// 用long類型計算(還會用一定風險)public void overFlow(int a) {long b = 999999L * a;int limit = 999999999;if (b < limit) {System.out.println("a*999999小于limit");}}// 將加法和乘法轉變成減法和除法運算public void overFlow(int a) {int limit = 999999999;if (a < limit/999999) {System.out.println("a*999999小于limit");}}
七、對象引用問題
public static void main(String[] args) {Map<Integer, Inner> map = new HashMap<Integer, Inner>();// inner1.list [1]Inner inner1 = new Inner(new LinkedList<Integer>());inner1.getList().add(1);map.put(1, inner1);// inner2.list [1, 2]Inner inner2 = new Inner(map.get(1).getList());inner2.getList().add(2);map.put(2, inner2);for (Entry<Integer, Inner> entry : map.entrySet()) {System.err.println("" + entry.getKey() + " " + entry.getValue().getList());}/*** 目的:inner1.list [1] inner2.list [1, 2]* 結果:inner1.list [1, 2] inner2.list [1, 2]*/}static class Inner {List<Integer> list;public List<Integer> getList() {return list;}public Inner(List<Integer> list) {this.list = list;}}
?
分析:
1.將inner1.list的引用給了inner2.list,nner1.list?inner2.list指向的是同一個List。
2.很簡單的問題,開發時習慣了構造方法里這樣寫:this.list = list;
解決:
this.list = list;? 改成? ?this.list = new LinkedList<Integer>(list);
?