首页 > 其他分享 >搞不清 值传递 和 引用传递 竟然影响如此重大?!

搞不清 值传递 和 引用传递 竟然影响如此重大?!

时间:2024-06-23 13:31:15浏览次数:18  
标签:搞不清 aaa Person 对象 bbb 传递 persons 引用

在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中,对象作为参数传递时是按值传递的,但由于传递的是对象引用的副本,可能会导致误解。

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

  • 解决方案

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

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

标签:搞不清,aaa,Person,对象,bbb,传递,persons,引用
From: https://blog.csdn.net/weixin_55745942/article/details/139886278

相关文章

  • SpringBoot前后端传递数据时常用的JSON格式数据是什么?【讲解JSON概念、语法、以及Java
    SpringBoot前后端传递数据时常用的JSON格式数据是什么?JSON概念JSON语法JSON的两种结构:JSON字符串和Java对象互转:objectMapper.writeValueAsString(person);objectMapper.readValue(jsonStr,Person.class);在SpringMVC框架中,前后端交互会自动转JsonJSON概念JSON:Jav......
  • QT中子工程的创建,以及如何在含有库的子工程项目中引用主项目中的资源文件
    1、背景在qt中创建多项目类型,如下:CustomDll表示其中的一个动态库子项目;CustomLib表示其中的一个静态库子项目;MyWidget表示主项目窗口(main函数所在项目);2、qrc资源的共享如何在CustomDll和CustomLib等子项目中也同样使用MyWidget项目中的qrc资源呢???直接使用即可,因......
  • 右值引用
    右值引用右值引用是C++11引入的与Lambda表达式齐名的重要特性之一。它的引入解决了C++中大量的历史遗留问题,消除了诸如std::vector、std::string之类的额外开销,也才使得函数对象容器std::function成为了可能。左值、右值的纯右值、将亡值、右值要弄明白右值引用到底......
  • dotnet 6 破坏性改动 仅引用程序集输出路径变更
    在dotnet5开始,可以设置ProduceReferenceAssembly为true让项目构建时输出仅引用程序集。仅引用程序集是仅导出项目的公开成员定义,而不包含具体的实现的代码逻辑。只用来被其他项目引用,体积很小,但不用来作为最终发布文件在此前的如下博客里面已经告诉大家如何创建仅引用程序......
  • C++ 面向对象高级开发 4、参数传递与返回值
    consructor构造函数:被放在private区ctors放在private区classA{public:staticA&getInsance();    setup(){...};private:A();    A(constA&rhs);};A&A::getInstance(){staticAa;    returna;}A::getInsance().s......
  • vb.net c#一键编绎引用DLL如何做?编绎成独立EXE
    .netc#一键编绎引用如何做?3个工程有依懒关系ClassLibrary1,ClassLibrary2,MainProject(主工程)ClassLibrary2依赖ClassLibrary1,MainProject依赖前2个如何实现一键按顺序编绎,自动添加前面2个生成的DLL到主工程,并且设置为不复制到输出目录,"生成操作"属性选择"嵌入的资源"VSI......
  • 【鸿蒙实战开发】HarmonyOS-ArkUI教程@link装饰器双向数据传递的使用
    前言2024年可谓至关重要,而生态建设的前提,就是要有足够的开发人才。与之对应的,今年春招市场上与鸿蒙相关岗位和人才旺盛的热度,一方面反应了鸿蒙生态的逐渐壮大,另一方面也让人们对鸿蒙下一阶段的发展更具信心。随着鸿蒙市场份额的不断提升,相应的岗位也会迎来一个爆发式的......
  • 使用EventBus在Activity和fragment之间传递数据,出现post一次,却接收到多次对应事件
    背景项目中有一个activity,其中通过viewpager管理着多个页面,在activity操作某些数据时,通过eventbus将消息传递给fragment。该fragment中,分别在onViewCreated注册了eventbusif(!EventBus.getDefault().isRegistered(this)){EventBus.getDefault().register(this);}在onDes......
  • kettle从入门到精通 第七十二课 ETL之kettle 三谈http post(含文件上传),彻底掌握参数传
    场景:群里有个小伙伴在使用httppost步骤调用接口时遇到问题,postman调用正常,但是kettle中调用异常。 解决方案:既然postman调用接口正常,肯定是httppost步骤中某些参数设置的不正确导致的。那就把常用的方式都梳理下,搞定它。 1、httppost请求参数放到body中,Content-Type是appl......
  • Maven 官网 查找&下载 jar包 & pom引用
    问题描述在我们在开发过程中,经常遇到程序中需要引用的某个版本jar包,但是在公司的私有仓库下载不到的情况。遇到这种情况,该怎么办呢?很多人应该首选百度搜索吧。(当然可以,但是,不一定能很快找到自己想要的某个版本的jar包)这里给出一个简洁,方便查找的方案。 完美方案在Maven......