关于Java中你所不知道的Integer详解
前言 本文主要给大家介绍了关于Java中Integer的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。 实参形参 前些天看到朋友圈分享了一片文章《Java函数的传参机制――你真的了解吗?》 有些触发,之前也研究过Java的Integer,所以写下本文,希望对你有所帮助。 交换 首先来看一个示例。 请用Java完成swap函数,交换两个整数类型的值。 public static void test() throws Exception { Integer a = 1,b = 2; swap(a,b); System.out.println("a=" + a + ",b=" + b); } static void swap(Integer a,Integer b){ // 需要实现的部分 } 第一次 如果你不了解Java对象在内存中的分配方式,以及方法传递参数的形式,你有可能会写出以下代码。 public static void swapOne(Integer a,Integer b) throws Exception { Integer aTempValue = a; a = b; b = aTempValue; } 运行的结果显示a和b两个值并没有交换。 那么让我们来看一下上述程序运行时,Java对象在内存中的分配方式: 对象地址分配 由此可以看到,在两个方法的局部变量表中分别持有的是对a、b两个对象实际数据地址的引用。 上面实现的swap函数,仅仅交换了swap函数里局部变量a和局部变量b的引用,并没有交换JVM堆中的实际数据。 所以main函数中的a、b引用的数据没有发生交换,所以main函数中局部变量的a、b并不会发生变化。 那么要交换main函数中的数据要如何操作呢? 第二次 根据上面的实践,可以考虑交换a和b在JVM堆上的数据值? 简单了解一下Integer这个对象,它里面只有一个对象级int类型的value用以表示该对象的值。 所以我们使用反射来修改该值,代码如下: public static void swapTwo(Integer a1,Integer b1) throws Exception { Field valueField = Integer.class.getDeclaredField("value"); valueField.setAccessible(true); int tempAValue = valueField.getInt(a1); valueField.setInt(a1,b1.intValue()); valueField.setInt(b1,tempAValue); } 运行结果,符合预期。 惊喜 上面的程序运行成后,如果我在声明一个 示例程序如下: public static void swapTwo(Integer a1,tempAValue); } public static void testThree() throws Exception { Integer a = 1,b = 2; swapTwo(a,b); System.out.println("a=" + a + "; b=" + b); Integer c = 1,d = 2; System.out.println("c=" + c + "; d=" + d); } 输出的结果如下: a=2; b=1 c=2; d=1 惊喜不惊喜!意外不意外!刺激不刺激! 深入 究竟发生了什么?让我们来看一下反编译后的代码: 作者使用IDE工具,直接反编译了这个.class文件 public static void testThree() throws Exception { Integer a = Integer.valueOf(1); Integer b = Integer.valueOf(2); swapTwo(a,b); System.out.println("a=" + a + "; b=" + b); Integer c = Integer.valueOf(1); Integer d = Integer.valueOf(2); System.out.println("c=" + c + "; d=" + d); } 在Java对原始类型int自动装箱到Integer类型的过程中使用了 肯定是这个方法在内部封装了一些操作,使得我们修改了 所有这涉及该部分的代码一次性粘完(PS:不拖拉的作者是个好码农): public class Integer{ /** * @since 1.5 */ public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 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 = sun.misc.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() {} } } 如上所示Integer内部有一个私有静态类IntegerCache,该类静态初始化了一个包含了 其中 在该区间内所有的 所以当我们修改了 我相信你们的智商应该理解了,如果不理解请在评论区call 10086。 好了,那么不在 很显然,它们是幸运的,没有被IntegerCache缓存到,法外之民,每次它们的到来,都会new一边,在JVM上分配一块土(内)地(存)。 遐想 如果我把转换的参数换成类型换成int呢? public static void testOne() throws Exception { int a = 1,b = 2; swapOne(a,b=" + b); } static void swapOne(int a,int b){ // 需要实现的部分 } 以作者目前的功力,无解。高手可以公众号留言,万分感谢! 至此swap部分已经讲完了。 1 + 1 首先让我们来看一下代码: public static void testOne() { int one = 1; int two = one + one; System.out.printf("Two=%d",two); } 请问输出是什么? 如果你肯定的说是2,那么你上面是白学了,请直接拨打95169。 我可以肯定的告诉你,它可以是 惊喜不惊喜!意外不意外!刺激不刺激! 让我们再撸(捋)一(一)串(遍)烧(代)烤(码)。 作者使用IDE工具,直接反编译了这个.class文件 public static void testOne() { int one = 1; int two = one + one; System.out.printf("Two=%d",two); } 这里的变量two竟然没有调用In 所以果断查看编译后的字节码。以下为摘录的部分字节码: LDC "Two=%d" ICONST_1 ANEWARRAY java/lang/Object DUP ICONST_0 ILOAD 2 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; AASTORE INVOKEVIRTUAL java/io/PrintStream.printf (Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; POP 可以看出确实是IDE的锅,这里不仅调用了一次 完整的Java代码应该是如下所示: public static void testOne() { int one = 1; int two = one + one; Object[] params = { Integer.valueOf(two) }; System.out.printf("Two=%d",params); } 所以只要在方法调用前修改 public class OnePlusOne { static { try { Class<?> cacheClazz = Class.forName("java.lang.Integer$IntegerCache"); Field cacheField = cacheClazz.getDeclaredField("cache"); cacheField.setAccessible(true); Integer[] cache = (Integer[]) cacheField.get(null); //这里修改为 1 + 1 = 3 cache[2 + 128] = new Integer(3); } catch (Exception e) { e.printStackTrace(); } } public static void testOne() { int one = 1; int two = one + one; System.out.printf("Two=%d",two); } } two == 2 ? 在修改完 public static void testTwo() { int one = 1; int two = one + one; System.out.println(two == 2); System.out.println(Integer.valueOf(two) == 2); } 上述代码输出如下 true false 因为two == 2不涉及到Integer装箱的转换,还是原始类型的比较,所以原始类型的2永远等于2。
这里可以看到如果拿一个值为null的Integer变量和一个int变量用双等号比较,会抛出NullPointException。 这里的方法如果换成 后记 XCache
java.lang.Integer.IntegerCache.high 看了IntegerCache类获取high的方法 1. 这个值如何如何传递到JVM中? 和系统属性一样在JVM启动时,通过设置 2. 这个方法和 为了将JVM系统所需要的参数和用户使用的参数区别开, 2.1 sun.misc.VM.savedProps中保存全部JVM接收的系统参数。 JVM会在启动时,调用 同时也会调用
以上罗列的属性都是JVM启动需要设置的系统参数,所以为了安全考虑和隔离角度考虑,将其从用户可访问的System.props分开。 2.2 java.lang.System.props中保存除了以下JVM启动需要的参数外的其他参数。
PS:作者使用的JDK 1.8.0_91 Java 9的IntegerCache 幻想一下,如果以上淘气的玩法出现在第三方的依赖包中,绝对有一批程序员会疯掉(请不要尝试这么恶劣的玩法,后果很严重)。 庆幸的是Java 9对此进行了限制。可以在相应的module中编写module-info.java文件,限制了使用反射来访问成员等,按照需要声明后,代码只能访问字段、方法和其他用反射能访问的信息,只有当类在相同的模块中,或者模块打开了包用于反射方式访问。详细内容可参考一下文章: 在 Java 9 里对 IntegerCache 进行修改? 感谢Lydia和飞鸟的宝贵建议和辛苦校对。 最后跟大家分享一个java中Integer值比较不注意的问题: 先来看一个代码片段: public static void main(String[] args) { Integer a1 = Integer.valueOf(60); //danielinbiti Integer b1 = 60; System.out.println("1:="+(a1 == b1)); Integer a2 = 60; Integer b2 = 60; System.out.println("2:="+(a2 == b2)); Integer a3 = new Integer(60); Integer b3 = 60; System.out.println("3:="+(a3 == b3)); Integer a4 = 129; Integer b4 = 129; System.out.println("4:="+(a4 == b4)); } 这段代码的比较结果,如果没有执行不知道各位心中的答案都是什么。 要知道这个答案,就涉及到Java缓冲区和堆的问题。 java中Integer类型对于-128-127之间的数是缓冲区取的,所以用等号比较是一致的。但对于不在这区间的数字是在堆中new出来的。所以地址空间不一样,也就不相等。
所以,以后碰到Integer比较值是否相等需要用 对于Double没有缓冲区。 答案 1:=true 2:=true 3:=false 4:=false 总结 以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |