java值传递与引用传递

最近在学习feign时,看了一些源码,其中有一段涉及到==的代码,于是复习了一下java中值传递和值引用的知识点
代码如下:

IClientConfig getClientConfig(Request.Options options, String clientName) {
        IClientConfig requestConfig;
        if (options == DEFAULT_OPTIONS) {
            requestConfig = this.clientFactory.getClientConfig(clientName);
        }
        else {
            requestConfig = new FeignOptionsClientConfig(options);
        }
        return requestConfig;
    }

注意在if中开发人员使用“==”对两个对象进行比较,这里我比较费解,options是一个参数配置对象,里面记录的是一些参数配置,那么比较的应该是其内容才对。而实际比较其地址值,因此我先模拟传参过程,观察对象在传参过程中的变化
模拟代码如下:

public class MyTest {
    private static final Person person = new Person("李四",20);

    public void testOne(Person p){
        System.out.println("入参p:" + p);

        p = new Person("张三",18);

        System.out.println("重新赋值后p:" + p);
    }

    public static void main(String[] args) {
        MyTest m = new MyTest();

        System.out.println("执行testOne前:" + person);

        m.testOne(person);

        System.out.println("执行testOne后:" + person);
    }
    

    public static class Person{
        private String name;
        private Integer age;

        public Person() {
        }

        public Person(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        //省略get set

打印结果如下:

执行testOne前:Person{name='李四', age=20}
入参p:Person{name='李四', age=20}
重新赋值后p:Person{name='张三', age=18}
执行testOne后:Person{name='李四', age=20}

根据打印结果可以得出以下结论:
1.person对象在进入testOne()方法前后,对象没有发生变化
2.person对象作为参数传递到testOne()方法内部后,person对象作为参数发生了变化
可能你认为private static final Person person = new Person("李四",20);这里的person对象使用final修饰的所以不会发生变化,那么我们不使用final修饰进行测试:

public class MyTest {
    private Person person = new Person("李四",20);

    public void testOne(Person p){
        System.out.println("入参p:" + p);

        p = new Person("张三",18);

        System.out.println("重新赋值后p:" + p);
    }

    public static void main(String[] args) {
        MyTest m = new MyTest();

        System.out.println("执行testOne前:" + m.getPerson());

        m.testOne(m.getPerson());

        System.out.println("执行testOne后:" + m.getPerson());
    }

结果如下:

执行testOne前:Person{name='李四', age=20}
入参p:Person{name='李四', age=20}
重新赋值后p:Person{name='张三', age=18}
执行testOne后:Person{name='李四', age=20}

可以看到得到的结论是一样的。那么为什么会有这样的结果呢,这就需要读者具备一定的java基础知识,了解java对象是如何在内存中存储的。简单来说,对于引用数据类型,也就是对象来说,初始化一个对象,比如:

Person person = new Person("李四",20);

创建对象时,会在栈中创建变量person,在堆中初始化对象,栈中person变量持有对堆中("李四",20)对象的引用,也就是我们常说的,变量持有地址值。使用变量person,就可以获取到堆中对象的内容name,age。那么当变量person被当做参数传递进一个方法后会发生什么,传递的内容是什么,我们用两张图来表示:
这是new对象时:


image.png

这是对象作为参数传入方法时:


image.png

当对象作为参数传入方法时,实际传入的是person对象的一个副本,传入方法的是对与对象的引用,因此你可以看到当参数进入方法时,打印出来的与传入前是一致的,因为他们持有相同的引用,获取的内容是相同的
执行testOne前:Person{name='李四', age=20}
入参p:Person{name='李四', age=20}

此时对变量进行的set,get操作,实际上是对变量持有引用的对象进行操作,比如这时候去修改对象内部属性值:

    public void testTwo(Person p){
        System.out.println("入参p:" + p);

        p.setName("王二麻子");
        p.setAge(25);

        System.out.println("重新赋值后p:" + p);
    }

    public static void main(String[] args) {
        MyTest m = new MyTest();

        System.out.println("执行testOne前:" + m.getPerson());

//        m.testOne(m.getPerson());
        m.testTwo(m.getPerson());

        System.out.println("执行testOne后:" + m.getPerson());
    }

结果如下:

执行testOne前:Person{name='李四', age=20}
入参p:Person{name='李四', age=20}
重新赋值后p:Person{name='王二麻子', age=25}
执行testOne后:Person{name='王二麻子', age=25}

但是为什么之前对p重新赋值并没有效果呢?因为传入person后,执行了

p = new Person("张三",18);

这段代码在内存中的效果如图所示


image.png

于是我们看到了测试的结果,在方法内部打印person,对象属性已经发生了变化,在方法外部打印person,没有改变原来的person对象,因为一旦内部new了新的对象,person副本持有的引用发生了变化,与原对象的引用不再有任何关系,而方法结束时,person副本消亡,并没有对原对象进行任何操作。

执行testOne前:Person{name='李四', age=20}
入参p:Person{name='李四', age=20}
重新赋值后p:Person{name='张三', age=18}
执行testOne后:Person{name='李四', age=20}

需要注意的时,这里person对象作为参数传入了方法,实际传入的是一个person对象的副本,他持有堆中对于person对象的引用,注意在没有改变这个引用前,对于person的操作,比如get,set仍旧是对原对象的内容进行操作。一旦这个引用发生了变化,对原对象就不会有影响了,这也正是上面两个测试方法产生不同结果的原因,我们可以得出结论当一个对象作为参数传入方法时,实际传递的是对于对象的引用,可以理解为引用传递,那么java中的值传递又是什么呢?我们使用基本数据类型int测试
测试代码:

    public void testThree(int a){
        System.out.println("入参a:" + a);

        a = 20;

        System.out.println("重新赋值后a:" + a);
    }
    public static void main(String[] args) {
        MyTest m = new MyTest();

        int  b = 100;
        System.out.println("执行testThree前:" + b);
        m.testThree(b);
        System.out.println("执行testThree后:" + b);

    }

结果如下:

执行testThree前:100
入参a:100
重新赋值后a:20
执行testThree后:100

其实结论与上面相似,传入的是副本,方法内部改变的是副本值,方法结束,副本消亡,原来的变量的值不受影响,我一位同事用一句话来总结,方法只能改变方法内部的东西,外部的是没有办法改变的

《java编程思想》中有一句话描述引用传递很有意思。解释一下:文中的句柄说的就是引用
“12.2 制作本地副本
稍微总结一下:Java中的所有自变量或参数传递都是通过传递句柄进行的。也就是说,当我们传递“一个对象”时,实际传递的只是指向位于方法外部的那个对象的“一个句柄”。所以一旦要对那个句柄进行任何修改,便相当于修改外部对象。”

同时《java核心技术卷I中》中也指出,java中任何传递都是按值传递的,只不过基本数据类型传递的是数值,而引用数据类型传递的是地址值。

在java核心技术卷I中也对此有描述,内容如下:
“按值调用表示方法接受到的是调用者提供的值。按引用调用表示方法接受到的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值”

事实上java核心技术卷I对值传递的描述就非常准确了,他直接指出了,值传递与引用传递的本质区别,即在方法内部,对变量的影响

总结一下

  1. java中对于基本数据类型都是值传递,传递的是一个副本值,副本在方法执行时生效,方法结束,副本消亡,原来的变量不会发生变化
  2. java中对于引用数据类型,传递的是其变量的副本,该副本持有与原变量对对象相同的引用,简单来说传递的是引用值
  3. 在引用值不发生变化时,可以访问对象内部,对对象进行操作,但需要注意,这个传入的参数仍旧是副本,并不是原对象,只是因为引用值相同,他可以对原对象进行访问
  4. 当副本持有的引用值发生变化时,与原对象已经没有关系,方法结束,副本消亡,不会对对象有影响
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352