搞不清Java开发中的 值传递 和 引用传递 竟然影响如此重大?!

在Java中,当我们传递一个对象作为参数时,有两种不同的方式:值传递和引用传递。这两种方式在实际开发中可能会导致对象的值没有真正地传递,从而引发一些问题。此次开发过程中就遇到了一个由此引发的问题,导致开发过程中本该拦截的数据未真正拦截!如果这种问题未及时拦截,使其出现在业务场景,很可能由于业务场景的重要性造成很大的损失。因此给大家分享一下,以此为鉴。在讲问题前,我们先来温习一下值传递和引用传递的概念。

1. 值传递

值传递是指将对象的副本传递给方法,而不是对象本身。这意味着对副本所做的任何更改都不会影响原始对象。在Java中,基本数据类型(如int、double等)和对象引用(如String、Integer等)都采用值传递。

public void modifyValue(int value) {
    value = 10;
}

public static void main(String[] args) {
    int originalValue = 5;
    modifyValue(originalValue);
    System.out.println(originalValue); // 输出: 5
}

在这个例子中,modifyValue()方法接收了一个整数值,并将其修改为10。然而,由于整数是通过值传递的,所以调用结束后,originalValue的值仍然是5。

2. 引用传递

引用传递是指传递对象的引用,即传递对象本身的地址。这样,对引用所做的任何更改都会影响原始对象。在Java中,对象引用(如Object、Person等)都是通过引用传递的。

public void modifyReference(Person person) {
    person.setName("New Name");
}

public static void main(String[] args) {
    Person originalPerson = new Person("Original Name");
    modifyReference(originalPerson);
    System.out.println(originalPerson.getName()); // 输出: New Name
}

在这个例子中,modifyReference()方法接收了一个Person对象的引用,并将其名称更改为"New Name"。由于对象引用是通过引用传递的,所以调用结束后,originalPerson的名称也发生了改变。

3. 值传递和引用传递混淆造成的问题举例

好的,让我们步入正题,看一下在开发中遇到的一个问题,我把问题简单模拟如下。下面这段代码定义了一个 Person 类,其中包含一个 name 字段。首先我们创建了一个集合List<Person>,并添加了两个 name 属性分别为 “aaa” 和“ “bbb”Person对象。然后调用了一个 fliterList 方法,该方法接受一个 List<Person> 和一个 curName 参数,以实现过滤掉List<Person>name 属性为 “aaa” 的对象。

    @Data
    static class Person {
        String name;
    }

        public static void fliterList(List<Person> persons, String curName) {
            persons.stream().filter(e -> !e.getName().equals(curName)).collect(Collectors.toList());
        }

        public static void main(String[] args) {
            List<Person> persons = new ArrayList<>();
            Person aaa = new Person();
            aaa.setName("aaa");
            Person bbb = new Person();
            bbb.setName("bbb");
            persons.add(aaa);
            persons.add(bbb);

            changeName(persons, "aaa");

            System.out.println(JSON.toJSONString(persons)); // 输出: [{"name":"aaa"},{"name":"bbb"}]
        }

在这里插入图片描述

但实际上在方法中并未改变传入的 persons 列表,在 main 方法中创建了两个 Person 对象并添加到 persons 列表中,然后调用 fliterList 方法,但未更新列表。最后输出了 persons 列表内容,仍然包含原始的两个 Person 对象。运行后输出结果仍为[{"name":"aaa"},{"name":"bbb"}],并未过滤掉“aaa”

那么应该怎么办?

    • 问题排查
    @Data
    static class Person {
        String name;
    }

        public static void fliterList(List<Person> persons, String curName) {
            persons = persons.stream().filter(e -> !e.getName().equals(curName)).collect(Collectors.toList());
            System.out.println(JSON.toJSONString(persons));// 输出: [{"name":"bbb"}]
        }

        public static void main(String[] args) {
            List<Person> persons = new ArrayList<>();
            Person aaa = new Person();
            aaa.setName("aaa");
            Person bbb = new Person();
            bbb.setName("bbb");
            persons.add(aaa);
            persons.add(bbb);

            changeName(persons, "aaa");

            System.out.println(JSON.toJSONString(persons)); // 输出: [{"name":"aaa"},{"name":"bbb"}]
        }

在这里插入图片描述
我们发现方法内部确实是做了过滤,但是最后得到的结果为什么没有过滤掉呢?

  • 破案
    因为Lambda表达式在Java中是值传递的。在fliterList方法中,虽然重新赋值了persons变量,但这不会影响main方法中传递的原始persons列表。原始列表仍然保持不变。

  • 解决方案
    知道了原因,那我们就对症下药,这里提供了两种修改方式以实现预期的功能:

方法一:

public static List<Person> fliterList(List<Person> persons, String curName) {
    return persons.stream()
            .filter(e -> !e.getName().equals(curName))
            .collect(Collectors.toList());
}

public static void main(String[] args) {
    List<Person> persons = new ArrayList<>();
    Person aaa = new Person();
    aaa.setName("aaa");
    Person bbb = new Person();
    bbb.setName("bbb");
    persons.add(aaa);
    persons.add(bbb);

    persons = fliterList(persons, "aaa");

    System.out.println(JSON.toJSONString(persons)); // 输出: [{"name":"bbb"}]
}

在这里插入图片描述

通过修改 fliterList 方法,使其返回过滤后的结果列表,并在 main 方法中更新 persons 列表为返回的新列表,才能实现预期的功能,即过滤出不等于指定名称的 Person 对象。

方法二:

@Data
    static class Person {
        String name;
    }

    public static void fliterList(List<Person> persons, String curName) {
        List<Person> filteredPersons = new ArrayList<>();
        for (Person person : persons) {
            if (!person.getName().equals(curName)) {
                filteredPersons.add(person);
            }
        }
        persons.clear();
        persons.addAll(filteredPersons);
    }

        public static void main(String[] args) {
            List<Person> persons = new ArrayList<>();
            Person aaa = new Person();
            aaa.setName("aaa");
            Person bbb = new Person();
            bbb.setName("bbb");
            persons.add(aaa);
            persons.add(bbb);

            fliterList(persons, "aaa");

            System.out.println(JSON.toJSONString(persons)); // 输出: [{"name":"bbb"}]
        }

在这里插入图片描述
Lambda表达式在Java中是值传递的。在fliterList方法中,虽然重新赋值了persons变量,但这不会影响main方法中传递的原始persons列表。原始列表仍然保持不变。也就是说Lambda表达式中的变量默认是final,Lambda表达式中无法修改外部的变量。因此,当使用Lambda表达式对列表进行操作时,虽然可以访问列表中的元素,但无法直接修改列表本身。使用普通循环则可以直接对列表进行修改,因为在普通循环中可以通过索引或迭代器访问并修改列表的元素。

4. 在日常开发中需要注意及解决方案

注意事项

  • 对于基本数据类型,确保理解它们的值传递特性,避免意外修改。

  • 对于对象引用,明确区分值传递和引用传递,以防止误操作。

  • 当需要修改对象状态时,使用引用传递,以便在方法内部修改对象。

  • 当不需要修改对象状态时,使用值传递,以确保不会意外地修改原始对象。

  • 在Java中,对象作为参数传递时是按值传递的,但由于传递的是对象引用的副本,可能会导致误解。

  • 修改对象时需要注意是否会影响原始对象,尤其在方法中对传入的对象进行修改时。

  • 解决方案

  • 当需要修改对象并使修改生效时,方法应该返回修改后的对象或者更新原始对象的引用。

  • 明确方法的目的,避免副作用,尽量避免在方法中直接修改传入的对象,而是通过返回值或者其他方式来实现。

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值