如何在Java中对原语进行等效的通过引用传递

Posted

技术标签:

【中文标题】如何在Java中对原语进行等效的通过引用传递【英文标题】:How to do the equivalent of pass by reference for primitives in Java 【发布时间】:2011-08-02 15:24:34 【问题描述】:

这个Java代码:

public class XYZ    
    public static void main()  
        int toyNumber = 5;   
        XYZ temp = new XYZ();  
        temp.play(toyNumber);  
        System.out.println("Toy number in main " + toyNumber);  
    

    void play(int toyNumber)  
        System.out.println("Toy number in play " + toyNumber);   
        toyNumber++;  
        System.out.println("Toy number in play after increement " + toyNumber);   
       
  

会输出这个:

游戏中的玩具编号 5 增加 6 后游戏中的玩具编号 主要玩具编号 5

在 C++ 中,我可以通过引用传递 toyNumber 变量以避免遮蔽,即创建与以下相同变量的副本:

void main()  
    int toyNumber = 5;  
    play(toyNumber);  
    cout << "Toy number in main " << toyNumber << endl;  


void play(int &toyNumber)  
    cout << "Toy number in play " << toyNumber << endl;   
    toyNumber++;  
    cout << "Toy number in play after increement " << toyNumber << endl;   
 

C++ 输出将是这样的:

游戏中的玩具编号 5 增加 6 后游戏中的玩具编号 主要 6 中的玩具编号

我的问题是 - 鉴于 Java is pass by value rather than pass by reference,在 Java 中获得与 C++ 代码相同的输出的等效代码是什么?

【问题讨论】:

这对我来说似乎不是重复的 - 据称重复的问题涉及 java 的工作原理以及术语的含义,即教育,而这个问题专门询问如何获得类似 pass-引用行为,许多 C/C++/D/Ada 程序员可能想知道为了完成实际工作,而不关心为什么 java 都是按值传递。 @DarenW 我完全同意 - 已投票重新开放。哦,你有足够的声誉做同样的事情:-) 关于原语的讨论颇具误导性,因为这个问题同样适用于参考值。 “在 C++ 中,我可以通过引用传递 toyNumber 变量以避免阴影” - 这不是阴影,因为在 main 方法中声明的 toyNumber 变量不在play 方法的范围内。 C++ 和 Java 中的阴影仅在存在嵌套范围时才会发生。见en.wikipedia.org/wiki/Variable_shadowing。 【参考方案1】:

您有多种选择。最有意义的一个实际上取决于您要做什么。

选择 1:将 toyNumber 设为类中的公共成员变量

class MyToy 
  public int toyNumber;

然后将对 MyToy 的引用传递给您的方法。

void play(MyToy toy)  
    System.out.println("Toy number in play " + toy.toyNumber);   
    toy.toyNumber++;  
    System.out.println("Toy number in play after increement " + toy.toyNumber);   

选择2:返回值而不是通过引用传递

int play(int toyNumber)  
    System.out.println("Toy number in play " + toyNumber);   
    toyNumber++;  
    System.out.println("Toy number in play after increement " + toyNumber);   
    return toyNumber

此选择需要对 main 中的调用站点进行小幅更改,使其显示为 toyNumber = temp.play(toyNumber);

选择 3:将其设为类或静态变量

如果这两个函数是同一个类或类实例上的方法,则可以将 toyNumber 转换为类成员变量。

选择 4:创建一个 int 类型的单元素数组并传递它

这被认为是一种 hack,但有时用于从内联类调用中返回值。

void play(int [] toyNumber)  
    System.out.println("Toy number in play " + toyNumber[0]);   
    toyNumber[0]++;  
    System.out.println("Toy number in play after increement " + toyNumber[0]);   

【讨论】:

清晰的解释,特别有利于提供多种选择,以实现所需的效果。对我现在正在做的事情有直接的帮助!这个问题被关闭真是太疯狂了。 虽然您的选择 1 确实符合您想要传达的意思,但实际上我不得不回去仔细检查它。该问题询问您是否可以通过引用传递;所以真的你应该在传递给函数的玩具所在的相同范围内完成 printlns。按照你的方式,printlns 与增量在同一范围内操作,这并不能真正证明这一点,当然在增量之后打印的本地副本将具有不同的值,但这并不意味着传递的引用会。同样,您的示例确实有效,但不能证明这一点。 不,我认为它的方式是正确的。我上面的答案中的每个函数“play()”都旨在替代原始“play()”函数,该函数还打印了递增前后的值。原始问题在 main 中有 println() 证明通过引用传递。 选择 2 需要进一步解释:main 中的调用站点必须更改为 toyNumber = temp.play(toyNumber); 才能正常工作。 这非常难看,而且给人一种骇人听闻的感觉......为什么这么基本的东西不是语言的一部分?【参考方案2】:

Java 不是按引用调用,它是仅按值调用

但对象类型的所有变量实际上都是指针。

所以如果你使用一个可变对象,你会看到你想要的行为

public class XYZ 

    public static void main(String[] arg) 
        StringBuilder toyNumber = new StringBuilder("5");
        play(toyNumber);
        System.out.println("Toy number in main " + toyNumber);
    

    private static void play(StringBuilder toyNumber) 
        System.out.println("Toy number in play " + toyNumber);
        toyNumber.append(" + 1");
        System.out.println("Toy number in play after increement " + toyNumber);
    

这段代码的输出:

run:
Toy number in play 5
Toy number in play after increement 5 + 1
Toy number in main 5 + 1
BUILD SUCCESSFUL (total time: 0 seconds)

您也可以在标准库中看到这种行为。 例如 Collections.sort(); Collections.shuffle(); 这些方法不会返回一个新列表,而是修改它的参数对象。

    List<Integer> mutableList = new ArrayList<Integer>();

    mutableList.add(1);
    mutableList.add(2);
    mutableList.add(3);
    mutableList.add(4);
    mutableList.add(5);

    System.out.println(mutableList);

    Collections.shuffle(mutableList);

    System.out.println(mutableList);

    Collections.sort(mutableList);

    System.out.println(mutableList);

这段代码的输出:

run:
[1, 2, 3, 4, 5]
[3, 4, 1, 5, 2]
[1, 2, 3, 4, 5]
BUILD SUCCESSFUL (total time: 0 seconds)

【讨论】:

这不是问题的答案。如果它建议创建一个包含单个元素的整数数组,然后在方法play 中修改该元素,它将回答这个问题;例如mutableList[0] = mutableList[0] + 1;。正如欧内斯特弗里德曼希尔所建议的那样。 使用非基元。对象的值不是参考。也许你想说:“参数值”是“参考”。引用按值传递。 我正在尝试做类似的事情,但在您的代码中,方法private static void play(StringBuilder toyNumber) - 如果它例如返回整数的公共静态int,您如何调用它?因为我有一个返回数字的方法,但是如果我不在某个地方调用它,它就没有被使用。【参考方案3】:

制作一个

class PassMeByRef  public int theValue; 

然后传递对它的实例的引用。请注意,最好避免通过参数改变状态的方法,尤其是在并行代码中。

【讨论】:

被我否决了。这在很多层面上都是错误的。 Java 总是按值传递。没有例外。 @duffymo - 当然你可以随意投反对票 - 但你考虑过 OP 的要求吗?他想通过引用传递和 int - 如果我通过值传递对上述实例的引用,就可以实现这一点。 @duffymo:嗯?你错了,或者误解了这个问题。这在 Java 中执行 OP 请求的传统方式之一。 @duffymo :您的评论在很多层面上都是错误的。 SO 是关于提供有用的答案和 cmets,而您只是因为(我猜)它不适合您的编程风格和理念而对正确答案投反对票。您能否至少对原始问题提供更好的解释,甚至更好的答案? @duffymo 这正是我所说的:按值传递 ref。您认为通过 ref 传递在减慢它的语言中是如何完成的?【参考方案4】:

您不能在 Java 中通过引用传递原语。当然,所有对象类型的变量实际上都是指针,但我们称它们为“引用”,它们也总是按值传递。

在你确实需要通过引用传递原语的情况下,人们有时会做的是将参数声明为原语类型的数组,然后将单元素数组作为参数传递。所以你传递一个引用int[1],在方法中,你可以改变数组的内容。

【讨论】:

不——所有的包装类都是不可变的——它们代表一个固定的值,一旦对象被创建就不能改变。 “您不能在 Java 中通过引用传递原语”:OP 似乎理解这一点,因为它们将 C++(如果可以)与 Java 进行对比。 需要注意的是,Ernest 所说的“所有包装类都是不可变的”适用于 JDK 内置的 Integer、Double、Long 等。您可以通过自定义包装类绕过此限制,例如Ingo's Answer 做了什么。【参考方案5】:

为了快速解决问题,您可以使用 AtomicInteger 或任何原子变量,它们可以让您使用内置方法更改方法内部的值。这是示例代码:

import java.util.concurrent.atomic.AtomicInteger;


public class PrimitivePassByReferenceSample 

    /**
     * @param args
     */
    public static void main(String[] args) 

        AtomicInteger myNumber = new AtomicInteger(0);
        System.out.println("MyNumber before method Call:" + myNumber.get());
        PrimitivePassByReferenceSample temp = new PrimitivePassByReferenceSample() ;
        temp.changeMyNumber(myNumber);
        System.out.println("MyNumber After method Call:" + myNumber.get());


    

     void changeMyNumber(AtomicInteger myNumber) 
        myNumber.getAndSet(100);

    


输出:

MyNumber before method Call:0

MyNumber After method Call:100

【讨论】:

我使用并喜欢这个解决方案,因为它提供了很好的复合动作,并且更容易集成到已经或将来可能想要使用这些原子类的并发程序中。【参考方案6】:
public static void main(String[] args) 
    int[] toyNumber = new int[] 5;
    NewClass temp = new NewClass();
    temp.play(toyNumber);
    System.out.println("Toy number in main " + toyNumber[0]);


void play(int[] toyNumber)
    System.out.println("Toy number in play " + toyNumber[0]);
    toyNumber[0]++;
    System.out.println("Toy number in play after increement " + toyNumber[0]);

【讨论】:

以上是关于如何在Java中对原语进行等效的通过引用传递的主要内容,如果未能解决你的问题,请参考以下文章

验证python中函数传参是引用传递

Ruby 是按值传递还是按引用传递? [复制]

java引用传递,值传递

SWIG:如何包装 std::string&(通过引用传递的 std::string)

值传递和引用传递

JAVA中值传递,引用传递