Java参数传递里面的“坑”

374 阅读3分钟

在 Java 中,“引用传递” 的问题经常让初学者困惑,因为 Java 本质上只有值传递(Pass by Value),但对于对象类型,传递的是引用的副本,而不是引用本身。这可能导致一些看似“引用传递失败”的情况。


1. Java 是值传递(Pass by Value)

  • 基本类型(int, double, char...):直接传递值的副本,修改不影响原变量。
  • 对象类型(String, 自定义类...):传递的是引用的副本(即对象内存地址的副本),修改对象属性会影响原对象,但重新赋值不会影响原引用

示例 1:基本类型(值传递)

public class Main {
    public static void modify(int x) {
        x = 20;  // 修改的是副本,不影响原变量
    }
    public static void main(String[] args) {
        int a = 10;
        modify(a);
        System.out.println(a);  // 输出 10(未改变)
    }
}

结论:基本类型是值传递,方法内修改不影响原变量。


示例 2:对象类型(传递引用的副本)

class Person {
    String name;
    Person(String name) { this.name = name; }
}

public class Main {
    public static void modify(Person p) {
        p.name = "Bob";  // 修改对象属性,会影响原对象
    }
    public static void reassign(Person p) {
        p = new Person("Alice");  // 重新赋值,不影响原引用
    }
    public static void main(String[] args) {
        Person person = new Person("Tom");
        
        modify(person);
        System.out.println(person.name);  // 输出 "Bob"(修改成功)
        
        reassign(person);
        System.out.println(person.name);  // 仍然输出 "Bob"(重新赋值失败)
    }
}

结论

  1. 修改对象属性p.name = "Bob")会影响原对象,因为传递的是引用的副本,但指向的是同一个对象。
  2. 重新赋值引用p = new Person("Alice"))不会影响原引用,因为方法内部只是修改了副本的指向。

2. 为什么“引用传递”似乎失败了?

情况 1:方法内重新赋值,但外部未改变

public static void changeString(String str) {
    str = "New Value";  // String 是不可变类,相当于 new String("New Value")
}
public static void main(String[] args) {
    String s = "Hello";
    changeString(s);
    System.out.println(s);  // 仍然输出 "Hello"
}

原因String 是不可变类,str = "New Value" 实际上是让 str 指向了一个新对象,但原引用 s 不变。

情况 2:方法内 new 了一个新对象

public static void changeArray(int[] arr) {
    arr = new int[]{100, 200};  // 重新赋值,不影响原引用
}
public static void main(String[] args) {
    int[] nums = {1, 2, 3};
    changeArray(nums);
    System.out.println(nums[0]);  // 仍然输出 1
}

原因arr = new int[]{...} 让方法内的 arr 指向了新数组,但原 nums 仍然指向旧数组。


3. 如何让“引用传递”生效?

如果希望方法内部修改能影响外部变量,可以:

  1. 返回新对象并赋值(推荐):
    public static Person reassignAndReturn(Person p) {
        return new Person("Alice");
    }
    public static void main(String[] args) {
        Person person = new Person("Tom");
        person = reassignAndReturn(person);  // 重新赋值
        System.out.println(person.name);  // 输出 "Alice"
    }
    
  2. 使用包装类(如 AtomicReference
    import java.util.concurrent.atomic.AtomicReference;
    
    public static void change(AtomicReference<String> ref) {
        ref.set("New Value");
    }
    public static void main(String[] args) {
        AtomicReference<String> strRef = new AtomicReference<>("Hello");
        change(strRef);
        System.out.println(strRef.get());  // 输出 "New Value"
    }
    
  3. 使用数组或容器(如 String[]
    public static void change(String[] arr) {
        arr[0] = "New Value";
    }
    public static void main(String[] args) {
        String[] s = {"Hello"};
        change(s);
        System.out.println(s[0]);  // 输出 "New Value"
    }
    

4. 总结

情况是否影响原变量原因
修改对象属性(obj.field = x✅ 影响引用副本仍指向原对象
重新赋值引用(obj = new Obj()❌ 不影响修改的是副本的指向
基本类型(int a❌ 不影响纯值传递
String(不可变类)❌ 不影响每次修改相当于 new String()

关键点

  • Java 只有值传递,对象类型传递的是引用的副本
  • 方法内部修改对象属性会影响原对象,但重新赋值引用不会影响原变量。
  • 如果需要方法内部修改影响外部变量,可以返回新对象或使用包装类(如 AtomicReference)。

希望这个解释能帮你彻底理解 Java 的“引用传递”问题! 🚀