昨天和其他项目的同事进行了关于基本类型和包装类型的一个比较问题的讨论,某项目组经理质疑比较代码不能正确进行比较。后来和那项目组的同事翻看源码确定了一下Integer Integer.valueOf(String)和int Integer.parseInt(String)内部解析过程,但从代码层面确实是看不到一个包装类型Integer和int比较的底层实现方法,就比较主观臆断的想jdk编译后的操作指令将会是int值转化相应的Integer对象,再比较引用。但事后始终觉得不看字节码指令始终不太确定,于是使用javap反编译了生成的抽取关键比较写法的TestIntegerCompare类的字节码,才发现自己的主观臆断是错误的。
方法valueOf实际是调用了方法parseInt,在没有指定基数的时候都是以十为基数。通过以下代码我们可以清楚的了解到Integer valueOf(String s)实际是调用Integer valueOf(int i),而Integer valueOf(int i)以int parseInt(s,radix))的返回值为参数进行调用;所以当int parseInt(s,radix))的返回值小于等于127、大于等于-128的Integer valueOf(String s)返回的Integer对象是从缓存中取出来的Integer的实例,否则返回一个新的Integer实例,而parseInt则是每次都是返回一个基本类型的int值,和缓存毫无关系。
而然某项目经理质疑的是一个包装对象和一个基本类型的值进行比较,究竟是比较值还是比较引用。而某项目经理则认为是比较引用,以下做法是错误的。但事实上是比较值,这里jdk编译后的是否对我们的对象和值的表的语句做了处理,最后都是比较基本类型的值?一下为讨论的代码,原出于hibernate的query通过unique方法取出一个Object值,实际上该值实际为Integer实例,所以使用一下类似测试代码先转换再比较的代码作为我们这次探索的问题代码。
测试比较代码:
public class TestIntegerCompare{ public static void main(String[] args) { Object obj = new Integer(1024); if(Integer.valueOf(obj.toString())==1024){ System.out.println("bing go! Integer.valueOf(obj.toString())==1024 is true"); } } }
jdk 1.6.34 java.lang.Integer.class源码:
public static Integer valueOf(String s, int radix) throws NumberFormatException { return Integer.valueOf(parseInt(s,radix)); } public static Integer valueOf(String s) throws NumberFormatException { return Integer.valueOf(parseInt(s, 10)); } public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } public static int parseInt(String s) throws NumberFormatException { return parseInt(s,10); } public static int parseInt(String s, int radix) throws NumberFormatException { //省略一系列参数检测 int result = 0; boolean negative = false; int i = 0, len = s.length(); int limit = -Integer.MAX_VALUE; int multmin; int digit; if (len > 0) { char firstChar = s.charAt(0); if (firstChar < '0') { // Possible leading "+" or "-" if (firstChar == '-') { negative = true; limit = Integer.MIN_VALUE; } else if (firstChar != '+') throw NumberFormatException.forInputString(s); if (len == 1) // Cannot have lone "+" or "-" throw NumberFormatException.forInputString(s); i++; } multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE digit = Character.digit(s.charAt(i++),radix); if (digit < 0) { throw NumberFormatException.forInputString(s); } if (result < multmin) { throw NumberFormatException.forInputString(s); } result *= radix; if (result < limit + digit) { throw NumberFormatException.forInputString(s); } result -= digit; } } else { throw NumberFormatException.forInputString(s); } return negative ? result : -result; }
让我们运行测试代码可以看到测试代码if里面的语句块是可以运行的且输出文字的,但自此我们仍对一个对象和一个基本类型的值进行比较的过程存在疑问。究竟是编译后jdk将int值转换成了Integer实例再与实例Integer实例进行比较引用,还是反之呢。这个疑问让javap帮我们看个究竟,输入javap -v -l Test TestIntegerCompare.class,可以看到以下反编译的jvm指令的呈现。
从上述jvm指令中可以看出来,code 的标号15:确实是调用了valueOf方法解析字符串为Integer实例,但其后标号18:有调用了Integer实例的intValue()方法返回了该实例相应的int值,从而最后比较的实际是int和int进行比较,所以比较结果是true;
通过上述分析可以确定 “包装对象==相应值” 这样的比较是可行的,但却不是推荐的。因为解析String实例为Integer实例,然后在去Integer实例里面去取的int值进行比较,在此Integer实例就多此一举了,还不如直接使用parseInt解析字符为相应的int值直接进行比较,节省创建一个Integer实例所浪费的资源。
ps:如上述解析有问题,还望各位资深专家前来指正。