欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Java基础】Integer的自动拆箱和享元模式,这次我学到了

程序员文章站 2022-04-03 18:02:06
...

前言

前段时间线上报了一个空指针异常,后来多方排查发现是Integer自动拆箱的问题。所以少侠又把Integer源码及一些比较底层的知识学习了一遍,这里记录一下,也分享给大家。

自动拆箱

先看下出问题的代码,业务逻辑我给省去了。要做的事情很简单,就是从表里查询某用户购买商品一共支付的价格。注意,用户可能不存在,即通过SQL的SUM函数可能为null

Mapper层代码:

/**
 * @author Carson
 * @date 20-9-16 下午7:27
 */
@Mapper
public interface UserOrderMapper {

    String TABLE_NAME = "cps_user_order_detail d";

    @Select("SELECT SUM(price) FROM " + TABLE_NAME + " WHERE d.user_id=#{userId}")
    Integer calPrice(@Param("userId") String userId);
}

业务层代码:

 public Integer calPrice(String userId) {
        int price = userOrderMapper.calPrice(userId);
        Double rate = 0.5;
        return rate * price;
    }

少侠先不说问题,各位在看到这段代码会想到可能的隐患吗?

手动分隔符 +++++++++++++++++++++++++++++++++++++++++

好了,不卖关子了,上述代码极可能出现空指针异常。
事实上线上日志报错就是在业务层代码的int price = userOrderMapper.calPrice(userId);这一行。报了NullPointerException,当时我的第一感觉是难道userOrderMapper没有注入进来?但是业务层该Mapper的其它方法都正常运行,所以立即排查这个可能。但是SQL里面会出错么,即使参数为null,SQL也不会报NPE的。
苦思无果,于是把SQL拿出来手动执行了一遍,发现返回值是null,于是立即想到肯定是这个null的返回值调用了什么方法,但是再一看业务层代码,没有调用直接返回了啊。但是再一看返回值,呦吼,是int,想到可能是自动拆箱(即Integer向int类型转换)的问题。具体是啥问题呢。
看官莫急,且来看一下这段代码:

public class Main {
    public static void main(String[] args) {
        Integer input = null;
        int result = input;
        System.out.println(result);
    }
}

运行完之后进入classes文件夹下执行javap -v Main.class,查看反编译执行结果,这个就很明了了,如下所示,实际上Integer类型的参数用int接收是通过隐式的调用Integer.intValue()实现的。这也解释了上面那段业务代码会出现异常。
【Java基础】Integer的自动拆箱和享元模式,这次我学到了

享元模式

还是先来看一段代码。相信参加过在线笔试的小伙伴很多人都做过下面这道题:

public class Main {
    public static void main(String[] args) {
        Integer a1 = new Integer(66);
        Integer b1 = new Integer(66);
        System.out.println(a1 == b1);
        Integer a2 = 128;
        Integer b2 = 128;
        System.out.println(a2 == b2);
        Integer a3 = 127;
        Integer b3 = 127;
        System.out.println(a3 == b3);
    }
}

a1==b1等于false相信大家都没疑问。a2==b2你会想到,声明了两个变量,在内存中开辟了两个地址空间,答案也是false,也没问题。
然后再一看,什么鬼?刚刚是128,a3和b3变成127,同样的问题问我两遍?这出题人水平不行啊!小伙子,这样想说明你还年轻啊。是的,a3==b3答案是true。这是为什么呢?
事实上这是Java源码级的优化,Integer为了提高内存的利用率,内部设置了一个缓冲区,附上源码:

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

这是什么意思呢,就是在[-128,127]的区间内的整数,在内存中始终只会有一份(当然你通过new创建新对象的方式不算)。并且这个就是大名鼎鼎的享元模式。下次再有面试官问你JDK应用了哪些设计模式,把Integer这里的实现甩给他!

小结

一个小小的Integer就含有很有讲究,虽然读代码、找问题的过程比较苦涩,但是掌握之后还是会觉得充实。做开发最需要的就是技术提升,而阅读源码就是一个绝佳的方式!
我是少侠露飞,爱技术,爱分享。

点点关注,不会迷路