java的Integer比较==你真的懂吗?

导致我去看Integer源码的原因是项目中的一个问题,业务逻辑:项目中有一个扣除优惠券的操作,为了使用户优惠券使用正确,在扣除优惠券之前,会先比较一下优惠券的使用数量(总量-余量)和优惠券的使用明细表中的数量是否一致,如果一致则扣除优惠券,否则扣除优惠券失败(使用异常了)。

最后出现了一个问题:用户操作一定时间后发现,扣除失败,前面都是成功的。

项目中大概的逻辑是下面这样的:


// 这里判断优惠券使用情况
// 1、先从数据库中查出来优惠券使用明细中的数量
Integer useCouponCount = couponDao.getUseCouponCount(memberId, couponId);

// 2、从数据库中查出来优惠券的详情(其中包含优惠券的总量和剩余数量)
CounponInfo couponInfo = couponDao.getCouponById(couponId);

// 3、计算正常优惠券的使用数量
Integer payCount = couponInfo.getPayCount();// 总量
Integer residueCount = couponInfo.getResidueCount();// 剩余数量

Integer useCount = payCount - residueCount;

// 4、比较使用明细和使用数量是否相等
if (useCouponCount != useCount) {
    // 这里说明优惠券使用情况异常,抛出异常
} else {
    // 这里优惠券使用正常,进行相应的业务逻辑操作
}

1、首先查看用户优惠券使用情况并没有发生异常情况,数量是对着呢,所以转而考虑应该是代码的问题

2、根据日志的跟踪发现,最终发现问题出现在上面的第4步中,但是大致看了下没什么问题啊,为什么会出现使用异常的情况呢?

useCouponCount 和 useCount 为什么不相等?

最后发现这两个对象是Integer对象,Java中对象的== 和 !=操作是比较的对象的地址,所以导致了两个对象不相等,才造成了上面bug的出现,问题发现之后,再一想为什么前面的使用没有出现异常呢,所以开启研究这个问题。

1、先做了几个简单的验证:

 // 验证1
 Integer a = new Integer(10);

 Integer b = new Integer(10);

 System.out.println(a == b);// 验证结果:false(在意料之中,两个对象两个地址肯定不相同)

2、因为Integer对应的有基本类型int,所以又做了如下验证:

Integer a = 10;

Integer b = 10;

System.out.println(a == b);// 结果:true(哇,why?)

感觉发现了新大陆,有木有?

3、接着做验证:

Integer a = 127;

Integer b = 127;

System.out.println(a == b);// 结果为:true

Integer a = 128;

Integer b = 128;

System.out.println(a == b);// 结果为:false

终于发现问题了,大致猜想了下估计是因为Java的拆装箱导致的问题。Java的装箱用的是Integer.valueOf(int)方法,拆箱用的是intValue()方法,所以我们看下装箱帮我们做了什么操作。

    // valueOf中比较了参数i是否在Integer维护的一个缓存数组IntegerCache的范围内
    // 如果再IntegerCache.low 和 IntegerCache.high之间就返回缓存中的对象
    // 不在缓存范围内的话,才返回了一个新的Integer对象(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);
    }

我们可以看到装箱操作对应的IntegerCache(也就是-128~127) 帮我们new了256Integer对象,所以才会导致了我们的优惠券使用127之内是没有问题的,超过127就不行了

至于如何查看Integer是如何拆装箱的,其实是编译器帮我们做了些什么事,我们使用javap可以将class文件翻译成汇编内容查看一下class文件中执行的指令信息。附上一个我看Integer拆装箱的class

java源码:

public class Test{
	public static void main(String[] args) {
		Integer a = 10;

		int b = a;
		
	}
}

1、利用javac Test.java编译成class文件

2、利用javap -v -l -c -s -sysinfo -constants Test输出class对应的汇编格式

C:\Users\Administrator\Desktop>javap -v -l -c -s -sysinfo -constants Test
Classfile /C:/Users/Administrator/Desktop/Test.class
  Last modified 2019-6-5; size 367 bytes
  MD5 checksum 40139609a08238441e8ca2e01940f968
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Methodref          #15.#16        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Methodref          #15.#17        // java/lang/Integer.intValue:()I
   #4 = Class              #18            // Test
   #5 = Class              #19            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Test.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #20            // java/lang/Integer
  #16 = NameAndType        #21:#22        // valueOf:(I)Ljava/lang/Integer;
  #17 = NameAndType        #23:#24        // intValue:()I
  #18 = Utf8               Test
  #19 = Utf8               java/lang/Object
  #20 = Utf8               java/lang/Integer
  #21 = Utf8               valueOf
  #22 = Utf8               (I)Ljava/lang/Integer;
  #23 = Utf8               intValue
  #24 = Utf8               ()I
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: bipush        10

         // 这行就是帮我们做的装箱操作(执行的是valueOf(int)方法)
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: astore_1
         6: aload_1

          // 这行是帮我们做的拆箱操作(执行的是intValue()方法)
         7: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        10: istore_2
        11: return
      LineNumberTable:
        line 3: 0
        line 5: 6
        line 7: 11
}
SourceFile: "Test.java"

之前只知道有装箱和拆箱的说法,但是并不知道有什么用,现在有点懵懂了。

希望这篇对大家理解Java有所帮助,有不完善的地方,希望指出。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值