文章目录
1.结论
java是值传递
为啥先说结论呢,因为好多文章总是一大篇推论,人都看晕了,最后才说结论,导致印象不深
解释
java里面其实只有一种参数传递方式就是按值传递
,为什么有争议,原因是参数类型分为引用类型
和基本类型
- 如果参数传递的是
基本类型
,接收的其实是原始值
的副本
,操作副本
并不会对原始值
产生影响 - 如果参数传递的是
引用类型
,接收的其实是原始值
的引用地址,而不是值的副本
,因此如果函数修改了这个参数的值,实际上是修改内存地址的值,从而影响原始值
JVM为什么这样设计?
为了提升效率,节省资源
-
基本类型和引用类型拷贝副本的效率不一样,基本类型更快
-
引用类型一般占空间比较大,拷贝副本会比较浪费内存,所以设计为传递引用地址
对象访问定位
https://blog.csdn.net/xiewenfeng520/article/details/85319048
对象的引用地址是存储在栈的reference里面,指向java堆中的内存地址
2.全方位证明
2.1包装类或者基本数据类型
场景:定义Integer类型a和b,交换a,b对象
why?为啥要将Integer和int一起说
虽然Integer是引用类型,但是源码实现中,Integer使用final类型的int进行存储的,final类型的变量不能被重新赋值,所以操作参数传递变量时,实际上是操作的变量对象的副本
。
和int类型的实现原理是一样的,都是操作变量的副本
java中的包装类型都是默认使用这种方式实现的,使用拷贝副本的方式提升效率和减少内存消耗
代码
public class IntegerTest {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
System.out.println("before----a=" + a + ",b=" + b);
swap(a, b);
System.out.println("after----a=" + a + ",b=" + b);
}
private static void swap(Integer a, Integer b) {
System.out.println("running----a=" + a + ",b=" + b);
Integer tmp;
tmp = a;
System.out.println("running----tmp="+tmp);
a = b;
b = tmp;
System.out.println("running----a=" + a + ",b=" + b+ ",tmp=" + tmp);
}
执行结果
结论
从这个结果可以看出,swap方法传递的是值,因为交换a,b的值之后,原来的对象没有发生变化;
如果是引用传递,交换的是对象引用的地址,a,b就会发生变化,实际上却没有;
其实swap中传递的a,b只是传递的a,b值的副本,对副本进行操作不会影响原来的值。
备注
将Integer类型替换为基本类型int,或者是String等结果都是一样的(博主亲测)
2.2集合类型
场景:定义List集合a和b,交换a,b对象
代码
public static void main(String[] args) {
List a = new ArrayList();
a.add(1);
List b = new ArrayList();
b.add(2);
System.out.println("before----a=" + a + ",b=" + b);
swap(a, b);
System.out.println("after----a=" + a + ",b=" + b);
}
private static void swap(List a, List b) {
List tmp ;
tmp = a;
a = b;
b = tmp;
}
执行结果
结论
和2.1中的结论一致
2.3 引用类型(争议点)
场景:定义User类型a和b,交换a,b对象
代码
public class ObjectTest {
public static void main(String[] args) {
User a = new User("1");
User b = new User("2");
System.out.println("before----a=" + a + ",b=" + b);
swap(a, b);
System.out.println("after----a=" + a + ",b=" + b);
}
private static void swap(User a, User b) {
User tmp ;
tmp = a;
a = b;
b = tmp;
}
}
user:
public 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() {
// +super.toString()
return "User{" +
"name='" + name + '\'' +
'}';
}
}
执行结果
误区
public class ObjectTest {
public static void main(String[] args) {
User a = new User("1");
System.out.println("before----a=" + a );
swap(a);
System.out.println("after----a=" + a );
}
private static void swap(User a) {
a.setName("2");
// a = new User("2");
}
}
执行结果
推导结论
第一个例子和我们2.1中的结论一致,但是第二个例子看似对变量a做了修改。
如果将a.setName("2");
换成a = new User("2");
,a的值不会发生变化
- 第一种setName的方式实际上是拿到对象
栈的reference
中的引用地址,找到了堆中的内存地址进行了修改,并不是直接对传递过来的原始对象进行修改
- 拿到对象的引用地址
- 对引用地址指向的内存地址的值进行修改
直接对对象进行修改,应该是开辟一块新的内存地址替换掉原始对象的引用地址
- 第二种new的方式则是将一个新的内存地址赋值给了
原始对象参数引用的副本
,所以并没有对原始对象
产生影响,如果是引用传递
会直接将新的内存地址替换掉原始值
的内存地址
然而a的值并没有发生变化,更加证实了参数传递的不是引用而是值
结论
引用类型的参数传递,是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。
3.最后结论
值传递
和引用传递
的区别并不是传递的内容
。而是实参到底有没有被复制
一份给形参
综上所述java参数传递是值传递
4.彩蛋
解决Integer a,b值交换问题
分享一道java面试题 Integer值交换