当前位置: 首页 > news >正文

Java笔记 —— 值传递与“引用传递”

在 Java 开发中,关于参数传递的讨论经久不衰。很多初学者(甚至一些有经验的开发者)都会困惑:Java 到底是值传递还是引用传递?本文将彻底澄清这个问题,通过代码示例和内存模型分析,证明一个事实:Java 只有值传递,不存在真正的“引用传递”。

一、什么是值传递?什么是引用传递?

在深入 Java 之前,我们先明确两个概念:

  • 值传递:方法调用时,实参将它的传递给形参。形参得到的是实参的副本,在方法内部对形参的修改不会影响实参本身。

  • 引用传递:方法调用时,实参将它的引用地址传递给形参。形参和实参指向同一个内存地址,对形参的修改会直接影响实参。

很多编程语言(如 C++)同时支持这两种方式,但Java 只支持值传递

二、Java 中的两种数据类型

要理解 Java 的传递机制,首先要了解 Java 中的两种数据类型:

  • 基本类型byteshortintlongfloatdoublecharboolean。变量本身存储的就是具体的数值。

  • 引用类型:类、接口、数组等。变量存储的是对象的内存地址(即引用),而不是对象本身。

三、基本类型的传递:值传递的直观体现

先看一个基本类型的例子:

public class PrimitiveTest { public static void main(String[] args) { int a = 10; System.out.println("调用前 a = " + a); // 10 change(a); System.out.println("调用后 a = " + a); // 10 } public static void change(int x) { x = 20; System.out.println("方法内 x = " + x); // 20 } }

输出:

调用前 a = 10 方法内 x = 20 调用后 a = 10

分析

  • a的值是 10。

  • 调用change(a)时,将a的值(10)复制一份给形参x

  • 在方法内部修改x为 20,仅仅改变了形参副本的值,a本身不受影响。

  • 这清晰地展示了值传递:传递的是值的副本。

四、引用类型的传递:表面看起来像引用传递,实则还是值传递

引用类型的例子更容易让人误解。先看代码:

public class ReferenceTest { public static void main(String[] args) { Person p = new Person("张三"); System.out.println("调用前姓名:" + p.getName()); // 张三 changeName(p); System.out.println("调用后姓名:" + p.getName()); // 李四 } public static void changeName(Person person) { person.setName("李四"); System.out.println("方法内姓名:" + person.getName()); // 李四 } } class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }

输出:

调用前姓名:张三 方法内姓名:李四 调用后姓名:李四

看起来方法内部修改了对象,外部也受到了影响,这难道不是引用传递吗?
不是!这里传递的依然是“值”,只不过这个值是对象的引用地址

内存分析

  1. Person p = new Person("张三");

    • 在堆内存中创建一个Person对象(地址假设为0x123)。

    • 栈中的变量p存储了这个地址0x123

  2. changeName(p);

    • p的值(地址0x123复制一份给形参person

    • 此时person也指向了同一个对象0x123

  3. person.setName("李四");

    • 通过person这个引用找到堆中的对象,修改其属性。

    • 因为pperson指向同一个对象,所以通过p访问时,看到的是修改后的内容。

关键点:传递的是引用的值(地址),而不是引用本身。形参和实参是两个不同的变量,但它们存储的地址值相同,所以操作的是同一个对象。如果尝试修改形参的指向,外部不会受影响:

public static void changeReference(Person person) { person = new Person("王五"); // 修改形参的引用 System.out.println("方法内姓名:" + person.getName()); // 王五 }

调用:

Person p = new Person("张三"); changeReference(p); System.out.println("调用后姓名:" + p.getName()); // 张三

输出:

方法内姓名:王五 调用后姓名:张三

可见,虽然形参person被重新赋值为一个新对象,但外部的p仍然指向原来的对象,因为形参只是实参的副本,改变副本的指向不会影响实参。

五、数组和 String 的特殊性

5.1 数组

数组也是引用类型,传递的是数组引用的副本:

public class ArrayTest { public static void main(String[] args) { int[] arr = {1, 2, 3}; changeArray(arr); System.out.println(arr[0]); // 100 } public static void changeArray(int[] a) { a[0] = 100; } }

因为aarr指向同一个数组对象,所以修改数组内容会反映到外部。

5.2 String 的不可变性

String 虽然是引用类型,但它是不可变的,且设计为常量池优化,可能会让人误解:

public class StringTest { public static void main(String[] args) { String s = "hello"; changeString(s); System.out.println(s); // hello } public static void changeString(String str) { str = "world"; } }

这里s仍然输出"hello"。因为str = "world"只是让形参指向了常量池中的另一个字符串,并没有改变实参s的引用。如果尝试修改 String 的内容(String 没有提供修改方法),就更不可能了。

六、常见误区与总结

误区一:“Java 对基本类型是值传递,对引用类型是引用传递”

这是错误的。Java 对所有类型都是值传递,区别仅在于:

  • 基本类型传递的是数据值。

  • 引用类型传递的是引用值(内存地址)。

误区二:“传递对象时,方法内修改对象属性会影响外部,所以是引用传递”

这混淆了“传递机制”和“操作结果”。即使形参和实参指向同一对象,修改对象属性是“通过引用操作对象”的必然结果,但不能因此认为参数传递方式是引用传递。引用传递要求形参和实参本身是同一个变量(即形参是实参的别名),而 Java 中形参只是实参的副本。

误区三:“C/C++ 中可以通过指针实现引用传递,Java 没有指针,所以只能值传递”

Java 的引用本质上就是受限的指针,但它不支持直接取地址或指针运算。参数传递时,引用的值被复制,因此是值传递。

七、总结

  1. Java 只有值传递,没有引用传递。

  2. 基本类型:传递的是数值本身,方法内修改形参不影响实参。

  3. 引用类型:传递的是引用的副本(地址值),方法内通过形参修改对象属性会影响实参指向的对象;但若修改形参的指向(如person = new Person()),不影响实参。

  4. 理解这一机制有助于写出更健壮的代码,避免因误以为“引用传递”而犯下的错误。

http://www.jsqmd.com/news/518762/

相关文章:

  • CPEditor刷题环境配置避坑指南:解决C++14语法不兼容问题
  • 02nginx配置文件
  • java毕业设计基于springboot音乐分享交流平台project60526
  • 895. 最长上升子序列(线性dp模版题2
  • Java笔记——数据类型(为什么商业计算必须用BigDecimal?)
  • Java笔记——包装类(自动拆装箱)
  • FatMouse‘s Speed(dp模版2 最长上升子序列
  • Python+PySpark+Hadoop图书推荐系统 图书可视化大屏 网上 图书个性化推荐系统 Django框架 可视化 协同过滤推荐算法
  • 金融级容灾标准:TDengine时序数据库实现分钟级RTO与秒级RPO的架构解析
  • 16 openclaw与数据库集成:ORM使用与性能优化
  • 基于vue的民族婚纱预订系统[vue]-计算机毕业设计源码+LW文档
  • 1010. 拦截导弹(dp模版二 最长上升子序列
  • 17 openclaw数据库连接池配置:避免性能瓶颈的关键
  • 好写作AI | 艺术类毕业创作说明文中AI辅助感性表达与理性论证的平衡
  • 基于python旅游景区数据分析可视化 热门旅游景点数据分析系统 可视化 Django框架
  • ABAQUS不规则线纤维投放插件及配套教程
  • 基于Hadoop和 spark招聘推荐系统+深度学习+推荐算法+爬虫可视化
  • 好写作AI | 医学类学位论文中AI辅助临床数据整理的精准度与伦理边界
  • 深度学习yolo26算法的智慧工地数据集 工地人员安全合规检测、施工区域风险识别、智能安防巡检、作业规范自动核查10599期
  • deepstream实战指南——环境搭建与依赖管理
  • 手把手教你用Makefile一键搞定NCVerilog与FineSim混合仿真(附完整脚本)
  • python基础学习笔记第九章——模块、包
  • (二)云端开发环境一站式部署:Miniconda3、GPU版PyTorch与PyCharm 2022远程调试及Jupyter Server配置实战
  • 基于Minio与Web Worker的现代前端大文件上传架构实践
  • 避坑指南:银河麒麟V10运行QT6时中文输入法崩溃的5个修复方案
  • hadoop+spark股票行情预测 量化交易分析 股票推荐系统 机器学习 随机森林算法 Python语言
  • Coze工作流实战:我把飞书多维表格变成了一个“第一人称视频”自动生产线
  • 好写作AI | 经管类毕业论文AI辅助案例分析框架构建的实践探索
  • 基于YOLOv8/YOLOv10/YOLOv11/YOLOv12与SpringBoot的车辆识别检测系统(DeepSeek智能分析+web交互界面+前后端分离+YOLO数据)
  • MaxViT多轴注意力机制详解:从理论到PyTorch实现