java integer_关于Java中你所不知道的Integer详解

本文深入探讨了Java中Integer对象的交换问题,揭示了Integer缓存机制的工作原理。通过实例展示了Integer对象在内存中的分配以及方法参数传递的影响。此外,文章还分析了Integer.valueOf(int)方法在特定数值范围内的缓存行为,以及这种行为对代码执行结果的影响。最后,文章提醒读者注意Integer对象比较时的陷阱,并讨论了Java9对此的限制。
摘要由CSDN通过智能技术生成

前言

本文主要给大家介绍了关于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对象在内存中的分配方式:

b63b6a02d92957630a277a07f649182d.png

对象地址分配

由此可以看到,在两个方法的局部变量表中分别持有的是对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文件,限制了使用反射来访问成员等,按照需要声明后,代码只能访问字段、方法和其他用反射能访问的信息,只有当类在相同的模块中,或者模块打开了包用于反射方式访问。详细内容可参考一下文章:

感谢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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值