数字运算那些事
本文介绍一些java数字运算的问题,通过阅读,希望你能清晰的了解java的数字运算。
假设你遇到了下面的笔试题
假设你遇到了下面的笔试题,你会给出什么答案。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class TestInteger {
public static void main (String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c == (a+b));
System.out.println(c.equals(a+b));
System.out.println(g == (a+b));
System.out.println(g.equals(a+b));
}
}
要得到正确结果,不了解java文件的编译和字节码技术,是不可能彻底明白的。
编译
1 | // |
很容易拿到class文件的反编译结果。
可以看到,定义的所有变量 都被转化为封装类。
下面两条语句没有发生变化:
System.out.println(c == d);
System.out.println(e == f);
从下面这条语句可以看出,在进行数字的基本运算时,数字被还原为基本类型。
System.out.println(c.intValue() == a.intValue() + b.intValue());
从下面这条语句可以看出,基本类型作为equals参数时,会自动转换为包装类。
System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
从下面这条语句可以看出,当==两边的数字类型不一致时,占有字节较小的一侧会发生类型强转。
System.out.println(g.longValue() == (long)(a.intValue() + b.intValue()));
Ok,原来不太明白的话,读到这里你应该能给出这样的运行结果了吧?
true
false
true
true
true
false
大致说明一下为什么是这样的结果。
第1,2个输出的原因:
看一下Integer类的源码中这段代码你就明白了。1
2
3
4
5public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
第3,4,5,你可能认为理应如此。
第6个输出为false,可以看下java.lang.Long#equals
如果你觉得你已经彻底明白了,你就错了,输出结果是这样的,还有一部分原因是class文件中的字节码技术。
字节码
下面两条语句,虽然结果都是ture,但是,他们生成字节码中进行比较的指令也是不同的。
System.out.println(c == d);
System.out.println(3 == 3);
我们把之前class文件的字节码拿出来几段分析一下。
下面的字节码应以了一个常量,调用了Method java/lang/Integer.valueOf获取常量的值。1
2
30: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
下面的字节码取出c和d两个变量,使用if_acmpne指令进行对比,这个命令比较的是两个对象的引用地址。1
2
348: aload_3
49: aload 4
51: if_acmpne 58
下面的字节码取出c,a,b三个变量,a和b使用iadd命令相加,然后使用了if_icmpne命令比较两个变量的数值。1
2
3
4
5
6
7
883: aload_3
84: invokevirtual #8 // Method java/lang/Integer.intValue:()I
87: aload_1
88: invokevirtual #8 // Method java/lang/Integer.intValue:()I
91: aload_2
92: invokevirtual #8 // Method java/lang/Integer.intValue:()I
95: iadd
96: if_icmpne 103
总结
如果想彻底了解java的数字运算,必须要知道编译后生成的class文件,甚至去查看字节码。
如果不想知其所以然,也可以通过下面的方式记忆。
- 如果你定义的是基本类型,编译器会根据你定义数字的具体大小,来描述你的变量。
比如你定义了int a = 1;但是在程序中没有使用,那编译后生成的字节码中可能是如下的结果:
boolean a = true;
如果你使用了,可能又是下面的结果:
byte h = 1; - 如果你定义的是包装类,而且是把基本类型赋值给包装类,那编译器会调用包装类的valueOf方法。
- 封装类进行基本运算都会转化成基本类型。
- ==两端,如果有一侧进行了数字运算,那么它使用if_icmpne指令来比较是否相等,也就是说比较数值大小。
- ==两端,如果两侧都没有进行数字运算,那么它使用if_acmpne指令来比较是否相等,也就是说比较的是引用地址。
大概就是这样了,开发jdk的大牛们,为了jvm更高效,让我们现在会遇到各种各样奇怪的问题。
所以,我们在比较数字是否相等时,最好这样做:
- 将数字转化为基本类型,使用==进行比较。
- 将数字都转化成同样类型的包装类,使用equals进行比较。
PS:上面的面试题其实意义不大,会的人也可能在工作中写出很差的代码,不会的人也不一定写不出好的代码。大部分面试官看不出一个人的能力,只能量化标准。正所谓千里马常有,伯乐不常有。