先来看下面的场景:
要求写一个函数交换Integer类型的a和b的值:
注意这是错误的版本
public static void swap(Integer a,Integer b) {
Integer t=a;
a=b;
b=t;
}
public static void main1(String[] args) {
Integer x = 1;
Integer y = 2;
System.out.println("交换前:" + x + ":" + y);
swap(x,y);
System.out.println("交换后:" + x + ":" + y);
}
很不幸,这是错误的方法。那么为什么Integer时引用类型的还是没有交换成功呢?
- 我们先来看内存图:
通过上图我们可以看到x,y在堆里的指向始终没有改变。去查看Integer内部的源码:
public final class Integer extends Number implements Comparable<Integer> {
我们可以看到Integer使用final类型的int进行存储的。final类型的
变量不能被重新赋值,所以操作参数传递变量时,实际上是操作的变量对
象的副本(java中的包装类型都是默认使用这种方式实现的,使用拷贝副
本的方式提升效率和减少内存消耗)。
所以swap中传递的a,b只是传递的x,y值的副本,对副本进行操作不会影响原来的值.
再看第二种错误方法:
public static void main(String[] args) {
User a = new User("1");
User b = new User("2");
System.out.println("交换前name:" + a + ":" + b);
swap(a,b);
System.out.println("交换后name:" + a + ":" + b);
}
private static void swap(User a, User b) {
User tmp = a;
a = b;
b = tmp;
}
}
class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
我们发现还是交换错误的。它实际上并没有修改堆里面的对象,
所以我们看他到底交换没主要是看他修改的是变量(引用)还是修改的堆里面的对象
那么我们该怎么交换两个变量的值呢?
我们可以用反射修改两个变量的值
public static void swap4(Integer a, Integer b) throws Exception {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);//设置可以访问成员的私有不可变的变量
Integer tmp =new Integer(a.intValue());
field.set(a, b.intValue());
field.set(b, tmp);
}
java到底是值传递还是引用传递?
我们知道java有基本数据类型(8种)和引用数据(类,接口,String,数组…)类型:
- 基本数据类型都是直接存储在内存中的内存栈上的,数据本身的值就是存储在栈空间里面,
- 引用类型继承于Object类(也是引用类型)都是按照Java里面存储对象的内存模型来进行数据存储的,使用Java内存堆和内存栈来进行这种类型的数据存储,简单地讲,“引用”是存储在有序的内存栈上的,而对象本身的值存储在内存堆上的;
而我们java中还是值传递,如果参数是基本类型,传递的是基本类型的变量值。如果参数是引用类型,传递的是该参数所引用的对象在堆中地址值。而地址值也是值,
- 值传递:就是在方法调用的时候,实参是将自己的一份拷贝赋给形参,在方法内,对该参数值的修改不影响原来实参。
- 引用传递:是在方法调用的时候,实参将自己的地址传递给形参,此时方法内对该参数值的改变,就是对该实参的实际操作。”