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

前言

前段时间线上报了一个空指针异常,后来多方排查发现是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()实现的。这也解释了上面那段业务代码会出现异常。
在这里插入图片描述

享元模式

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

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

点点关注,不会迷路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值