Java日常错误及需要注意细节,持续更新......

记录日常工作中一些容易被忽视的错误及细节,持续更新......

一、问题:HashMap<Long, String>中,用get(Integer key)取不到值

        Map<Long, String> map = new HashMap<Long, String>();
        map.put(1L, "1");
        System.err.println(map.get(1));// null
        System.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最大值=2147483647
        int limit = 999999999; // 9个9
        if (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);

 

posted @ 2018-10-11 15:08  那股泥石流  阅读(925)  评论(0编辑  收藏  举报