前不久,一个同事一大早发了一个题目给我,让我实现,今天突然想起来,闲来无聊,做一下笔记
入题
请用Java完成swap函数,交换两个整数类型的值。
我用的Java版本是1.8u152
@Test
public void swap() throws Exception {
Integer a = 1, b = 2;
swap(a, b);
System.out.println("a=" + a + ", b=" + b);
}
public void swap(Integer a, Integer b){
// 需要实现的部分
}
很多人会写出下面这样的代码
public void swap(Integer a, Integer b) throws Exception {
Integer temp= a;
a = b;
b = temp;
}
上面的运行的结果显示a和b两个值肯定不会交换。 那么让我们来看一下上述程序运行时,Java对象在内存中的分配方式:
由此可以看到,在两个方法的局部变量表中分别持有的是对a、b两个对象实际数据地址的引用。上面实现的swap函数,仅仅交换了swap函数里局部变量a和局部变量b的引用,并没有交换JVM堆中的实际数据。所以main函数中的a、b引用的数据没有发生交换,所以main函数中局部变量的a、b并不会发生变化。
那么要交换main函数中的数据要如何操作呢?
仔细想了一下,只能去内存中去修改a和b的数值,然后第一时间就是想到反射去修改,于是去查了一下integer的成员属性,发现有一个int 型的value属性。所以我们使用反射来修改该值,代码如下:
/**
* 1、java 存在传参和传引用,不像C/C++中使用指针改变一下指向的地址就解决了,
* 于是我们使用反射来实现,需要注意的是,要避免拆箱装箱
* 2、如何避免使用缓存空间:
* a) 使用new 关键字来强制生成对象,避免自动拆箱装箱
* b) 使用Field 类的 setInt 来避免使用缓存,避免拆箱装箱
*
* @param var1
* @param var2
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
private void swap(Integer var1, Integer var2) throws IllegalAccessException, NoSuchFieldException {
int temp = var1;
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
value.set(var1, var2);
System.out.println("反射机制修改var1后的Field实例的值:" + value.get(var1));
System.out.println("-------------separate------------");
value.setInt(var2, temp);
System.out.println("反射机制修改var2后的Field实例的值:" + value.get(var2));
}
运行结果完全符合预期。
上面的程序运行成后,如果我在声明一个 Integer c = 1, d = 2;
会有什么结果
@Test
public void swap() throws Exception {
Integer a = 1, b = 2;
swap(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
惊不惊喜,意不意外!!!
深入
在Java对原始类型int自动装箱到Integer类型的过程中使用了 Integer.valueOf(int)
这个方法了。肯定是这个方法在内部封装了一些操作,使得我们修改了 Integer.value
后,产生了全局影响。
然后去看了一下integer的源码,发现了事情的真相
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 ]
的返回值都会变更。
好了,那么不在 [ IntegerCache.low
,IntegerCache.high
]的部分呢?
很显然,它们是幸运的,没有被IntegerCache缓存,每次它们的到来,都会new一个新的对象,在JVM上分配一块内存。
后记
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
传递进来,在IDEA中可以这样设置。
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