难住了同事:Java 方法调用到底是传值还是传引用

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

难住了同事:Java 方法调用到底是传值还是传引用

风的姿态   2020-03-05 我要评论
> **Java 方法调用中的参数是值传递还是引用传递呢?**相信每个做开发的同学都碰到过传这个问题,不光是做 Java 的同学,用 C#、Python 开发的同学同样肯定遇到过这个问题,而且很有可能不止一次。 > > 那么,Java 中到底是值传递还是引用传递呢,**答案是值传递,Java 中没有引用传递这个概念。** ### 数据类型和内存分配 Java 中有可以概括为两大类数据类型,一类是基本类型,另一类是引用类型。 **基本类型** byte、short、int、long、float、double、char、boolean 是 Java 中的八种基本类型。基本类型的内存分配在栈上完成,也就是 JVM 的虚拟机栈。也就是说,当你使用如下语句时: ```java int i = 89; ``` 会在虚拟机栈上分配 4 个字节的空间出来存放。 **引用类型** 引用类型有类、接口、数组以及 null 。我们平时熟悉的各种自定义的实体类啊就在这个范畴里。 当我们定义一个对象并且使用 new 关键字来实例化对象时。 ```java User user = new User(); ``` 会经历如下三个步骤: 1、声明一个引用变量 user,在虚拟机栈上分配空间; 2、使用 new 关键字创建对象实例,在堆上分配空间存放对象内的属性信息; 3、将堆上的对象链接到 user 变量上,所以栈上存储的实际上就是存的对象在堆上的地址信息; 数组对象也是一样的,栈上只是存了一个地址,指向堆上实际分配的数组空间,实际的值是存在堆上的。 为了清楚的展示空间分配,我画了一张类型空间分配的示例图。 ![](https://img2020.cnblogs.com/blog/273364/202003/273364-20200305110626395-2019826808.png) ### 没有争议的基本类型 当我们将 8 种基本类型作为方法参数传递时,没有争议,传的是什么(也就是实参),方法中接收的就是什么(也就是形参)。传递过去的是 1 ,那接到的就是1,传过去的是 true,接收到的也就是 true。 看下面这个例子,将变量 oldIntValue 传给 changeIntValue 方法,在方法内对参数值进行修改,最后输出的结果还是 1。 ```java public static void main( String[] args ) throws Exception{ int oldIntValue = 1; System.out.println( oldIntValue ); passByValueOrRef.changeIntValue( oldIntValue ); System.out.println( oldIntValue ); } public static void changeIntValue( int oldValue ){ int newValue = 100; oldValue = newValue; } ``` 改变参数值并不会改变原变量的值,没错吧,Java 是按值传递。 ### 数组和类 **数组** 有的同学说那不对呀,你看我下面这段代码,就不是这样。 ```java public static void main( String[] args ) throws Exception{ int[] oldArray = new int[] { 1, 2 }; System.out.println( oldArray[0] ); changeArrayValue( oldArray ); System.out.println( oldArray[0] ); } public static void changeArrayValue( int[] newArray ){ newArray[0] = 100; } ``` 这段代码的输出是 ```shell 1 100 ``` 说明调用 changeArrayValue 方法时,修改传过来的数组参数中的第一项后,原变量的内容改变了,那这怎么是值传递呢。 别急,看看下面这张图,展示了数组在 JVM 中的内存分配示例图。 ![](https://img2020.cnblogs.com/blog/273364/202003/273364-20200305110643673-430684053.png) 实际上可以理解为 changeArrayValue 方法接收的参数是原变量 oldArray 的副本拷贝,只不过数组引用中存的只是指向堆中数组空间的首地址而已,所以,当调用 changeArrayValue 方法后,就形成了 oldArray 和 newArray 两个变量在栈中的引用地址都指向了同一个数组地址。所以修改参数的每个元素就相当于修改了原变量的元素。 **类** 一般我们在开发过程中有很多将类实例作为参数的情况,我们抽象出来的各种对象经常在方法间传递。比如我们定义了一个用户实体类。 ```java public class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = 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 "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } } ``` 比方说我们有一个原始的实体 User 类对象,将这个实体对象传给一个方法,这个方法可能会有一些逻辑处理,比如我们拿到这个用户的 name 属性,发现 name 为空,我们就给 name 属性赋予一个随机名称,例如 “用户398988”。这应该是很常见的一类场景了。 我们通常这样使用,将 user 实例当做参数传过来,处理完成后,再将它返回。 ```java public static void main( String[] args ) throws Exception{ User oldUser = new User( "原始姓名", 8 ); System.out.println( oldUser.toString() ); oldUser = changeUserValue( oldUser ); System.out.println( oldUser.toString() ); } public static User changeUserValue( User newUser ){ newUser.setName( "新名字" ); newUser.setAge( 18 ); return newUser; } ``` 但有的同学说,我发现修改完成后就算不返回,原变量 oldUser 的属性也改变了,比如下面这样: ```java public static void main( String[] args ) throws Exception{ User oldUser = new User( "原始姓名", 8 ); System.out.println( oldUser.toString() ); changeUserValue( oldUser ); System.out.println( oldUser.toString() ); } public static void changeUserValue( User newUser ){ newUser.setName( "新名字" ); newUser.setAge( 18 ); } ``` 返回的结果都是下面这样 ```json User{name='原始姓名', age=8} User{name='新名字', age=18} ``` 那这不就是引用传递吗,改了参数的属性,就改了原变量的属性。仍然来看一张图 ![](https://img2020.cnblogs.com/blog/273364/202003/273364-20200305110710417-1437029179.png) 实际上仍然不是引用传递,引用传递我们学习 C++ 的时候经常会用到,就是指针。而这里传递的其实是一个副本,副本中只存了指向堆空间对象实体的地址而已。我们我们修改参数 newUser 的属性间接的就是修改了原变量的属性。 有同学说,那画一张图说这样就是这样吗,你说是副本就是副本吗,我偏说就是传的引用,就是原变量,也说得通啊。 确实是说的通,如果真是引用传递,也确实是这样的效果没错。那我们就来个反例。 ```java public static void main( String[] args ) throws Exception{ User oldUser = new User( "原始姓名", 8 ); System.out.println( oldUser.toString() ); wantChangeUser( oldUser ); System.out.println( oldUser.toString() ); } public static void wantChangeUser( User newUser ){ newUser = new User( "新姓名", 18 ); } ``` 假设就是引用传递,那么 newUser 和 main 方法中的 oldUser 就是同一个引用对象,那我在 wantChangeUser 方法中重新 new 了一个 User 实体,并赋值给了 newUser,按照引用传递这个说法,我赋值给了参数也就是赋值给了原始变量,那么当完成赋值操作后,原变量 oldUser 就应该是 name = "新名字"、age=18 才对。 然后,我们运行看看输出结果: ```json User{name='原始姓名', age=8} User{name='原始姓名', age=8} ``` 结果依然是修改前的值,我们修改了 newUser ,并没有影响到原变量,显然不是引用传递。 ### 结论 Java 中的参数传递是值传递,并且 Java 中没有引用传递这个概念。我们通常说的引用传递,一般都是从 C 语言和 C like 而来,因为它们有指针的概念。 而我们也知道,C、C++ 中需要程序员自己管理内存,而指针的使用经常会导致内存泄漏一类的问题,Java 千辛万苦的就是为了让程序员解放出来,而使用垃圾收集策略管理内存,这其中很重要的一点就是规避了指针的使用,所以在 Java 的世界中没有所谓的指针传递。 > 人在江湖,各位捧个赞场,轻轻点个推荐吧

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们