IE盒子

搜索
查看: 193|回复: 13

终于弄懂了:Java 中到底有没有引用传递?

[复制链接]

5

主题

9

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 2023-1-18 21:33:25 | 显示全部楼层 |阅读模式
大家好,我是 杰哥
还记不记得,刚开始找工作的时候,笔试题中,往往会有那么几道代码题是这样的:给你几行代码,让你判断输出的结果是什么。经常出现的是判断程序启动时,父类子类的构造方法、静态方法、静态属性与普通方法等的执行顺序。还有一种类型则是,一个值,调用了一个 void 方法之后,判断打印出来的是修改以后的值是什么,一般都是考你经过这个方法的调用,这个值会不会发生变化
这些套路题,往往在最初会难倒很多基础较为薄弱的童鞋,但是若搞懂了这些背后的原理,那你还怕什么,这些对你来说,岂不是送分题?推你进大厂呢?
那么,今天我们就来一起看看,java 中的值传递与引用传递,消灭以前的一些错误理解,彻底搞清楚 java 在调用中,究竟是如何进行参数的传递的?
一 理论先行

要搞清楚 java 中的方法调用,到底是值传递,还是引用传递之前,先来看看 值传递和引用传递分别是什么
值传递(pass by value)是指在调用函数时将实际参数 复制 一份传递到函数中,而并不是将这个值直接传递给函数
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,而传递过来的地址还是与之前的地址指向同一个值,那么要是修改了这个参数,就会影响这个值的改变
二 实践寻找

很多人都说 Java 中实际上是只有值传递,其实,我也很认同,并通过以下几个例子,做了验证
看的时候,可以先别看答案,先自己猜一猜会是什么结果哦
1 传递类型为基本类型 int

public static void main(String[] args) {
        int value = 10;
        setValue(value);
        System.out.println("调用后:"+ value);
    }


    public static void setValue(int value) {
        value = 2 * value;
        System.out.println("调用时:" + value);

    }
打印结果:
调用时:20
调用后:10
说明
value 的值并没有因为调用了 setValue()方法,而发生改变
也就是说,我们验证了 java 中的 基本类型 是采用值传递的方式的
2 传递类型为引用类型: String

public static void main(String[] args) {
        String value = "hello";
        setValue(value);
        System.out.println("调用后:"+ value);
    }


    public static void setValue(String value) {
        value = value + "123";
        System.out.println("调用时:" + value);

    }
打印结果:
调用时:hello123
调用后:hello
说明
value 的值并没有因为调用了 setValue()方法,而发生改变
那么,我们也验证了 java 中的 引用类型 是采用值传递的方式的
中场休息

来来来,那么也就是说,我们基本上可以下个结论,就是说:java 的确如大家所说,在方法调用过程中,是采用值传递的
我们来分析一下他们调用过程中的步骤
我们知道,java 在进行方法调用时,会将线程中每个方法分别放入栈中,先进入的方法,最终被放在了最低层,因此会在最后被执行,最后入栈的方法,则会最先被执行
那么,对于 main 方法和 setValue() 方法,在 栈中存放的 方式如下:


main  方法 中的 value,被传递给 setValue()  方法,最终它的值并没有改变,也就是说,它是值传递:即传递 给 setValue() 的参数,是 value 的副本,而并不是 value 本身,所以,我们在 setValue() 方法中,即时为 value 重新赋值,由于它是另一个 value,那么,原来的 value 值当然不会被修改了
也许,你会对此提出质疑,因为你在想,即使 基本类型 和这里的 String 类型,虽然被印证了是采用值传递的,但是要是其他引用类型呢?比如说 一个实体类对象的值,往往就因为调用方法,而发生变化
是这样吗?我们来接着看看
3 传递类型为引用类型:User

public static void main(String[] args) {
        User user = User.builder().age(11).name("杰哥").build();
        setValue(user);
        System.out.println("调用后:"+ user);
    }


    public static void setValue(User user) {
        user = user.builder().age(15).name("李四").build();
        System.out.println("调用时:" + user);
    }
打印结果:
调用时:User(id=0, name=李四, age=15)
调用后:User(id=0, name=杰哥, age=11)
说明
value 的值并没有因为调用了 setValue()方法,而发生改变。杰哥,还是那个杰哥
这下你相信了吧?引用类型,也是采用了值传递的:在方法调用过程中,只会传递一份参数的副本,因此并不会影响原来的值
如果没有完全说服你,那么我们再来一个例子,传递类型为集合
4 传递类型为 集合:List

public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        setValue(list);
        System.out.println("调用后:"+ list);
    }


    public static void setValue(List<String> list) {
        list = new ArrayList<>();
        list.add("world");
        list.add("hello");
        System.out.println("调用时:" + list);

    }
打印结果:
调用时:[world, hello]
调用后:[hello, world]
说明:
并且将 list 换成 List<User> 类型的结果也一模一样,调用方法之后的 list 值并不会发生变化。这下,我猜你应该已经确信了,java 的确是值传递的,嗯,没毛病
但是,估计有一少部分人,还是会举出一个反例,来推倒这个理论,为了加深大家的理解,我们也一起来看看 这个所谓的"反例"
三 反例说明

public static void main(String[] args) {
        User user = User.builder().age(11).name("杰哥").build();
        setValue(user);
        System.out.println("调用后:"+ user);
    }


    public static void setValue(User user) {
        user.setAge(12);
        user.setName("李四");
        System.out.println("调用时:" + user);
    }
先来猜猜打印结果。。。。。。
是的,打印结果如下:
调用时:User(id=0, name=李四, age=12)
调用后:User(id=0, name=李四, age=12)
说明:
首先,user 的值,在调用了 setValue()  方法之后,是被修改了的,怎么样,是不是会有一点点蒙?
哈哈,没有关系,往下看
1 对比

这个可以跟第二章中的第 3 个例子,对比着看,同样是 User 对象的传递,最终前者未发生变化,而后者却发生了变化。不用疑惑,我们先来对比一下两者的 不同之处
前者的 setValue()  方法,如下:
public static void setValue(User user) {
        user = user.builder().age(15).name("李四").build();
        System.out.println("调用时:" + user);
    }
后者的 setValue() 方法,如下:
public static void setValue(User user) {
        user.setAge(12);
        user.setName("李四");
        System.out.println("调用时:" + user);
    }
其实你会发现,两者的区别很明显:
前者是直接修改了整个 user 的值;而后者,是分别对 user 对象的属性进行重新赋值的
前者的调用过程,我们在上面已经分析过了:由于这里的 user 是 main 方法中 user 对象的拷贝,那么,这里即使重新对这个 user 赋值,并不会更改 main  方法中的 user 的值
我们一起来分析一下后者这段代码的调用过程,如下图所示:
2 分析



说明

  • main 方法 将 user 拷贝 给 setValue() 方法
2)拷贝的内容是 user 本身,但是拷贝之后的 user 对象的值,指向的还是与拷贝前的 user 所指向的同一份值
age = 11,
name = "杰哥"
3)那么,在 setValue() 中,修改了这份值,改为了:
age = 12,
name = "李四"
那么,值就这一份,被修改了,那最终呈现出来的效果,就是我们最终的 user 对象就发生了变化
这其实也印证了,java 是值传递的,只是对于 java 中的对象参数来说,值的内容是对象的引用。我们再来回顾一下值传递到底是什么?
值传递 是指在调用函数时将实际参数 复制 一份传递到函数中,而并不是将这个值直接传递给函数
而,我们这里实际上也是把 user 复制了一份到 setValue()函数中,这里的值实际上是 user 的对象引用,只 copy 了引用,也就是地址,但是他们所指向的内容,却依旧是同一份,java 中并不会为你再创建一份,那么,直接修改了这些内容,那么原来的 user 对象的值肯定也就发生了改变
四 总结

好了,事实证明,java 中的确只是存在值传递的,不知道你理解了没有。我们再来总结一下
1 java 中只存在值传递,对于 基本类型、引用类型以及对象类型均是如此,因此调用了一个对某个传递过来的参数进行赋值操作的时候,均不会影响原来的值
2 对于对象类型,若直接修改了它的具体属性,当出现在调用方法之后,会发生改变的原因是:在 java 中,对象类型的值是对象引用,在调用过程中,传递的是一份对象引用的拷贝进行传递的,但是原引用和拷贝的引用依旧指向的是堆中的同一份值,因此,这份值做了改变,原来的 对象类型本身就发生了变化
回复

使用道具 举报

3

主题

7

帖子

13

积分

新手上路

Rank: 1

积分
13
发表于 2023-1-18 21:33:41 | 显示全部楼层
public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");
        setValue(list);
        System.out.println("调用后:"+ list);
    }

    public static void setValue(List<String> list) {
        list = new ArrayList<>();
        list.add("world");
        list.add("hello");
        System.out.println("调用时:" + list);

    }
你这样写,你妈妈知道吗?[捂脸]
回复

使用道具 举报

1

主题

7

帖子

6

积分

新手上路

Rank: 1

积分
6
发表于 2023-1-18 21:33:54 | 显示全部楼层
https://blog.csdn.net/bjweimengshu/article/details/79799485,建议你看看
回复

使用道具 举报

0

主题

4

帖子

0

积分

新手上路

Rank: 1

积分
0
发表于 2023-1-18 21:34:04 | 显示全部楼层
回复

使用道具 举报

2

主题

5

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2023-1-18 21:34:50 | 显示全部楼层
你细品一下你这个操作,没啥意义
回复

使用道具 举报

2

主题

4

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2023-1-18 21:35:22 | 显示全部楼层
在方法里重新new了一个list,你的传参还有用吗?你把setValue中的list=new  ArrayList<>()去掉,不明白你的代码是想证明什么。突然有人拿着你这段代码问我,我一脸懵逼,这样写没有意义吧
回复

使用道具 举报

2

主题

7

帖子

8

积分

新手上路

Rank: 1

积分
8
发表于 2023-1-18 21:35:37 | 显示全部楼层
你有认真看你发来的链接里面的代码吗?
回复

使用道具 举报

5

主题

7

帖子

17

积分

新手上路

Rank: 1

积分
17
发表于 2023-1-18 21:36:01 | 显示全部楼层
兄嘚 你这妥妥的基础不扎实。先搞懂什么是引用,什么是值再说吧。
回复

使用道具 举报

4

主题

10

帖子

18

积分

新手上路

Rank: 1

积分
18
发表于 2023-1-18 21:36:24 | 显示全部楼层
大佬大佬
回复

使用道具 举报

1

主题

4

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2023-1-18 21:37:12 | 显示全部楼层
这个只是要验证这个变量是不是创建了一个副本而已。从而验证java只有值传递
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表