天真,居然还有人认为java的参数传递方式是引用传递

java 专栏收录该内容
47 篇文章 0 订阅

代码写的越急,程序跑得越慢。—— Roy Carlson

时间过得真快,2020已经过去了一半,但是疫情好像还没有真正的消灭,人们出行还是得带着口罩,天气越来越热,受罪啊。

言归正传,都2020年了,居然还有人认为java的参数传递方式是引用传递,今天我就来讲一讲java的参数传递,好好看,写的不对的地方,请大声说出来,反正我也不会改,憋坏了就不好了

基本数据类型传递

我们先来看一个普通的例子

package com.ymy.param;

/**
 * @ProjectName: demo
 * @Package: com.ymy.param
 * @ClassName: BaseTypeTest
 * @Author: 流星007
 * @Description: 基本数据类型传递
 * csdn:https://blog.csdn.net/qq_33220089
 * 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
 * @Date: 2020/7/5 12:52
 * @Version: 1.0
 */
public class BaseTypeTest {

    public static void main(String[] args) {
        int a = 1;
        dosomthing(a);
        System.out.println("主函数a的值 = "+a);

    }

    private static void dosomthing(int a) {
        a = a-1;
        System.out.println("修改过后,a = "+a);

    }
}

这是一个很简单的一个方法,在主函数main中对变量进行了初始化a=1,然后将a传递给dosomthing(),然后再dosomthing中输出了修改之后的值,最后在主函数中打印a的值,你们觉得这几句输出中a的值分别是多少呢?

第一种:
修改过后,a = 0
主函数a的值 = 1
第二种:
修改过后,a = 0
主函数a的值 = 0
第三种:
修改过后,a = 1
主函数a的值 = 1

想要得到答案的话就得先明白参数传递的两个类型:值传递和引用传递。

什么是引用传递?
在C++中,函数参数的传递方式有引用传递。所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

什么是值传递?
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

我们再回过头来看上面的例子,如果是引用传递的话打印结果应该是第二种情况,如果是值传递,打印结果应该是第一种情况,所以到底打印的结果是什么呢?

我们一起看一看控制台输出

Connected to the target VM, address: '127.0.0.1:59333', transport: 'socket'
修改过后,a = 0
主函数a的值 = 1
Disconnected from the target VM, address: '127.0.0.1:59333', transport: 'socket'

Process finished with exit code 0

这就是第一种情况,很明显,在dosomthing函数中修改了a的值,但是主函数中的a并没有受到影响,所以肯定不会是引用传递,如果是引用传递,主函数的a应该会变成0,只有在参数传递的时候将主函数的中参数复制一份给dosomthing,才能在dosomthing中修改a不会对主函数造成影响,所以从基本数据类型来看,java的参数传递方式为:值传递

这个时候你可能会有疑问了,这只是基本数据类型的传递方式,其他的参数类型呢?下面我们一起来看看引用类型和对象类型的传递方式。

follow me !!!!!

引用类型传递

我们都知道java中的String类型不属于基本数据类型,它是一个引用类型,也可以说是一个对象,那么它的传递方式是什么呢?

我们还是先来看例子

package com.ymy.param;

/**
 * @ProjectName: demo
 * @Package: com.ymy.param
 * @ClassName: StringTypeTest
 * @Author: 流星007
 * @Description: String类型传递
 * csdn:https://blog.csdn.net/qq_33220089
 * 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
 * @Date: 2020/7/5 14:22
 * @Version: 1.0
 */
public class StringTypeTest {

    public static void main(String[] args) {
        String a = "hello";
        dosomthing(a);
        System.out.println("主函数a的值 = "+a);

    }

    private static void dosomthing(String a) {
        a = a+" bug";
        System.out.println("修改过后,a = "+a);

    }

}

打印结果

修改过后,a = hello bug
主函数a的值 = hello

Process finished with exit code 0

我们发现主函数的a并没有受到dosomthing函数的影响,所以这并不是引用传递,这个时候你说是因为
a = a+" bug";这行代码生成了新的对象,所以才会导致数据不一致,我们先来看看a的赋值情况吧

// class version 52.0 (52)
// access flags 0x21
public class com/ymy/param/StringTypeTest {

  // compiled from: StringTypeTest.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 14 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/ymy/param/StringTypeTest; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
   L0
    LINENUMBER 17 L0
    LDC "hello"
    ASTORE 1
   L1
    LINENUMBER 18 L1
    ALOAD 1
    INVOKESTATIC com/ymy/param/StringTypeTest.dosomthing (Ljava/lang/String;)V
   L2
    LINENUMBER 19 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "\u4e3b\u51fd\u6570a\u7684\u503c = "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 21 L3
    RETURN
   L4
    LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
    LOCALVARIABLE a Ljava/lang/String; L1 L4 1
    MAXSTACK = 3
    MAXLOCALS = 2

  // access flags 0xA
  private static dosomthing(Ljava/lang/String;)V
    // parameter  a
   L0
    LINENUMBER 24 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC " bug"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 0
   L1
    LINENUMBER 25 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "\u4fee\u6539\u8fc7\u540e\uff0ca = "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 27 L2
    RETURN
   L3
    LOCALVARIABLE a Ljava/lang/String; L0 L3 0
    MAXSTACK = 3
    MAXLOCALS = 1
}

这是上面代码的字节码代码,我们可以清楚的看到a在赋值的时候都调用了StringBuilder的同String方法,现在我们来看看这个神奇的同String方法。

@Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

这是StringBuilder中的toString方法,确实是new了一个新的对象,就算是a变成了一个新的对象,如果是引用传递,主函数的a就不会受影响吗?这点我会讲完对象类型传递之后在进行讲解,请继续往下看。

对象类型传递

其实上面的两种其实很好区分,很多人都知道是值传递,很多人说java的传递方式是引用传递的原因就是出自这里:传递的参数为对象

有些人看到对象传递的时候会改变主函数的值,就认为java的参数传递是引用传递,有些人又因为基本数据类型不会队主函数的值造成修改,所以他们的结论是:基本数据类型为值传递;对象类型为引用传递,想法很好,那我们现在一起来解开对象传递的神秘面纱,它到底是引用传递还是值传递呢?

go go go !!!!

还是老规矩,我们一起来看一个例子

package com.ymy.param.vo;

/**
 * @ProjectName: demo
 * @Package: com.ymy.param.vo
 * @ClassName: LolVo
 * @Author: 流星007
 * @Description: lol英雄属性
 * csdn:https://blog.csdn.net/qq_33220089
 * 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
 * @Date: 2020/7/5 15:11
 * @Version: 1.0
 */
public class LolVo {

    /**
     * 姓名
     */
    private String name;

    /**
     * 职业
     */
    private String profession;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getProfession() {
        return profession;
    }

    public void setProfession(String profession) {
        this.profession = profession;
    }

    @Override
    public String toString() {
        return "LolVo{" +
                "name='" + name + '\'' +
                ", profession='" + profession + '\'' +
                '}';
    }
}

package com.ymy.param;

import com.ymy.param.vo.LolVo;

/**
 * @ProjectName: demo
 * @Package: com.ymy.param
 * @ClassName: ObjectTypeTest
 * @Author: 流星007
 * @Description: 对象类型传递
 * csdn:https://blog.csdn.net/qq_33220089
 * 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
 * @Date: 2020/7/5 15:16
 * @Version: 1.0
 */
public class ObjectTypeTest {

    public static void main(String[] args) {
        LolVo lolVo = new LolVo();
        lolVo.setName("无极剑圣");
        lolVo.setProfession("刺客");
        dosomthing(lolVo);
        System.out.println("主函数 lolVo = "+lolVo);

    }

    private static void dosomthing(LolVo lolVo) {
        lolVo.setProfession("战士");
        System.out.println("dosomthing lolVo = "+lolVo);

    }


}

结果如下:

dosomthing lolVo = LolVo{name='无极剑圣', profession='战士'}
主函数 lolVo = LolVo{name='无极剑圣', profession='战士'}

Process finished with exit code 0

主函数中剑圣的职业是刺客,在dosomthing中将他修改成,我们发现主函数中剑圣的职业也被修改成战士了,显然这符合引用传递的条件,被调用方修改会影响到调用方也就是主函数,所以这个时候很多人就认为java的对象传递的方式为引用传递,如果你也是这么认为,那么你就要认真看一下我后面的分析。

我们先来一个否定它是引用传递的例子,请看好,不要眨眼

package com.ymy.param;

import com.ymy.param.vo.LolVo;

/**
 * @ProjectName: demo
 * @Package: com.ymy.param
 * @ClassName: ObjectTypeTest
 * @Author: 流星007
 * @Description: 对象类型传递
 * csdn:https://blog.csdn.net/qq_33220089
 * 今日头条:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523
 * @Date: 2020/7/5 15:16
 * @Version: 1.0
 */
public class ObjectTypeTest {

    public static void main(String[] args) {
        LolVo lolVo = new LolVo();
        lolVo.setName("无极剑圣");
        lolVo.setProfession("刺客");
        dosomthing(lolVo);
        System.out.println("主函数 lolVo = "+lolVo);

    }

    private static void dosomthing(LolVo lolVo) {
        lolVo = new LolVo();
        lolVo.setProfession("战士");
        System.out.println("dosomthing lolVo = "+lolVo);

    }


}

做了小小的改动,仅仅只是在dosomthing方法中加了一行代码:lolVo = new LolVo();

我们再来看运行结果是什么呢?还会和上面一样吗?

dosomthing lolVo = LolVo{name='null', profession='战士'}
主函数 lolVo = LolVo{name='无极剑圣', profession='刺客'}

Process finished with exit code 0

我们发现主函数中剑圣的属性变回了刺客,并没有受到dosomthing函数的影响,如果是引用传递的话,主函数中剑圣的职业应该是战士而不是刺客。这是为什么呢?为什么是应用传递主函数中剑圣的职业因该是战士呢?

下面我们一起来分析一波

我们假设对象的传递方式为引用传递
在这里插入图片描述
这是堆栈中的信息,当我们将对象lolVo传递给dosomthing的时候,是克隆了一个对象出来还是原来的那个对象呢?我们知道,不管传递的是不是它本身,值都是内存的地址引用,我们现在先假设主函数没有复制,是直接将lolVo传递给了dosomthing,那图形应该是什么样的呢?
在这里插入图片描述
如果是引用传递,格式是不是应该是这样呢?main主函数和dosomthing函数公用一个lolVo,这个时候我们在dosomthing函数中执行了一句:lolVo = new LolVo();
那又会变成什么样呢?
在这里插入图片描述
在dosomthing方法中我们new了一个新的LolVo对象,并且将这个新的对象赋值给了lolVo,那是不是代表着main主函数核dosomthing函数中的lolVo应该是一样的呢,我们再来回顾一下上面的代码

private static void dosomthing(LolVo lolVo) {
        lolVo = new LolVo();
        lolVo.setProfession("战士");
        System.out.println("dosomthing lolVo = "+lolVo);

    }

我们做了修改之后,主函数剑圣的职业并没有修改成战士,所以,说java是引用传递是说不通的,闹我们再来看看它正确的流程应该是什么样的呢?

在这里插入图片描述
尽管dosomthing对参数的修改会影响调用方,但是它还是属于值传递,会影响调用方是因为java转递的时候拷贝的是对象的引用地址。

举个栗子:比如某公司开发了一套员工的内部管理系统,有一个管理员的账号,你把这个账号给了你的同事,他直接使用你这个账号,这就是引用传递,如果你是在用户管理中添加了一条管理员的用户,再将这个账号给你的同事,这就是值传递,还需要解释一下,为什么值传递会影响调用方,如果你给痛的账号,你同事改了用户名称,这对你是不是没有影响,但如果他手抖把员工全删了,你这边还能看到员工信息吗?就是这个道理,lolVo引用地址相当于管理员账号,系统内的功能相当于LolVo对象,你修改自己的账号对别人当然没有影响,但是你把系统搞没了,你觉得有影响吗?这就是为什么java的参数传递方式为值传递却能影响调用方。

总结

文章发布了一会就有人说java的参数传递方式是引用传递,我呢,如果你说的是对的,那我会虚心接受,所以我就认为我的讲解除了问题,然后我就去oracle官方找了一波,官方给出的也是值传递。
在这里插入图片描述
英文看不懂?那就右击翻译成中文
在这里插入图片描述

官方demo地址:https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html
如果还有人和你说java的参数传递是引用传递的话,请他来看一下我这篇博客,我把他劝退一下。
在这里插入图片描述

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值