两个Long类型真的不能直接用>或<比较么?其实可以
2018-01-24 23:22
483 查看
当我在Google输入“Long类型的比较”时,会出现多如牛毛的与这个问题相关的博文,并且这些博文对此问题的看法如出一辙,都“不约而同”地持有如下观点:
对于Long类型的数据,它是一个对象,所以对象不可以直接通过“>”,“==”,“<”的比较。若要比较是否相等,可以用Long对象的equals方法;若要进行“>”,“<”的比较,需通过Long对象的longValue方法。
那么问题来了,这个观点真的全对吗?或者准确地说,后半段关于“>”,“<”的说法真的对吗?起初我也差点信了,按理说Java中并没有像C++中的操作符重载之类的东东,对象直接拿来用“>”或“<”比较确实很少这么干的,而且有童鞋可能会说,既然大家都这么说,当然是对的无疑咯。那么今天笔者想告诉你的是,它是错的!Long类型可以直接用“>”和“<”比较,并且其他包装类型也同理。不信?先别急着反驳,且听笔者娓娓道来。
然后oschina上的一个热心网友关于此提出了一个很好的问题:
即有没有可能比较的是内存地址并且刚好其大小满足上述条件?想想也不无道理,毕竟对于Java中的对象引用a、b、c的值实际就是对象在堆中的地址。关于这个问题,其实我最初也质疑过,为此我编写了多种类似上面的testCase,比如:
最终的结论跟预期一致的:两者的比较结果跟Long对象中的数值大小的比较结果是一致的,至少从目前所尝试过的所有testCase来看是这样的。
于是我开始琢磨:毕竟对于
接下来以实例来演示我们的推断过程。首先上代码:
我们以debug模式运行上述testCase,首先运行到断点1处,此处可观察到
此时我们通过
然后F8到断点2,观察此时
最后的输出结果如下:
由此说明,两个Long对象直接用“>”或“<”比较时,是数值比较而非地址比较。
好了,上面的debug测试已经能解释我们的困惑,但是笔者认为这还不够!仅仅停留在表面不是我们程序猿的作风,我们要从本质——源码出发。原理是什么?为什么最终比较的是数值而不是引用?难道这也发生了自动拆箱吗?(跟我们以前所认知的自动拆箱有出入哦)
第59行(这里的“行”是一种形象的描述,实指当前字节码相对于方法体开始位置的偏移量)是我们打印结果的地方:
从字节码可以清晰地看到第23、27行以及第41、45行,invokevirtual,显式调用了
除了Long类型,感兴趣的童鞋还可以找Integer、Byte、Short等来验证下,结果是一样的,这里我就不做过多叙述了。
虽然今天谈论的只是Long对象的”>”或”<”用法问题,看起来好像是个“小问题”,最坏情况下,如果不确定是否可以直接比较,大不了直接用Long.longValue来比较,并不会阻碍你编码。但是,笔者想说但是,作为一个程序猿,打破砂锅问到底的精神是不可少的,我们应该拒绝黑盒,追求细节,这样才可能更好地成长,在代码的世界里游刃有余。
同步更新到原文。
对于Long类型的数据,它是一个对象,所以对象不可以直接通过“>”,“==”,“<”的比较。若要比较是否相等,可以用Long对象的equals方法;若要进行“>”,“<”的比较,需通过Long对象的longValue方法。
那么问题来了,这个观点真的全对吗?或者准确地说,后半段关于“>”,“<”的说法真的对吗?起初我也差点信了,按理说Java中并没有像C++中的操作符重载之类的东东,对象直接拿来用“>”或“<”比较确实很少这么干的,而且有童鞋可能会说,既然大家都这么说,当然是对的无疑咯。那么今天笔者想告诉你的是,它是错的!Long类型可以直接用“>”和“<”比较,并且其他包装类型也同理。不信?先别急着反驳,且听笔者娓娓道来。
问题起源
关于Long类型的大小比较这个问题,其实是源于我的上一篇博文谈谈ali与Google的Java开发规范,在其中关于“相同类型的包装类对象之间值的比较”这一规范,我补充了如下一点:然后oschina上的一个热心网友关于此提出了一个很好的问题:
即有没有可能比较的是内存地址并且刚好其大小满足上述条件?想想也不无道理,毕竟对于Java中的对象引用a、b、c的值实际就是对象在堆中的地址。关于这个问题,其实我最初也质疑过,为此我编写了多种类似上面的testCase,比如:
Long a = new Long(1000L); Long b = new Long(2000L); Long c = new Long(222L); Assert.isTrue(a<b && a>c); //断言成功
最终的结论跟预期一致的:两者的比较结果跟Long对象中的数值大小的比较结果是一致的,至少从目前所尝试过的所有testCase来看是这样的。
从现象到本质
但是,光靠那几个有限的单元测试,貌似并不具有较强的说服力,心中难免总有疑惑:会不会有特殊的case没覆盖到?会不会还是地址比较的巧合?怎么才能有效地验证我的结论呢?于是我开始琢磨:毕竟对于
new Long()这种操作,是在堆中动态分配内存的,我们不太好控制a、b等的地址大小,那又该怎么验证上述的比较不是地址比较的结果呢?除了地址之外,还有别的我们能控制的吗?有的,那就是对象中的内容!我们可以在不改变对象引用值的情况下,改变对象的内容,然后看其比较结果是否发生变化,这对于我们来说轻而易举。有时候换个角度思考问题,就能有新的收获!
一、debug验证
那么接下来,我们就可以用反证法来证明上述问题,还是以本文开头的testCase为例:假设上述testCase中比较的是地址值,只要我们不对a、b进行赋值操作,即不改变它们的地址值,其比较结果就应该也是始终不变,此时我们仅修改对象中的数值,这里对应Long对象中的value字段,使数值的大小比较与当前Long对象的比较结果相反,如果此时Long对象的比较结果也跟着变为相反,也就推翻了地址比较这一假设,否则就是地址比较,证毕。接下来以实例来演示我们的推断过程。首先上代码:
/** * @author sherlockyb * @2018年1月14日 */ public class JdkTest { @Test public void longCompare() { Long a = new Long(1000L); Long b = new Long(222L); boolean flagBeforeAlter = a > b; boolean flagAfterAlter = a > b; // 断点1 System.out.println("flagBeforeAlter: " + flagBeforeAlter + ", flagAfterAlter: " + flagAfterAlter); // 断点2 } }
我们以debug模式运行上述testCase,首先运行到断点1处,此处可观察到
flagBeforeAlter的当前值为true:
此时我们通过
Change Value修改a中的value值为100L,如图:
然后F8到断点2,观察此时
flagAfterAlter的值为false:
最后的输出结果如下:
flagBeforeAlter: true, flagAfterAlter: false
由此说明,两个Long对象直接用“>”或“<”比较时,是数值比较而非地址比较。
好了,上面的debug测试已经能解释我们的困惑,但是笔者认为这还不够!仅仅停留在表面不是我们程序猿的作风,我们要从本质——源码出发。原理是什么?为什么最终比较的是数值而不是引用?难道这也发生了自动拆箱吗?(跟我们以前所认知的自动拆箱有出入哦)
二、回归本质——字节码
真理来自源码。我们通过javap -c来看下刚才那个JdkTest类,反编译字节码是啥:
// Compiled from "JdkTest.java" public class org.sherlockyb.blogdemos.jdk.JdkTest { public org.sherlockyb.blogdemos.jdk.JdkTest(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public void longCompare(); Code: 0: new #17 // class java/lang/Long 3: dup 4: ldc2_w #19 // long 1000l 7: invokespecial #21 // Method java/lang/Long."<init>":(J)V 10: astore_1 11: new #17 // class java/lang/Long 14: dup 15: ldc2_w #24 // long 222l 18: invokespecial #21 // Method java/lang/Long."<init>":(J)V 21: astore_2 22: aload_1 23: invokevirtual #26 // Method java/lang/Long.longValue:()J 26: aload_2 27: invokevirtual #26 // Method java/lang/Long.longValue:()J 30: lcmp 31: ifle 38 34: iconst_1 35: goto 39 38: iconst_0 39: istore_3 40: aload_1 41: invokevirtual #26 // Method java/lang/Long.longValue:()J 44: aload_2 45: invokevirtual #26 // Method java/lang/Long.longValue:()J 48: lcmp 49: ifle 56 52: iconst_1 53: goto 57 56: iconst_0 57: istore 4 59: getstatic #30 // Field java/lang/System.out:Ljava/io/PrintStream; 62: new #36 // class java/lang/StringBuilder 65: dup 66: ldc #38 // String flagBeforeAlter: 68: invokespecial #40 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 71: iload_3 72: invokevirtual #43 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 75: ldc #47 // String , flagAfterAlter: 77: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 80: iload 4 82: invokevirtual #43 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 85: invokevirtual #52 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 88: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 91: return }
第59行(这里的“行”是一种形象的描述,实指当前字节码相对于方法体开始位置的偏移量)是我们打印结果的地方:
System.out.println(...)
从字节码可以清晰地看到第23、27行以及第41、45行,invokevirtual,显式调用了
java/lang/Long.longValue:()方法,确实自动拆箱了。也就是说对于基本包装类型,除了我们之前所认知的自动装箱和拆箱场景(关于自动装箱和拆箱,大家可以参考这篇博文——Java中的自动装箱与拆箱,写的不错,这里我就不做过多叙述了)外,对于两个包装类型的>和<的操作,也会自动拆箱。无需任何testCase来佐证,结论一目了然。
除了Long类型,感兴趣的童鞋还可以找Integer、Byte、Short等来验证下,结果是一样的,这里我就不做过多叙述了。
总结
古人说得好——尽信书,则不如无书。可能,大多数的我们在面对这个问题时,都会下意识地去Google一把,然后多家博客对比查阅,最后发现几乎所有的博文都是一致的观点:Long对象不可直接用”>”或”<”比较,需要调用Long.longValue()来比较。于是毫无疑问地就信了。当再次遇到这个问题时,就会“很自信”地告诉别人,要用
Long.longValue()比较。而实际呢,却不知道自己已经陷入误区!
虽然今天谈论的只是Long对象的”>”或”<”用法问题,看起来好像是个“小问题”,最坏情况下,如果不确定是否可以直接比较,大不了直接用Long.longValue来比较,并不会阻碍你编码。但是,笔者想说但是,作为一个程序猿,打破砂锅问到底的精神是不可少的,我们应该拒绝黑盒,追求细节,这样才可能更好地成长,在代码的世界里游刃有余。
同步更新到原文。
相关文章推荐
- 两个Long类型真的不能直接用>或<比较么?其实可以
- 两个 Long 类型为什么不能直接用==比较
- 两个 Long 类型为什么不能直接用==比较
- <c:if>标签中的test可以比较两个el表达式中的值
- C中不能直接比较两个double类型
- List<?>不能直接被类型转换
- double由于是双精度,所以比较大小不能直接用>,<或者=,需要使用BigDecimal,具体看例子
- Long类型比较不能直接用等于
- Long类型比较不能直接用等于
- java Long、Integer 、Double、Boolean类型 不能直接比较
- 今天做一个winform,想直接把窗体改成输出类库,其他地方直接调结果总提示不能注册组件,回来调度,可以,总结,windows还是直接用新建的类型项目,改容易出错
- POJ 3162 Walking Race 树的直径+单调队列(其实暴力也可以>_<)
- 编写一个程序,将两个字符串s1和s2比较,如果s1 > s2,输出一个正数;s1 = s2,输出0,;s1 < s2输出一个负数。不要使用strcmp函数。
- pig 的chararry不能用于比较的类型可以comparison operator
- C++ 不能通过‘...’传递有不能平凡复制的类型‘const string {aka const class std::basic_string<char>}’
- 探讨float类型的数值,为什么两个float不能直接相等
- 比较两个数的大小,要求不能用if,<,>,?
- 泛型系列<6>:创建一个可以被初始化为空的值类型
- 两个Long类型怎么比较大小
- 在jsp中用<s:if test />比较两个变量