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


116

此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是通过值传递而不是通过引用传递,那么Java中与C ++代码获得相同输出的等效代码是什么?


22
这对我来说似乎不是重复的-所谓重复的问题涉及java的工作方式以及术语的含义,即教育程度,而这个问题专门询问如何获得类似于传递引用行为的东西,许多C / C ++ / D / Ada程序员可能想知道如何完成实际工作,而不在乎Java为什么都是按值传递。
2011年

1
@DarenW我完全同意-已投票决定重新开放。哦,您有足够的声誉做同样的事情:-)
Duncan Jones

关于原语的讨论颇具误导性,因为该问题同样适用于参考值。
shmosel

“在C ++中,我可以通过引用传递toyNumber变量,以避免产生阴影” -这不是阴影,因为方法中toyNumber声明的变量main不在方法范围内play。在C ++和Java中,只有在合并范围时,才会发生阴影。参见en.wikipedia.org/wiki/Variable_shadowing
斯蒂芬C,

Answers:


171

您有几种选择。最有意义的选择实际上取决于您要执行的操作。

选择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]);   
}

2
清晰的解释,尤其对于在如何编码以获得期望的效果方面提供多种选择特别有用。对于我现在正在做的事情有直接的帮助!这个问题被关闭真是胡闹。
2011年

1
尽管您的选择1确实可以完成您要传达的内容,但实际上我不得不回过头来仔细检查一下。问题问您是否可以通过引用?因此,实际上您应该在传递给该函数的玩具所在的范围内完成printlns的操作。通过这种方式,printlns的操作范围与COURSE的增量(实际上并不能证明这一点)相同增量后打印的本地副本将具有不同的值,但这并不意味着传递的引用将具有此值。同样,您的示例确实有效,但并不能证明这一点。
2013年

不,我认为这是正确的。我在上面的答案中的每个函数“ play()”都旨在代替原始的“ play()”函数,该函数还在递增和递增之前输出值。原始问题的主要内容是println(),它通过引用证明是合格的。
laslowh's

选择2需要进一步说明:必须将main中的呼叫站点更改toyNumber = temp.play(toyNumber);为可以正常工作的位置。
制造商史蒂夫(Steve)2014年

1
这是非常丑陋的,并带有点怪异的感觉……为什么这么基本的东西不是语言的一部分?
shinzou

30

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)

2
这不是问题的答案。如果它建议创建一个包含单个元素的整数数组,然后在method中修改该元素,它将回答这个问题play。例如mutableList[0] = mutableList[0] + 1;。正如欧内斯特·弗里德曼·希尔所说。
制造商史蒂夫(Steve)2014年

与非原语。对象的值不是参考。也许您想说:“参数值”是“参考”。引用按值传递。
vlakov

我正在尝试做类似的事情,但是在您的代码中,该方法private static void play(StringBuilder toyNumber)-如果它例如返回一个整数的public static int,您将如何调用它?因为我有一个返回数字的方法,但是如果我不在某个地方调用它,那么它就不会被使用。
frank17 2015年

18

做一个

class PassMeByRef { public int theValue; }

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


3
被我否决了。在许多级别上这都是错误的。Java是按价值传递的-始终如此。没有例外。
duffymo 2011年

14
@duffymo-您当然可以随意投票-但您考虑过OP的要求吗?他想通过引用传递和int-如果我按值传递对上述实例的引用,就可以实现。
Ingo

7
@duffymo:嗯?您错了,否则会误解这个问题。这 Java中执行OP请求的传统方式之一。
制造商史蒂夫(Steve)2014年

5
@duffymo:您的评论在很多层面上都是错误的。SO是关于提供有用的答案和评论的,您只是简单地拒绝了正确的答案,只是因为(我猜)它不适合您的编程风格和理念。您至少可以对原始问题提供更好的解释,甚至更好的答案吗?
paercebal,2015年

2
@duffymo就是我所说的:按值传递引用。您如何认为通过引用传递是通过减慢引用速度的语言完成的?
Ingo 2015年

11

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

在确实需要通过引用传递基元的情况下,人们有时会做的是将参数声明为基元类型的数组,然后将单元素数组作为参数传递。因此,您传递了一个引用int [1],并且在该方法中,您可以更改数组的内容。


1
否-所有包装器类都是不可变的 -它们表示一个固定值,一旦创建对象便无法更改。
欧内斯特·弗里德曼·希尔

1
“您不能在Java中通过引用传递基元”:OP似乎理解了这一点,因为它们将C ++(您可以)与Java进行了对比。
Raedwald 2014年

应当注意,Ernest所说的“所有包装器类都是不可变的”适用于JDK的内置Integer,Double,Long等。您可以使用自定义包装器类绕过此限制,就像Ingo的Answer所做的那样。
user3207158

10

为了快速解决方案,可以使用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

我使用并喜欢此解决方案,是因为它提供了不错的复合动作,并且更易于集成到已经或将来可能会使用这些原子类的并发程序中。
RAnders00 '01

2
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]);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.