Java的参数传递是「按值传递」还是「按引用传递」?

Java 编程语言中最大的困惑之一就是: java 是按值传递还是按引用传递。我在面试中经常会问面试者这个问题,但还是有很多面试者对这个问题的理解不是很正确。

有很多面试者是这样理解的:

  • 如果传递类型为基础数据类型,则按值传递,
  • 如果传递类型为类,则按引用传递。

这样的理解正确吗?他们甚至还可以写出示例代码来验证他们的想法,让我们来一起看一看大多数人是如何验证“基础类型按值传递,非基础类型按引用传递”这个想法的:

基础类型数据作为参数传递

/**
 * 基础类型数据作为参数传递
 * @Author: danding
 * @Date: 2019/11/5
 */
public class TestParams {

    public static void main(String[] args){
        int x = 6;
        System.out.println("x的初始值为:" + x);
        add(x);
        System.out.println("x的最终值为:" + x);
    }

    public static void add(int x){
        x = x + 1;
        System.out.println("add 方法中的x值为:" + x);
    }

}

运行结果:

x的初始值为:6
add 方法中的x值为:7
x的最终值为:6

非基础类型作为参数传递
首先我们定义一个类

/**
 * 定义一个女朋友的类
 * (简陋了点,只有年龄,但不影响我们使用呀)
 * @Author: danding
 * @Date: 2019/11/5
 */
public class GrilFriend {
    private int age;

    public GrilFriend(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

然后我们将创建实例并作为参数传递

/**
 * 基础类型数据作为参数传递
 * @Author: danding
 * @Date: 2019/11/5
 */
public class TestParams {

    public static void main(String[] args){
        GrilFriend gril = new GrilFriend(18);
        System.out.println("女朋友的初始年龄为:" + gril.getAge());
        add(gril);
        System.out.println("女朋友的最终年龄为:" + gril.getAge());
    }

    private static void add(GrilFriend friend){
        friend.setAge(friend.getAge()+1);
        System.out.println("女朋友在方法中的年龄为:" + friend.getAge());
    }

}

运行结果:

女朋友的初始年龄为:18
女朋友在方法中的年龄为:19
女朋友的最终年龄为:19

非基础类型作为参数传递时,值的确被修改了。

这个时候很多同学通过以上两个示例验证,自己就得出了自己的结论:

如果传递类型为基础数据类型,则按值传递,否则为按引用传递。

在此说明,这个理解是错误的,错误的,错误的。下面我们就来说说 Java中的参数传递到底是按值传递还是按引用传递?

首先说下正确的答案:Java 的参数传递,不管是基本数据类型还是引用类型的参数,都是按值传递,没有按引用传递!

首先,我们应该了解按值传递或按引用传递的含义。

  • 按值传递:将方法参数值复制到另一个变量,然后传递复制的对象,将其称为按值传递。
  • 按引用传递:将对实际参数的别名或引用传递给方法,将其称为按引用传递的原因。

你个糟老头子坏得狠,我信你个鬼,你这个解释给我要给差评.....

且听老夫(哦,不,是小编)慢慢道来...

当一个对象被当作参数传递到一个方法后,在此方法内可以改变这个对象的属性,那么这里到底是「按值传递」还是「按引用传递」?

答:是按值传递。Java 语言的参数传递只有「按值传递」。当一个实例对象作为参数被传递到方法中时,参数的值就是该对象的引用的一个副本。指向同一个对象,对象的内容可以在被调用的方法内改变,但对象的引用(不是引用的副本) 是永远不会改变的。

基础类型参数传递

这个上面的示例已经验证了,为按值传递,这个大家应该不会有什么异议。

非基础类型参数传递

我们重点来说下对象类型作为参数传递

先来看一下传递的例子:

public class TestParams {

    public static void main(String[] args){
        Person p1 = new Person();
        System.out.println(p1);
        change(p1);
        System.out.println(p1);
    }

    private static void change(Person p2){
        p2 = new Person();
    }

}

class Person{

}

运行结果

[email protected]
[email protected]

可以看出两次打印person的地址值是一样的,即调用完change() 方法之后,person变量并没有发生改变。

这个传递过程的示意图如下:

当执行到Person p1 = new Person();代码时,程序在堆内存中开辟了一块内存空间用来存储Person类的实例对象,同时在栈内存中开辟了一个存储单元用来存储该实例对象的引用,即上图中person指向的存储单元。

当执行到change(p1);代码时,person作为参数传递给change()方法,需要注意的是:person将自己存储单元的内容传递给了change()方法的p2变量!此后,在change()方法中对p2的一切操作都是针对p2所指向的存储单元,与person所指向的那个存储单元没有关系了!

这个时候该有同学说了,那上面那个女朋友示例中,女朋友的年龄不是被在方法中修改了吗?如果传递的是副本那不应该修改不了女朋友的年龄吗?

如果我们将女朋友中的代码放到内存示例图中走一遍,你应该就明白其中的道理了。
所谓引用副本,但其所指向的还是真实的对象,所以修改的还是真实对象上的属性。

我希望上面的解释能消除所有疑问,只需要记住Java 的参数传递,不管是基本数据类型还是引用类型的参数,都是按值传递,没有按引用传递!。当您将了解堆空间和栈内存以及存储不同对象和引用的位置时,将会更加清楚,有关程序的详细说明,请阅读 Java Heap vs Stack


“不积跬步,无以至千里”,希望未来的你能:有梦为马 随处可栖!加油,少年!

关注公众号:「Java 知己」,每天更新Java知识哦,期待你的到来!

  • 发送「Group」,与 10 万程序员一起进步。
  • 发送「面试」,领取BATJ面试资料、面试视频攻略。
  • 发送「玩转算法」,领取《玩转算法》系列视频教程。
  • 千万不要发送「1024」...

原文地址:https://www.cnblogs.com/java-friend/p/11797656.html

时间: 2024-08-28 08:51:55

Java的参数传递是「按值传递」还是「按引用传递」?的相关文章

「七天自制PHP框架」第三天:PHP实现的设计模式

往期回顾:「七天自制PHP框架」第二天:模型与数据库,点击此处 原文地址:http://www.cnblogs.com/sweng/p/6624845.html,欢迎关注:编程老头 为什么要使用设计模式? 设计模式,我的理解是为了达到"可复用"这个目标,而设计的一套相互协作的类. 感兴趣的读者可以阅读<Design Patterns: Elements of Reusable Object-Oriented Software>,四位作者(Gang of Four)在书中列举

「Rancher社区技术支持计划」全面启动

2015年6月 Rancher Labs第一次推出原始测试版Rancher 2016年3月 开源的全栈化容器管理平台Rancher正式版发布 600多个日夜 Rancher推出了共计569个版本 在全球范围内下载量超过1900万次 愈发庞大的开源社区伙伴队伍 愈发频繁的迭代和新功能发布 我们为之欣喜 也生怕因有限的精力 而无法给所有用户更好的技术支持 为了为Rancher用户创造更好的使用体验 为了回应您提出的每一个问题 解决您遇见的每一个故障 重视您发现的每一个bug Rancher Labs

「Mobile Testing Summit China 2016」 中国移动互联网测试大会-议题征集

时至北京盛夏,一场由 TesterHome 主办的关于移动互联网测试技术的盛会正在紧锣密鼓的筹备中.只要你关注软件质量,热爱测试,期待学习,都欢迎你加入这次移动测试技术大会中和我们一起分享经验.探讨话题,结识业界朋友. 「Mobile Testing Summit China 2016」中国移动互联网测试大会 大会定位:专注移动互联网测试技术的分享会,关注移动互联网质量的有志之士的集会. 大会主旨:秉承着务实.能落地.有深度.高质量.重分享的原则与广大测试工程师做最新最实用的分享与交流,以推广新

Java的参数传递问题

Java中方法的参数传递方式是什么? 相信不少人会认为Java和C是一样的:当参数是基本数据类型时采用值传递而参数是非基本数据类型是采用引用传递即地址传递.真的是这样吗? 先看段代码: public class Snippet { public static void main(String[] args) { MyObj obj = new MyObj(); System.out.println(obj.age); process(obj); System.out.println(obj.ag

Linux 小知识翻译 - 「Linux和CPU的兼容性」

Linux刚开始是作为可运行在 Intel 的 「i386」CPU上,与POSIX兼容的内核来开发的. 而现在主流的Linux是指能在所谓「PC」上运行的内核.「PC」是指采用「IA(intel架构)」的机器. IA(Intel Architecture)虽然「I」是指Intel,但Linux并不是只能运行在Intel生产的CPU上.比如另一个非常大CPU生产商AMD所生产的兼容IA的CPU, 还有其他很多以省电为目的的兼容IA的CPU,这些CPU都可以被称作「IA」. 因此,Linux也就有可

读薄「Linux 内核设计与实现」(1) - 从内核出发

这篇文章是<读薄「Linux 内核设计与实现」>系列文章的第一篇,本文主要讲了两个问题:内核编程的特点以及 GNU C 在内核开发中的特点. 0x00 内核编程特点 无 libc 库,不能访问标准 C 文件 使用 GNU C 无内存保护机制 慎用浮点数计算 注意同步和并发 可移植性考虑:保持字节顺序.64位对齐.不假定字长和页面长度 0x01 GNU C 内联函数:将函数展开至调用位置,省却函数调用代价 内联汇编:在确定体系结构的情况下,在 C 代码中直接嵌入汇编代码 分支声明:分支时可根据预

读薄「Linux 内核设计与实现」(2) - 进程管理和调度

这篇文章是<读薄「Linux 内核设计与实现」>系列文章的第 II 篇,本文主要讲了以下问题:进程管理的任务.进程管理与其他模块的依赖关系.进程描述符和任务队列.进程的创建.线程的实现.进程的终止.进程调度. 0x00 进程管理的任务 进程能创建新的进程(通过复制现有进程) 确定哪个进程能拥有 CPU 接受中断并将中断导向相应的内核子系统 管理时钟硬件 当一个进程结束时释放其资源 动态装载执行模块 0x01 进程管理与其他模块的依赖关系 I 进程模块的内外界面 对用户进程提供了一组简单的系统调

Linux 小知识翻译 - 「Linux」和「发行版」之间的关系

「Linux」本来指的仅仅是内核.5年之前大多都是这么认为的,但是最近不这么说了. 最近一般都说「Linux」是个 OS,这里的OS,不仅仅是内核,而是指电脑的整体环境(除了内核,还包括一些外围的软件). 内核本来是作为硬件和各种应用软件之间的桥梁而存在的,只有内核的PC是无法使用的. 因此,会将各式各样的软件和内核组合在一起,作为一个可以运行的OS来打包,打包后的OS就被称为「Linux发行版」. 最近,把「Linux发行版」称为「Linux」的情况也比较多了. 但是,「Linux内核」只有一

Linux 小知识翻译 - 「Unix」和「兼容Unix的OS」

经常有人会问「Linux和Unix有什么区别?」,「Linux就是Unix吗?」. 回答一般都是「Linux是仿照Unix而开发的OS」,「Linux和Unix相似但不是一种OS」之类的. 关于「Linux和Unix」,这其中还有些故事. 首先,「Unix」是1970年左右由美国AT&T公司的贝尔实验室开发,并将开发推进下去而形成的OS. 现在,「Unix」这个商标由The Open Group所拥有,因此可以说「Linux不是Unix」. 另一方面,还有一个「Linux不是Unix」的理由.1