今天偶尔看到一个讲Java函数中的参数传递相关问题的视频,虽然是一个很简单的程序,但是里面涉及到很多Java基础知识,有必要整理一下。
Java的函数参数传递到底是值传递还是引用传递?
明确概念
值传递:表示方法接收的是调用者提供的值。
引用传递:表示方法接收的是调用者提供的变量地址。
根据Horstmann的《java核心技术》可知,Java是没有引用传递的。
java程序设计语言总是采用值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。
有些程序员(甚至是本书的作者),认为java程序设计语言对对象采用的是引用调用,实际上这种理解是不对的。
关于Java的值传递大部分人认为,
传递基本类型
public class Swap {
public static void main(String[] args) {
Integer a = 10;
System.out.println("swap before: a = "+a);
swap(a);
System.out.println("swap after: a = "+a);
}
private static void swap(Integer a) {
// TODO Auto-generated method stub
a = 30;
}
}
结果:
swap before: a = 10
swap after: a = 10
结果还是不变的,方法内对参数操作不影响原数据。
传递对象类型
public class Swap {
public static void main(String[] args) {
Person p = new Person();
p.setAge(20);
System.out.println("update before: age = "+p.getAge());
update(p);
System.out.println("update after: age = "+p.getAge());
}
private static void update(Person p) {
p.setAge(30);
}
}
class Person{
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
结果:
update before: age = 20
update after: age = 30
结果变化了,对象参数是传入地址值,然后修改了对象中的值,地址仍不变化,但是值变了。
基于上述情况,如果写一个互换数据的函数就不能按照常规写法,以下是使用反射来互换
import java.lang.reflect.Field;
public class Swap {
public static void main(String[] args) {
// TODO Auto-generated method stub
Integer a=10,b=15;
System.out.println("swap before:a="+a+",b="+b);
swap(a,b);
System.out.println("swap after:a="+a+",b="+b);
}
private static void swap(Integer a, Integer b) {
try {
Field f = Integer.class.getDeclaredField("value");
f.setAccessible(true);
Integer temp = new Integer(a.intValue());
f.set(a, b.intValue());
f.set(b, temp);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
结果:
swap before:a=10,b=15
swap after:a=15,b=10
其中需要注意其中一句代码
Integer temp = new Integer(a.intValue());
这是新建一个对象,而不是直接赋予,为什么呢?
Java的Integer.java源码中有这样一段代码
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.low
到IntegerCache.high
之间([-127,128]),那么直接在IntegerCache
里面获取,如果值大于这个范围则会新建一个Integer类型
。
例如若改成Integer temp = a.intValue()
则结果会是
swap before:a=10,b=15
swap after:a=15,b=15
temp第一次赋值的之后是10,之后进行f.set(a, b.intValue());
操作之后a变成15,因为是从IntegerCache
拿值,导致这个时候temp变了,变成了15,最后导致结果出现偏差。所以需要避开从IntegerCache
取值才能使得结果准确。
如果a、b超过那个范围,再看结果
swap before:a=231,b=144
swap after:a=144,b=231
所以就需要自己重新新建对象才能正确。