首先说观点: java 只有值传递没有引用传递
然后再来看看值传递与引用传递两者的定义
值传递 (pass by value) 是指在调用函数时将实际参数复制一份传递到函数中, 这样在函数中如果对参数进行修改, 将不会影响到实际参数.
引用传递 (pass by reference) 是指在调用函数时将实际参数的地址直接传递到函数中, 那么在函数中对参数所进行的修改, 将影响到实际参数.
这里牢记值传递中将实际参数复制一份.
然后就是对于参数类型: 值类型 和 引用类型.
结合起来理解就是: 值类型传递, java 是将其值内容复制一份给形参; 对于引用类型传递, java 是将其地址复制一份给形参.
下面结合实例深入理解为什么 java 只有值传递
package 字符串;
public class 值传递 {
- public static void main(String[] args)
- {
- String str1="abc";
- updateStr1(str1);
- System.out.println("main 函数中"+str1);
- }
- public static void updateStr1(String str1)
- {
- str1="cba"; //<注解>
- System.out.println("调用函数中"+str1);
- }
- }
结果:
在这里我们能够清晰看到我们传递的是 String 类型的对象即(引用类型), 并且在调用函数中我们修改了 str1 为 cba, 如果是引用传递那么我们在主函数打印则应该是 cba,
但是很遗憾我们在主函数中仍然打印出来的是 abc. 所以我们可以说 java 是值传递类型了吗, 答案是不完全的.
接下来再看这一段代码:
package 字符串;
- public class person {
- private int age;
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
public class 值传递 2 {
- public static void main(String[] args)
- {
- person p1=new person();
- p1.setAge(10);
- System.out.println("我在主函数里对 p1 的年龄属性赋值为"+p1.getAge());
- setage(p1);
- System.out.println("我再从主函数里获取 P1 的年龄属性"+p1.getAge());
- }
- public static void setage(person p1)
- {
- p1.setAge(18); // 不是我们对它的地址进行了操作, 而是我们对它地址的内容进行了操作
- System.out.println("我在调用函数里对 p1 的年龄属性重新赋值为"+p1.getAge());
- }
- }
结果:
咦, 怎么回事这次也是传递的对象(引用类型), 为什么这次我们对年龄这个字段的修改在主函数同步了呢?
别急, 下面我们先来分析这两个例子.
首先第一个类型的例子中, 我们传递的是 String 类型的变量, 它是一个特殊的类型的引用变量.
(不可变字符串: 编译器可让字符串共享, 即将各种字符串存放于公共存储池中, 字符串变量是指向其中相应位置 -- 出自《Java 核心技术卷 1》)
出于这句话的理解就是每个字符串都对应一个地址: 我们例一中是将 str1 的地址复制给了我们的形参 str1, 并且形参中 str1 的地址进行了改变指向了 "cba" 的地址. 所以说在主函数中的 str1 的地址仍然指向的是 "abc" 所对应的地址.
所以说对于 String 类型的变量, 我们对于给它重新赋值不是改变了它的内容, 而是改变了它指向字符串的位置. 这也就解释了为什么 java 中 String 类型是不可变类型.
而在我们例二中, 我们将 p1 的地址复制给了我们形参中的 p1, 此时他们都指向的内存中一块相同的地址这里存放着相同内容, 所以我们在调用函数对这个地址中的内容进行修改时就会同步到我们主函数中的 p1. 所以这个并不意味着这个是引用传递.
好吧, 那怎么才能解释好 Java 确实是值传递呢(上面 String 类型例子是特殊的引用类型不方便解释)
下面我们通过这个例子说明:
package 字符串;
- public class person {
- private int age;
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
public class 值传递 3 {
- public static void main(String[] args) {
- person p1=new person();
- person p2=new person();
- p1.setAge(10);
- p2.setAge(18);
- System.out.println("我在主函数里对 p1 的年龄属性赋值为"+p1.getAge());
- System.out.println("我在主函数里对 p2 的年龄属性赋值为"+p2.getAge());
- swap(p1,p2);
- System.out.println("************ 我是主函数里的分割线 ***************");
- // 我再在主函数里分别对 p1,p2 获取他们的年龄, 若为引用传递则 p1 的年龄应该为 18,p2 为 10.
- System.out.println("我在主函数里获取 p1 的年龄"+p1.getAge());
- System.out.println("我在主函数里获取 p1 的年龄"+p2.getAge());
- }
- public static void swap(person p1,person p2)
- {
- System.out.println("************ 我是调用函数里的分割线 ***************");
- person temp=new person();
- temp=p1;
- p1=p2;
- p2=temp;
- System.out.println("我在调用函数里交换了 p1 和 p2 指向的地址");
- System.out.println("我在调用函数里对 p1 的年龄属性赋值为"+p1.getAge());
- System.out.println("我在调用函数里对 p2 的年龄属性赋值为"+p2.getAge());
- }
- }
结果:
看到没, 这就是充分说明 Java 是值传递的例子. 在这个例子中我们依然传递的是 person 类的对象 p1,p2(引用类型), 他们将各自的地址复制一份到了形参 p1,p2.
然后我们在调用函数中交换了他们的地址, 确实在调用函数中他们的 age 属性发生交换. 但是再当我们在主函数获取他们的 age 时, 如果是引用传递则应该 p1 的 age 为 18,p2 的 age 为 10,
和我们在调用函数中打印结果一致. 但是, 很遗憾在主函数中他们的值仍然是 p1(10),p2(18). 所以这也充分印证了 java 是值传递.
那么什么是引用传递呢? 我们把代码放入 C# 看看.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
namespace 值传递 or 引用传递
- {
- public class person
- {
- private int age;
- public int getAge()
- {
- return age;
- }
- public void setAge(int age)
- {
- this.age = age;
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- person p1 = new person();
- person p2 = new person();
- person p3 = new person();
- p1.setAge(10);
- p2.setAge(18);
- p3.setAge(15);
- Console.WriteLine("我在主函数里对 p1 的年龄属性赋值为" + p1.getAge());
- Console.WriteLine("我在主函数里对 p2 的年龄属性赋值为" + p2.getAge());
- Console.WriteLine("我在主函数里对 p3 的年龄属性赋值为" + p3.getAge());
- swap(ref p1,ref p2,p3);
- Console.WriteLine("************ 我是主函数里的分割线 ***************");
- // 我再在主函数里分别对 p1,p2 获取他们的年龄, 若为引用传递则 p1 的年龄应该为 18,p2 为 10.
- Console.WriteLine("我在主函数里获取 p1 的年龄" + p1.getAge());
- Console.WriteLine("我在主函数里获取 p2 的年龄" + p2.getAge());
- Console.WriteLine("我在主函数里获取 p3 的年龄" + p3.getAge());
- }
- public static void swap(ref person p1,ref person p2, person p3)
- {
- Console.WriteLine("************ 我是调用函数里的分割线 ***************");
- person temp = new person();
- temp = p1;
- p1 = p2;
- p2 = temp;
- p3.setAge(20);
- Console.WriteLine("我在调用函数里交换了 p1 和 p2 指向的地址");
- Console.WriteLine("我在调用函数里对 p1 交换地址后年龄为" + p1.getAge());
- Console.WriteLine("我在调用函数里对 p2 交换地址后年龄为" + p2.getAge());
- Console.WriteLine("我在调用函数里修改 p3 年龄为" + p3.getAge());
- }
- }
- }
结果:
请注意在 C# 中如果我们要实现引用传递, 请加上关键字 ref, 否则, 它执行的原理仍然与我们 java 中执行的机制一样, 即拷贝一份地址给形参.
如果你还有点晕, 不妨我们来看看下面两张图.
为了方便大家理解把图画成这样, 然后关于 java 的值传递深度分析就到这里. 欢迎大家一起讨论.(可以打脸 / 哈哈)
来源: https://www.cnblogs.com/kunming97/p/10665287.html