轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

      • Java8--Lambda 表达式、Stream 和时间 API
      • Java集合
      • Java IO
      • Java 文件操作
      • Java 网络编程
      • Java运行期动态能力
      • Java可插入注解处理器
      • Java基准测试(JMH)
      • Java性能分析(Profiler)
      • Java调试(JDI与JDWP)
      • Java管理与监控(JMX)
      • Java加密体系(JCA)
      • Java服务发现(SPI)
      • Java随机数生成研究
      • Java数据库连接(JDBC)
      • Java历代版本新特性
      • 写好Java Doc
      • 聊聊classpath及其资源获取
      • Java参数传递机制
        • 一、核心结论
        • 二、基本类型传递
        • 三、引用类型传递
          • 3.1 可以修改对象内部状态
          • 3.2 不能改变引用指向
        • 四、常见误区
          • 误区:引用类型是"引用传递"
        • 五、String 的特殊性
        • 六、实战应用
          • 6.1 为什么 swap 方法不生效
          • 6.2 如何正确返回多个值
        • 七、如何实现引用修改效果
          • 7.1 返回值方式(推荐)
          • 7.2 包装类/容器类
          • 7.3 对象属性方式
          • 7.4 方案选择建议
        • 八、总结
    • 并发

    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 核心
轩辕李
2021-06-11
目录

Java参数传递机制

深入理解 Java 的参数传递机制,澄清值传递与引用传递的常见误区。

# 一、核心结论

Java 只有值传递(Pass by Value),没有引用传递(Pass by Reference)。

无论是基本类型还是引用类型,Java 在方法调用时传递的都是值的副本:

  • 基本类型:传递的是数据值的副本
  • 引用类型:传递的是引用(对象地址)的副本

# 二、基本类型传递

基本类型传递时,方法内部对参数的修改不会影响原变量。

public class PrimitivePassDemo {
    public static void main(String[] args) {
        int num = 10;
        modify(num);
        System.out.println(num);  // 输出: 10
    }
    
    static void modify(int n) {
        n = 20;  // 只修改了副本
    }
}

原理:modify 方法接收的是 num 值的副本,修改副本不影响原变量。

# 三、引用类型传递

引用类型传递时,传递的是引用(对象地址)的副本。

# 3.1 可以修改对象内部状态

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

public class ReferencePassDemo {
    public static void main(String[] args) {
        Person p = new Person("张三");
        modifyObject(p);
        System.out.println(p.name);  // 输出: 李四
    }
    
    static void modifyObject(Person person) {
        person.name = "李四";  // 通过引用副本修改对象属性
    }
}

# 3.2 不能改变引用指向

public class ReferencePassDemo2 {
    public static void main(String[] args) {
        Person p = new Person("张三");
        reassign(p);
        System.out.println(p.name);  // 输出: 张三
    }
    
    static void reassign(Person person) {
        person = new Person("李四");  // 只是修改了引用副本的指向
    }
}

原理:

  • modifyObject 方法中,person 是 p 引用的副本,两者指向同一个对象,所以可以修改对象属性
  • reassign 方法中,person = new Person("李四") 只是让副本指向新对象,不影响原引用 p

# 四、常见误区

# 误区:引用类型是"引用传递"

很多人认为引用类型是引用传递,因为可以修改对象属性。这是错误的。

真正的引用传递(如 C++ 的引用参数)可以改变原引用的指向:

// C++ 引用传递示例
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int x = 1, y = 2;
swap(x, y);  // x = 2, y = 1,真正交换了

Java 无法实现这样的效果:

// Java 的 swap 无效
static void swap(Integer a, Integer b) {
    Integer temp = a;
    a = b;
    b = temp;  // 只是交换了副本
}

Integer x = 1, y = 2;
swap(x, y);  // x 仍然是 1, y 仍然是 2

# 五、String 的特殊性

String 是不可变类,修改 String 会创建新对象,因此表现更像基本类型。

public class StringPassDemo {
    public static void main(String[] args) {
        String str = "Hello";
        modify(str);
        System.out.println(str);  // 输出: Hello
    }
    
    static void modify(String s) {
        s = "World";  // 创建新对象,只改变了副本的指向
    }
}

同样不可变的类型还包括:

  • 包装类(Integer、Long、Double 等)
  • BigDecimal、BigInteger
  • LocalDate、LocalDateTime 等时间类

# 六、实战应用

# 6.1 为什么 swap 方法不生效

static void swap(Person a, Person b) {
    Person temp = a;
    a = b;
    b = temp;
}

Person p1 = new Person("张三");
Person p2 = new Person("李四");
swap(p1, p2);  // p1 仍是"张三", p2 仍是"李四"

原因:交换的是引用副本,不影响原引用。

# 6.2 如何正确返回多个值

方式一:使用返回值(推荐)

static int[] calculate(int a, int b) {
    return new int[]{a + b, a - b, a * b};
}

int[] result = calculate(5, 3);
System.out.println("和: " + result[0]);
System.out.println("差: " + result[1]);
System.out.println("积: " + result[2]);

方式二:使用对象封装

class Result {
    int sum;
    int diff;
    int product;
}

static Result calculate(int a, int b) {
    Result r = new Result();
    r.sum = a + b;
    r.diff = a - b;
    r.product = a * b;
    return r;
}

方式三:使用 Map(灵活但类型不安全)

static Map<String, Integer> calculate(int a, int b) {
    Map<String, Integer> result = new HashMap<>();
    result.put("sum", a + b);
    result.put("diff", a - b);
    result.put("product", a * b);
    return result;
}

# 七、如何实现引用修改效果

虽然 Java 没有真正的引用传递,但可以通过以下方式实现类似效果:

# 7.1 返回值方式(推荐)

最简洁直观,符合函数式编程思想。

static String modify(String str) {
    return str + " World";
}

String original = "Hello";
String modified = modify(original);  // 使用返回值

# 7.2 包装类/容器类

通过修改容器内部的内容,而非容器引用本身。

单值包装:

// 方式1: AtomicReference
static void modify(AtomicReference<String> ref) {
    ref.set("New Value");
}

AtomicReference<String> ref = new AtomicReference<>("Old");
modify(ref);
System.out.println(ref.get());  // 输出: New Value

// 方式2: 数组
static void modify(String[] holder) {
    holder[0] = "New Value";
}

String[] holder = {"Old"};
modify(holder);
System.out.println(holder[0]);  // 输出: New Value

// 方式3: 自定义 Holder 类
class Holder<T> {
    T value;
    Holder(T value) { this.value = value; }
}

static void modify(Holder<String> holder) {
    holder.value = "New Value";
}

Holder<String> holder = new Holder<>("Old");
modify(holder);
System.out.println(holder.value);  // 输出: New Value

多值容器:

// 使用 List
static void addValues(List<String> list) {
    list.add("New Item");
}

List<String> list = new ArrayList<>();
list.add("Item1");
addValues(list);
System.out.println(list);  // [Item1, New Item]

// 使用 Map
static void modifyMap(Map<String, Integer> map) {
    map.put("count", map.getOrDefault("count", 0) + 1);
}

Map<String, Integer> map = new HashMap<>();
modifyMap(map);
System.out.println(map.get("count"));  // 1

# 7.3 对象属性方式

将需要修改的值封装为对象字段。

class Context {
    String result;
    int count;
}

static void process(Context ctx) {
    ctx.result = "Processed";
    ctx.count = 100;
}

Context ctx = new Context();
process(ctx);
System.out.println(ctx.result);  // Processed
System.out.println(ctx.count);   // 100

# 7.4 方案选择建议

场景 推荐方案 原因
单一返回值 返回值方式 最简洁,类型安全
多个返回值 对象封装 语义清晰,类型安全
需要传入传出同一对象 对象属性方式 符合面向对象思想
临时共享状态 容器类(List/Map) 灵活,但注意类型安全
原子操作 AtomicReference 等 线程安全

反模式警告:

  • ❌ 避免使用数组作为 Holder(如 String[1]),可读性差
  • ❌ 避免过度使用 Map 传递多个值,类型不安全
  • ❌ 避免为了"修改引用"而强行使用包装类,优先考虑返回值

# 八、总结

  1. Java 只有值传递,引用类型传递的是引用的副本
  2. 可以修改对象属性,因为引用副本指向同一个对象
  3. 不能改变原引用指向,因为修改的只是副本
  4. String 和包装类不可变,修改会创建新对象
  5. 实现引用修改效果:优先使用返回值,其次使用容器类或对象属性

理解参数传递机制有助于:

  • 避免 swap 等常见陷阱
  • 正确设计方法返回值
  • 理解对象在方法间的传递行为
  • 编写更清晰、更少 bug 的代码

祝你变得更强!

编辑 (opens new window)
#Java参数传递#值传递
上次更新: 2025/11/29
聊聊classpath及其资源获取
Java并发-线程基础与synchronized关键字

← 聊聊classpath及其资源获取 Java并发-线程基础与synchronized关键字→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式