Java 是值传递还是引用传递

Java 是值传递还是引用传递最近整理面试题 整理到值传递 引用传递 到网上搜了一圈 争议很大 带着一脸蒙圈 线上线下查了好多资料 最终有所收获 所以分享给大家 希望能对你有所帮助 首先说下我的感受 这个题目出的很好 但是在 Java 中这个题目是有问题的 在下面我会解释 并且 有很多结论是 Java 中只有值传递 我认为这样说不够严谨 当然如果针对 Java 语言本身来讲 Java 中只有值传递 没有

一、值传递、引用传递定义

public class StringBase {     public static void main(String[] args) {         int c = 66; //c 叫做实参         String d = "hello"; //d 叫做实参         StringBase stringBase = new StringBase();         stringBase.test5(c, d); // 此处 c 与 d 叫做实参         System.out.println("c的值是:" + c + " --- d的值是:" + d);     }          public void test5(int a, String b) { // a 与 b 叫做形参         a = 55;         b = "no";     } }

可以看出通过方法传递后,int 类型与 String 类型的原值并没有受到前面 test5 方法执行后的影响,还是输出了原值。这种形为通常被说成值传递。如果原值经过 test5 方法后被改变了,这种形为通常被描述为引用传递

定义

    值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
    引用传递:是指在调用函数时将实际参数的地址直接传递到函数中(的形参),那么在函数中对参数所进行的修改,将影响到实际参数。
    引用传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。(下面文章中 C++ 的定义,我觉得这样说更精简形象一些,所以放了两个定义,其实意思是一样的)




    以上,就是相关的定义,大家对这个定义几乎没有分歧,但是我建议大家,有必要去看看 C++ 中 值传递、引用传递的定义。因为在 C++ 中有三个定义:值传递、引用传递、指针传递,推荐一个地址: C++ 值传递、指针传递、引用传递详解

//引用传递 void change2(int &n) {     cout << "引用传递--函数操作地址" << &n << endl;     n++; }

    我们看上边 C++ 引用传递的代码,使用的 & 操作符。& 操作符在 C++ 中被定义为"引用",引用在 C++ 中的定义是“引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样”,再看引用其中的一个描述:“声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元”。因此这引用的概念在 Java 中根本不存在。Java 中哪有给变量起个别名的!!!
    因此说,这个题出的就有问题,在 Java 官方中我一直没有找到明确的证据说“Java 中 值传递、引用传递 的定义”我所看到的全是说 C++ 中关于值传递、引用传递的定义。但是,在 Java 中没有 C++ 里"引用"的概念。Java 里只有对象,new 关键字。这就很尴尬了,拿 C++ 中的定义,来解释 Java,我觉得这就是有问题的。问题就出在了引用传递!!!
    在 C++ 中关于引用传递的定义明确,代码解释清晰。在 C++ 中引用传递,传递的是一个别名,操作别名就跟操作原值一个样。
    然而在 Java 中,没有引用的概念,Java 中只要定义变量就会开辟一个存储单元。因此,对 Java 语言来说只有值传递,没有引用传递是正确的。
    虽然 Java 中没有引用(C++ 中 引用"&")。但是,引用传递的定义,在 Java 中还是有符合条件的。抛开语言中的特性。只针对:值传递、引用传递的定义我们来分析一下,Java 是属于值传递还是引用传递。
    要想知道 Java 是属于值传递还是引用传递,这就要从 Java 内存模型聊起了,我们来看基本数据类型与引用类型在内存中的存储方式。










二、基本数据类型、引用类型

1.基本数据类型、引用类型定义
    基本数据类:Java 中有八种基本数据类型“byte、short、int、long、float、double、char、boolean”
    引用类型:new 创建的实体类、对象、及数组
2.基本数据类型、引用类型在内存中的存储方式
    基本数据类型:存放在栈内存中。用完就消失。
    引用类型:在栈内存中存放引用堆内存的地址,在堆内存中存储类、对象、数组等。当没用引用指向堆内存中的类、对象、数组时,由 GC回收机制不定期自动清理。
3.基本类型、引用类型内存简单说明图












Java 是值传递还是引用传递
    好,看了基本的内存图,应该能明白 Java 是属于值传递还是引用传递。不明白,也没关系,下面会详细说明,先说引起争议的代码。

三、在 Java 中 值传递 与 引用传递,产生模糊不清的代码

public class TransmitTest {     public static void main(String[] args) {         String a = "hello"; //String 引用数据类型,调用 pass 方法后 b 的值没有改变,不是 hello         int b = 1; //int 基本数据类型,调用 pass 方法后 a 的值没有改变,还是 1         User user = new User(); //new Class 引用类型,调用 pass 方法后 name 与 age 的值改变了         user.setName("main"); // 调用 pass 后,name 为 pass 了         user.setAge(2); //调用 pass 后,age 为 4 了         pass(user, a, b); //pass 方法调用         System.out.println("main 方法 user 是:" + user.toString());         System.out.println("main 方法 a 的值是:" + a + " --- b 的值是:" + b);     }     public static void pass(User user, String a, int b) {         a = "你好";         b = 3;         user.setName("pass");         user.setAge(4);         System.out.println("pass 方法 user 是:" + user.toString());         System.out.println("pass 方法 a 的值是:" + a + " --- b 的值是:" + b);     } } class User {     String name;     int age;     public String getName() {         return name;     }     public void setName(String name) {         this.name = name;     }     public int getAge() {         return age;     }     public void setAge(int age) {         this.age = age;     }     @Override     public String toString() {         return "name = " + name + " --- age = " + age;     } }
public static void pass(User user, String a, int b) {     a = "你好";     b = 3;     user = new User();     user.setName("pass");     user.setAge(4);     System.out.println("pass 方法 user 是:" + user.toString());     System.out.println("pass 方法 a 的值是:" + a + " --- b 的值是:" + b); }

    这样一来,改变了形参的值,但是实参没有改变。因此有人得出结论,Java 中只有值传递,没有引用传递。(我并不这么认为,原因如下)
    使用 user = new User() 这个代码来做验证,我觉得是符合 String 类型做形参时的验证地,但是,此示例不符合引用传递的验证
    在验证之前,我们先看下使用 user=new User(); 语句之前与之后的内存模型图,能有助于我们更好的验证结果,同时也有助于更好的理解 Java 内存模型。我们看 TransmitTest 类在 Java 内存模型中的存储图:




图1 pass() 方法中没有使用 user=new User() 语句的内存模型图

Java 是值传递还是引用传递
    在 图1 中,main() 方法中的 user 类,与 pass() 方法中的 user 类,指向的是同一个堆内存中的 User 类,红色虚线是在 main() 方法中初次给 name 属性赋的值"main"。实线部分,是在 pass() 方法中给 name 属性赋的值"pass"。因为在堆内存中只有一个 User 类实体,因此 main() 方法与 pass() 方法中的 user 指向的都是同一个 User 类 0x000031。因此,无论在 main() 方法还是 pass() 方法中,改变其 user 的属性值后,打印 User 类的属性值肯定是一样的,他们用的是一个实体类。

图2 pass() 方法中使用了 user=new User() 语句的内存模型图

Java 是值传递还是引用传递
    在 图2 中,main() 方法中的 user 类首次加载,堆内存开辟了一个地址为 0x000031 的 User 类实体。当把 main() 方法中的实参 user 传递给 pass() 方法中形参 user 的时候,栈内存在 pass() 方法区中开辟了一个空间,并引用了地址为 0x000031 的 User 类。此时两个方法中的 User 类其实是一个。
    然而当 pass() 方法中的 user=new User()语句执行后,堆内存中新开辟了一个地址为 0x000032 的 User 类,pass() 方法中的 user 从此指向了地址为 0x000032 的 User 类。
    因为 pass() 方法 与 main() 方法中的 user 属性分别指向了不同的 User 类,所以两个方法中的 User 类的属性无论怎么修改,相互都不影响。
    但是,这种操作是不能验证引用传递定义的。因为实参传值给形参后,形参自己改变了地址,这就和引用传递无关了。我们再来用代码验证。
    我们可以使用 C++ 引用传递代码来验证,使用 user = new User() 语句验证引用传递的错误性
C++ 中引用传递代码












class User { public:     int age;   // 长度     string name;  // 宽度 }; //引用传递 void pass(User &user) {     cout << "引用传递 -- user的地址是:" << &user << endl;     user.age = 2;     user.name = "你好"; } int main() {     User user;     user.age = 1;     user.name = "hello";     cout << "实参 -- user的地址是:" << &user << endl;     pass(user);     cout << "实参 -- user的值 age=" << user.age << ",name=" << user.name << endl;     system("pause");     return false; }

    在 C++ 中,引用传递的实参与形参地址一致,在引用的方法中,使用的就是实参的地址。当修改形参值后,实参值也跟着变。现在我们按照 user=new User(); 的方法改变一下引用方法 pass 如下:

//引用传递 void pass(User &user) {     cout << "引用传递 -- user的地址是:" << &user << endl;     User user2 = user; //相当于 Java 中的 user=new User();     cout << "引用传递 -- user2的地址是:" << &user2 << endl;     user2.age = 2;     user2.name = "你好"; }

    我们看,改变引用传递中形参 user 的地址后(后期改变的地址,这跟引用传递,值传递还有什么关系?),再修改形参 user 的值,实参没有任何变化。这就破坏了引用传递的场景,因此不能使用 user=new User(); 语句来验证引用传递的定义。

    排除了其他异议,我们再来分析 Java 中有没有引用传递。

先把引用传递的定义放上

    引用传递:是指在调用函数时将实际参数的地址直接传递到函数中(的形参),那么在函数中对参数所进行的修改,将影响到实际参数。
    引用传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

定义关键1:是指在调用函数时将实际参数的地址直接传递到函数中(给形参了)
    证明:Java 在进行方法调用传递引用类型参数的时候,就是先给形参一个与实参相同的地址的(此处与 C++ 的不同之处是,C++ 是别名,没有在内存中给形参开辟空间,而 Java 给形参开辟了一个栈内存空间,存放与实参相同的引用地址。但是这与引用传递的定义不违背啊!!!定义可没说形参是否有开辟空间的概念)。

定义关键2:在函数中对参数所进行的修改,将影响到实际参数。
    证明:Java 在进行方法调用传递引用类型参数后,修改形参的内容后,就是影响了实参的值。

四、String 与包装类的特殊分析

    好了,解决了实例对象,我们再来说 String 与包装类,为什么 String 与包装类作为引用类型,却有值传递的功能,居然没有影响到实参!

原因如下
    我们都知道。String 类型及其他七个包装类,是一群特殊群体。当使用 String a = "hello"; 语句时,相当于执行了 String a = new String("hello")。然而在 Java 中每一次 new 都是一次对象的创建。如果你创建的对象在堆中不存在,便会创建一个,如果是新创建的对象,那么地址都会变的,后期改变的地址,这跟引用传递,值传递还有什么关系?

    其实 String 型方法参数传值的过程,可以用以下代码来解释,我们先看 String 类型的还原:

String a = "hello"; //a 相当于实参 String a1 = a; //a1 就相当于形参 a1 = "你好"; System.out.println("a是:" + a + " --- a1是:" + a1);

内存图如下

Java 是值传递还是引用传递
    a1 = "你好"; 等同于 a1 = new String("你好")。在 String 池中检查是否有 "你好" 的常量。如果有,将 a1 的地址指向 "你好" 的地址。如果 String 池中没有 "你好" 常量,在堆内存中创建 "你好" 常量,并将 a1 地址指向 "你好"。

内存图如下

Java 是值传递还是引用传递
    总结如下:String 类型,在进行方法传参的时候,是先将实参地址,赋值给形参(形参在栈内存中确实新开辟了一个新的内存空间,用于存储地址)。但是当再次给 String 类型的形参赋值(与实参内容不一样的值时),形参地址变了,这就和引用传递无关了。我们可以用 C++ 代码中的引用传递,来验证 String 型的这一特殊情况,代码如下:

//引用传递 void pass(string &a, int &b) {     cout << "引用传递 -- a的地址是:" << &a << " --- b的地址是:" << &b << endl;     cout << "引用传递 -- a的值是:" << a << " --- b的值是:" << b << endl;     string c = a; // 相当于 java 中的 new String     int e = b;     cout << "引用传递 -- c的地址是:" << &c << " --- e的地址是:" << &e << endl;     cout << "引用传递 -- c的值是:" << c << " --- e的值是:" << e << endl;     c = "你好"; //在引用传递中改变形参地址后做修改操作,不影响实参     e = 2; //在引用传递中改变形参地址后做修改操作,不影响实参 } int main() {     string a = "hello";     int b = 1;     cout << "实参 -- a的地址是:" << &a << " --- b的地址是:" << &b << endl;     pass(a, b);     cout << "实参 -- a的值是:" << a << " --- b的值是:" << b << endl;     system("pause");     return false; }

    我们看,在 C++ 中的引用传递方法中,改变形参的地址后做修改操作,照样不影响实参的值,这就破坏了引用传递的本质,不能这样比喻。

    因此,String 与其他包装类,在做形参的时候,由于他们在赋不同于实参的值时,改变了形参的地址,因此使引用传递,看起来像值传递,其实本质还是引用传递。

五、总结

此题争议很大,我仅分享自己的理解,如有不同结论,欢迎指正,一起共勉!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/210187.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午8:06
下一篇 2026年3月19日 上午8:06


相关推荐

  • 优先队列的优先级_kafka优先级队列

    优先队列的优先级_kafka优先级队列概念☺优先队列是一种用来维护一组元素构成的结合S的数据结构,其中每个元素都有一个关键字key,元素之间的比较都是通过key来比较的。优先队列包括最大优先队列和最小优先队列,优先队列的应用比较广泛,比如作业系统中的调度程序,当一个作业完成后,需要在所有等待调度的作业中选择一个优先级最高的作业来执行,并且也可以添加一个新的作业到作业的优先队列中。优先队列的实现中,我们可以选择堆数据结构,最…

    2025年11月20日
    4
  • javascript静态变量

    javascript静态变量只提供获取变量的方法创建立即执行函数 创建一个独立的作用域 避免变量污染 varConf function varconf 静态变量习惯大写 MAX NUM 100 MIN NUM 1 COUNT 1000 return get function n

    2026年3月16日
    1
  • kettle工具使用简明手册[通俗易懂]

    kettle工具使用简明手册[通俗易懂]kettle工具使用简明手册运行启动脚本spoon.bat快捷方式如果正确启动,则出现的主界面应该是下面这样的。初次进入需右键新建作业新建数据库连接也可以想下面一样,新建数据库连接向导填完参数测试一下连接通过向导分步来我已经建立了转换任务和数据库连接,可以直接应用。 建立多表复制点击开始即可,多表的…

    2022年10月9日
    4
  • icem网格数和节点数_icem如何查看网格数量[通俗易懂]

    icem网格数和节点数_icem如何查看网格数量[通俗易懂]>减少总块数,加速求解关键:统一索引y/j索引空间索引空间x/i结构网格的索引与合并ICEM中块的合并Autodyn中网格的合并结构网格的索引与合并索引……网格的索引合并->减少总块数,加速求解关键:统一索引y/j索引空间索引空间x/i结构网格的索引与合并ICEM中块的合并Autodyn中网格的合并结构网格的……选择…

    2022年5月25日
    118
  • Tomcat遇到”Error listenerStart”或”Error filterStart”问题且无详细日志时的log配置….

    Tomcat遇到”Error listenerStart”或”Error filterStart”问题且无详细日志时的log配置….昨天部署web应用到Tomcat之后,无法成功启动,并且控制台没有详细的错误信息,顶多就两行提示信息,例如:严重:ErrorlistenerStart严重:Context[/lizongbo]startupfailedduetopreviouserrors或者严重:ErrorfilterStartorg.apache.catalina.core.StandardCo…

    2022年7月27日
    9
  • executorservice实例_java controller

    executorservice实例_java controllerExecutorService是Javajava.util.concurrent包的重要组成部分,是JavaJDK提供的框架,用于简化异步模式下任务的执行。一般来说,ExecutorService会自动提供一个线程池和相关API,用于为其分配任务。实例化ExecutorService实例化ExecutorService的方式有两种:一种是工厂方法,另一种是直接创建。Exec…

    2025年10月17日
    12

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号