Different Ways to Achieve Pass by Reference in Java

2025年5月9日 | 阅读 7 分钟

在 Java 中,所有参数都是按值传递的。然而,我们可以通过修改可变对象、使用包装类或使用数组或集合等引用来模拟“按引用传递”的行为。这些方法允许在方法内间接修改原始数据,从而模仿“按引用传递”的功能,同时遵循 Java 的值语义。

示例

输入: Person {name = "John"}

输出: Person {name = "Alice"}

解释

在此示例中,Person 对象的初始姓名为“John”。当对象传递给方法时,会使用对象的引用,从而允许该方法修改对象的状态。

方法调用后,姓名将更新为“Alice”,反映了在方法内所做的更改,这表明可变对象如何模拟 Java 中“按引用传递”的行为。

方法 1:使用对象模拟“按引用传递”

算法

步骤 1:创建一个具有可更改字段的类:开始设计一个包含可以修改的字段的类。该字段代表您要操作的数据,例如人的姓名。此方法允许在方法内间接修改原始数据,从而模仿“按引用传递”的功能。

步骤 1.1:定义要修改的字段:在此子步骤中,在您创建的 中,定义一个将保存您要修改的值的字段(或属性)。该字段是方法稍后将更改的字段。例如,如果我们的类是 Person,则字段可以命名为 name,它代表人的姓名。定义此字段允许对象存储可以访问和修改的数据。

步骤 2:实例化一个对象:创建该类的对象,并为其字段赋值。例如,给对象一个名字,如“John”。

步骤 3:显示初始值:在执行任何操作之前,打印字段的值。这为比较建立了起点。

步骤 3.1:确认对象的初始状态:在此子步骤中,在进行任何更改之前,请确保对象的初始状态正确。您可以添加检查或验证以确认字段(例如姓名)是否具有预期的值,从而确保起点符合预期。此步骤通过在继续之前验证对象的数据来帮助避免以后的混淆。

步骤 4:将对象传递给方法:调用一个 方法,并将对象作为参数传递。在 Java 中,传递的是对象的引用,这使得该技术可以作用于内存中的实际对象。

步骤 4.1:按引用传递对象:在此子步骤中,调用方法时,请确保对象被正确传递。由于 Java 按引用传递对象,因此方法应接收原始对象的引用(或地址),而不是副本。此步骤确保在方法内所做的任何更改都会直接影响方法外部的原始对象。

步骤 5:在方法内修改字段:在方法内更改对象字段的值。例如,将姓名更新为“Alice”。由于引用指向相同的内存位置,这将直接改变对象的状态。

步骤 6:返回主程序:方法完成后,控制权返回到主程序。原始对象现在反映了在方法内所做的更改。

步骤 7:显示最终值:打印对象字段的更新值。新值,例如“Alice”,确认在方法中进行的更改是持久的。

步骤 8:在不同上下文中验证更改:在此步骤中,您可以测试在程序的其他部分中对对象所做更改的效果。例如,您可以将修改后的对象传递给另一个方法,在主程序中再次打印其状态,或使用该对象执行其他操作。

它验证修改是否跨程序持久存在,从而确认对象的状态已通过该方法更新。它演示了修改对象的实际效果,确保了方法所做的更改在全球范围内得到反映。

输出

 
Before method call: John
After method call: Alice   

复杂度分析

时间复杂度

此方法的 time complexity 为 O(1)。这是因为操作——创建对象、将其传递给方法以及修改字段——都是常数时间操作。它们不取决于数据的大小,并且在过程中不涉及循环或递归。

空间复杂度

此方法的 space complexity 为 O(1)。无论对象大小如何,都只使用对象引用和少量局部变量。由于不涉及额外的数据结构或内存密集型操作,因此所需的空间保持恒定,与输入或对象的属性无关。

方法 2:使用包装类

算法

步骤 1:创建包装类:定义一个充当要修改的值的容器的类。例如,创建一个名为 Wrapper 的类,其中包含一个 value 字段。

步骤 1.1:定义要修改的值的字段:在此步骤中,创建 Wrapper 类后,我们需要定义将保存您要修改的值的特定字段(或属性)。该字段是在方法内更改的字段,反映了“按引用传递”的行为。

例如,在 Wrapper 类中,我们可能有一个字段,如 int value; 来存储将被修改的 整数。定义此字段允许类封装传递给方法并在其中修改的数据。

步骤 2:在 Wrapper 类中定义构造函数:添加一个构造函数,在创建对象时初始化该字段。例如,将 value 初始化为给定的数字,例如 10。

步骤 3:创建 Wrapper 类的对象:在 main 方法中实例化 Wrapper 类,并为其字段赋值。例如,创建一个对象 wrapper,其 value = 10。

步骤 4:打印初始值:在将对象传递给任何方法之前,显示字段的值。这有助于验证对象的初始状态。

步骤 5:将 Wrapper 对象传递给方法:调用一个方法,并将包装器 对象 作为参数传递。该方法将接收包装器对象的引用,而不是副本。

步骤 6:在方法内修改字段:在方法内部,更新包装器对象的字段。例如,将 value 字段从 10 更改为 20。由于传递的是引用,因此更改将影响原始对象。

步骤 7:返回主方法:方法执行完成后,控制权将返回到 main 方法。原始包装器对象现在包含更新后的字段值。

步骤 8:在不同场景下验证更改:在 main 方法中打印更新值后,您可以在程序的其他部分测试修改的效果。

例如,将修改后的 Wrapper 对象传递给另一个方法,在另一个上下文中打印其状态,或执行其他操作。这确保了在方法内所做的更改持久存在,并确认了对象的状态已全局更新。

步骤 8:打印更新值:在 main 方法中再次显示字段的值,以确认在方法内所做的修改已持久存在。

输出

 
Before method call: 10
After method call: 20   

复杂度分析

时间复杂度

此方法的 time complexity 为 O(1),因为在包装类中修改字段是常数时间操作。执行不涉及循环、递归或取决于输入大小的操作,从而确保所需时间与要修改的值无关。

空间复杂度

程序的 space complexity 为 O(1),因为它只使用单个包装器对象及其引用。不需要额外的数据结构或内存密集型操作,因此所需的空间保持恒定,与要修改的值或程序的大小无关。

优点

  • 模拟“按引用传递”:实现对原始数据的修改,从而在 Java 中模拟“按引用传递”行为。
  • 封装:将相关数据分组,提高代码清晰度和组织性。
  • 可重用性:包装类可以跨多个方法重用,减少冗余。
  • 简单实现:易于实现,无需高级构造或库。

缺点

  • 额外的对象创建:需要额外的包装器对象,稍微增加了内存使用量。
  • 不直观:不熟悉此技术的开发人员可能觉得理解起来更困难。
  • 仅限于可变数据:仅与可变对象或字段有效。
  • 简单任务的开销:对于小型程序,使用包装器可能显得多余。
  • 目的:演示如何在 Java 中模拟“按引用传递”行为,其中所有参数都按值传递,方法是利用可变对象、包装类、数组或集合。
  • 关键概念:按值传递:Java 传递对象变量引用的副本,从而允许间接修改实际数据。
  • 模拟“按引用传递”:通过修改传递给方法的对象或结构的内容来实现。
  • 讨论的技术:使用可变对象:方法内对象字段的更改在方法调用后仍然存在。
  • 使用包装类:将原始类型或不可变类型包装在自定义类中以修改其状态。
  • 使用数组/集合:传递这些结构可以修改其中的元素。