在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中,对象作为参数传递时是按值传递的,但由于传递的是对象引用的副本,可能会导致误解。
-
修改对象时需要注意是否会影响原始对象,尤其在方法中对传入的对象进行修改时。
-
解决方案:
-
当需要修改对象并使修改生效时,方法应该返回修改后的对象或者更新原始对象的引用。
-
明确方法的目的,避免副作用,尽量避免在方法中直接修改传入的对象,而是通过返回值或者其他方式来实现。