关于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); }
运行结果,符合预期。
惊喜
上面的程序运行成后,如果我在声明一个integer c = 1, d = 2;
会有什么结果
示例程序如下:
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 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类型的过程中使用了integer.valueof(int)
这个方法了。
肯定是这个方法在内部封装了一些操作,使得我们修改了integer.value
后,产生了全局影响。
所有这涉及该部分的代码一次性粘完(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,该类静态初始化了一个包含了integer.integercache.low
到java.lang.integer.integercache.high
的integer数组。
其中java.lang.integer.integercache.high
的取值范围在[127~integer.max_value - (-low) -1]
之间。
在该区间内所有的integer.valueof(int)
函数返回的对象,是根据int值计算的偏移量,从数组integer.integercache.cache
中获取,对象是同一个,不会新建对象。
所以当我们修改了integer.valueof(1)
的value后,所有integer.integercache.cache[ 1 - integercache.low ]
的返回值都会变更。
我相信你们的智商应该理解了,如果不理解请在评论区call 10086。
好了,那么不在[integercache.low~integercache.high)
的部分呢?
很显然,它们是幸运的,没有被integercache缓存到,法外之民,每次它们的到来,都会new一边,在jvm上分配一块土(内)地(存)。
遐想
如果我把转换的参数换成类型换成int呢?
public static void testone() throws exception { int a = 1, b = 2; swapone(a, b); system.out.println("a=" + 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。
我可以肯定的告诉你,它可以是[integer.min_value~integer.max_value]
区间的任意一个值。
惊喜不惊喜!意外不意外!刺激不刺激!
让我们再撸(捋)一(一)串(遍)烧(代)烤(码)。
作者使用ide工具,直接反编译了这个.class文件
public static void testone() { int one = 1; int two = one + one; system.out.printf("two=%d", two); }
这里的变量two竟然没有调用integer.valueof(int)
,跟想象的不太一样,我怀疑这是ide的锅。
所以果断查看编译后的字节码。以下为摘录的部分字节码:
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的锅,这里不仅调用了一次integer.valueof(int)
,而且还创建一个object的数组。
完整的java代码应该是如下所示:
public static void testone() { int one = 1; int two = one + one; object[] params = { integer.valueof(two) }; system.out.printf("two=%d", params); }
所以只要在方法调用前修改integer.integercache.cache[2+128]
的值就可以了,所以在类的静态初始化部分加些代码。
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 ?
在修改完integer.integercache.cache[2 + 128]
的值后,变量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。
integer.valueof(two)==2
的真实形式是integer.valueof(two).intvalue == 2
,即3==2,所以是false。
这里可以看到如果拿一个值为null的integer变量和一个int变量用双等号比较,会抛出nullpointexception。
这里的方法如果换成system.out.println("two=" + two)
的形式会有怎样的输出?你可以尝试一下。
后记
xcache
类 | 是否有cache | 最小值 | 最大值 |
---|---|---|---|
boolean | 无 | -- | -- |
byte | bytecache | -128 | 127(固定) |
short | shortcache | -128 | 127(固定) |
character | charactercache | 0 | 127(固定) |
integer | integercache | -128 | java.lang.integer.integercache.high |
long | longcache | -128 | 127(固定) |
float | 无 | -- | -- |
double | 无 | -- | -- |
java.lang.integer.integercache.high
看了integercache类获取high的方法sun.misc.vm.getsavedproperty
,可能大家会有以下疑问,我们不拖沓,采用一个问题一解答的方式。
1. 这个值如何如何传递到jvm中?
和系统属性一样在jvm启动时,通过设置-djava.lang.integer.integercache.high=xxx
传递进来。
2. 这个方法和system.getproperty
有什么区别?
为了将jvm系统所需要的参数和用户使用的参数区别开,java.lang.system.initializesystemclass
在启动时,会将启动参数保存在两个地方:
2.1 sun.misc.vm.savedprops中保存全部jvm接收的系统参数。
jvm会在启动时,调用java.lang.system.initializesystemclass
方法,初始化该属性。
同时也会调用sun.misc.vm.saveandremoveproperties
方法,从java.lang.system.props
中删除以下属性:
- sun.nio.maxdirectmemorysize
- sun.nio.pagealigndirectmemory
- sun.lang.classloader.allowarraysyntax
- java.lang.integer.integercache.high
- sun.zip.disablememorymapping
- sun.java.launcher.diag
以上罗列的属性都是jvm启动需要设置的系统参数,所以为了安全考虑和隔离角度考虑,将其从用户可访问的system.props分开。
2.2 java.lang.system.props中保存除了以下jvm启动需要的参数外的其他参数。
- sun.nio.maxdirectmemorysize
- sun.nio.pagealigndirectmemory
- sun.lang.classloader.allowarraysyntax
- java.lang.integer.integercache.high
- sun.zip.disablememorymapping
- sun.java.launcher.diag
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 b3=60
,这是一个装箱过程也就是integer b3=integer.valueof(60)
所以,以后碰到integer比较值是否相等需要用intvalue()
对于double没有缓冲区。
答案
1:=true
2:=true
3:=false
4:=false
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。