Java 方法调用中的参数是值传递还是引用传递呢?相信每个做开发的同学都碰到过传这个问题,不光是做 Java 的同学,用 C#、Python 开发的同学同样肯定遇到过这个问题,而且很有可能不止一次。

那么,Java 中到底是值传递还是引用传递呢,答案是值传递,Java 中没有引用传递这个概念。

数据类型和内存分配

Java 中有可以概括为两大类数据类型,一类是基本类型,另一类是引用类型。

基本类型

byte、short、int、long、float、double、char、boolean 是 Java 中的八种基本类型。基本类型的内存分配在栈上完成,也就是 JVM 的虚拟机栈。也就是说,当你使用如下语句时:

int i = 89;

会在虚拟机栈上分配 4 个字节的空间出来存放。

引用类型

引用类型有类、接口、数组以及 null 。我们平时熟悉的各种自定义的实体类啊就在这个范畴里。

当我们定义一个对象并且使用 new 关键字来实例化对象时。

User user = new User();

会经历如下三个步骤:

1、声明一个引用变量 user,在虚拟机栈上分配空间;

2、使用 new 关键字创建对象实例,在堆上分配空间存放对象内的属性信息;

3、将堆上的对象链接到 user 变量上,所以栈上存储的实际上就是存的对象在堆上的地址信息;

数组对象也是一样的,栈上只是存了一个地址,指向堆上实际分配的数组空间,实际的值是存在堆上的。

为了清楚的展示空间分配,我画了一张类型空间分配的示例图。

没有争议的基本类型

当我们将 8 种基本类型作为方法参数传递时,没有争议,传的是什么(也就是实参),方法中接收的就是什么(也就是形参)。传递过去的是 1 ,那接到的就是1,传过去的是 true,接收到的也就是 true。

看下面这个例子,将变量 oldIntValue 传给 changeIntValue 方法,在方法内对参数值进行修改,最后输出的结果还是 1。

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 是按值传递。

数组和类

数组

有的同学说那不对呀,你看我下面这段代码,就不是这样。

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;
}

这段代码的输出是

1
100

说明调用 changeArrayValue 方法时,修改传过来的数组参数中的第一项后,原变量的内容改变了,那这怎么是值传递呢。

别急,看看下面这张图,展示了数组在 JVM 中的内存分配示例图。

实际上可以理解为 changeArrayValue 方法接收的参数是原变量 oldArray 的副本拷贝,只不过数组引用中存的只是指向堆中数组空间的首地址而已,所以,当调用 changeArrayValue 方法后,就形成了 oldArray 和 newArray 两个变量在栈中的引用地址都指向了同一个数组地址。所以修改参数的每个元素就相当于修改了原变量的元素。

一般我们在开发过程中有很多将类实例作为参数的情况,我们抽象出来的各种对象经常在方法间传递。比如我们定义了一个用户实体类。

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 实例当做参数传过来,处理完成后,再将它返回。

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 的属性也改变了,比如下面这样:

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 );
}

返回的结果都是下面这样

User{name='原始姓名', age=8}
User{name='新名字', age=18}

那这不就是引用传递吗,改了参数的属性,就改了原变量的属性。仍然来看一张图

实际上仍然不是引用传递,引用传递我们学习 C++ 的时候经常会用到,就是指针。而这里传递的其实是一个副本,副本中只存了指向堆空间对象实体的地址而已。我们我们修改参数 newUser 的属性间接的就是修改了原变量的属性。

有同学说,那画一张图说这样就是这样吗,你说是副本就是副本吗,我偏说就是传的引用,就是原变量,也说得通啊。

确实是说的通,如果真是引用传递,也确实是这样的效果没错。那我们就来个反例。

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 才对。

然后,我们运行看看输出结果:

User{name='原始姓名', age=8}
User{name='原始姓名', age=8}

结果依然是修改前的值,我们修改了 newUser ,并没有影响到原变量,显然不是引用传递。

结论

Java 中的参数传递是值传递,并且 Java 中没有引用传递这个概念。我们通常说的引用传递,一般都是从 C 语言和 C like 而来,因为它们有指针的概念。

而我们也知道,C、C++ 中需要程序员自己管理内存,而指针的使用经常会导致内存泄漏一类的问题,Java 千辛万苦的就是为了让程序员解放出来,而使用垃圾收集策略管理内存,这其中很重要的一点就是规避了指针的使用,所以在 Java 的世界中没有所谓的指针传递。

人在江湖,各位捧个赞场,轻轻点个推荐吧

难住了同事:Java 方法调用到底是传值还是传引用的更多相关文章

  1. 难道同事:Java 方法调用到底是传值还是传引用

    Java 方法调用中的参数是值传递还是引用传递呢?相信每个做开发的同学都碰到过传这个问题,不光是做 Java 的同学,用 C#.Python 开发的同学同样肯定遇到过这个问题,而且很有可能不止一次. ...

  2. JAVA方法传递参数:传值?传引用?

    先来看下面这三段代码: //Example1: public class Example1 { static void check(int a) { a++; } public static void ...

  3. Java:方法的参数是传值还是传引用

    Java中方法的参数总是采用传值的方式. 下列方法欲实现对象的交换,但实际上是不能实现的. public void swap(simpleClass a,simpleClass b){ simpleC ...

  4. 关于Java对象作为参数传递是传值还是传引用的问题

    前言 在Java中,当对象作为参数传递时,究竟传递的是对象的值,还是对象的引用,这是一个饱受争议的话题.若传的是值,那么函数接收的只是实参的一个副本,函数对形参的操作并不会对实参产生影响:若传的是引用 ...

  5. Java实参和形参与传值和传引用

    实参和形参的定义: 形参出现函数定义中,在整个函数体内都可以使用,离开函数则不能使用. 实参出现在主函数中,进入被调函数后,实参变量也不能使用. 形参和实参的功能是做数据传送.发生函数调用时,主调函数 ...

  6. Java中的值传递和地址传递(传值、传引用)

    首先,不要纠结于 Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其实本质上是传值”这种并不能解决问题无意义论战中.更何况,要想知道 ...

  7. Java经典问题:传值与传引用?

    转自:http://developer.51cto.com/art/201104/254715.htm Java到底是传值还是传引用?相信很少有人能完全回答正确.通常的说法是:对于基本数据类型(整型. ...

  8. JAVA方法调用中的解析与分派

    JAVA方法调用中的解析与分派 本文算是<深入理解JVM>的读书笔记,参考书中的相关代码示例,从字节码指令角度看看解析与分派的区别. 方法调用,其实就是要回答一个问题:JVM在执行一个方法 ...

  9. java方法调用之动态调用多态(重写override)的实现原理——方法表(三)

    上两篇篇博文讨论了java的重载(overload)与重写(override).静态分派与动态分派.这篇博文讨论下动态分派的实现方法,即多态override的实现原理. java方法调用之重载.重写的 ...

随机推荐

  1. 【Java杂货铺】JVM#Class类结构

    代码编译的结果从本地机器码转为字节码,是储存格式发展的一小步,却是编程语言的一大步.--<深入理解Java虚拟机> 计算机只认识0和1.所以我们写的编程语言只有转义成二进制本地机器码才能让 ...

  2. day10-time模块

    import time print(time.time()) #时间戳1553070877.0166008 print(time.strftime('%Y-%m-%d %H %M %S')) #201 ...

  3. gpio 的配置

    1.时钟使能 B引脚RCC时钟脉冲启动.|(或)表示只要第三个为1就行. 也可以写为RCC_APB2ENR  | = 0X0008; 2.配置GPIO的输出模式以及速度 3 . 如果是 0号引脚:GP ...

  4. php函数 之 iconv 不是php的默认函数,也是默认安装的模块。需要安装才能用的。

    windows下最近在做一个小偷程序,需要用到iconv函数把抓取来过的utf-8编码的页面转成gb2312, 发现只有用iconv函数把抓取过来的数据一转码数据就会无缘无故的少一些.  让我郁闷了好 ...

  5. discussion|局限性|解释|猜测|前作与同行

    讨论是整篇论文的精华和灵魂,考查作者的文献积累量和对所研究内容的理解深度,作者需要阐述为什么结果是重要的,内容包括理论.应用.在其他其他领域的作用及应用,阐述时要求直接明确. 具体而言,首先概述最重要 ...

  6. HTTP请求方法及常见状态码

    GET: 请求指定的页面信息,并返回实体主体. HEAD: 只请求页面的首部. POST: 请求服务器接受所指定的文档作为对所标识的URI的新的从属实体. PUT: 从客户端向服务器传送的数据取代指定 ...

  7. 数据库引擎MyiSAM和InnoDB区别

    数据库操作原理归根到底还是对文件操作,只不过是数据库文件. MySQL数据库引擎取决于MySQL在安装的时候是如何被编译的.要添加一个新的引擎,就必须重新编译MYSQL.在缺省情况下,MYSQL支持三 ...

  8. MOOC(7)- case依赖、读取json配置文件进行多个接口请求-封装mock(9)

    封装mock # -*- coding: utf-8 -*- # @Time : 2020/2/12 8:51 # @File : mock_demo_9.py # @Author: Hero Liu ...

  9. Scarpy框架安装教程

    在一切之前,建议升级pip,如果版本太低,安装会失败 升级pip命令: python -m pip install --upgrade pip 如果上面的命令不能用,用下面这个 easy_instal ...

  10. Shell字符串比较相等、不相等方法小结

    #!/bin/sh #测试各种字符串比较操作. #shell中对变量的值添加单引号,爽引号和不添加的区别:对类型来说是无关的,即不是添加了引号就变成了字符串类型, #单引号不对相关量进行替换,如不对$ ...