在Java中,我能否以引用方式传递参数?

160

我希望语义类似于C#中的ref关键字。


你好。这个答案解决了我的问题:https://dev59.com/q2DVa4cB1Zd3GeqPeYlC#9324155 - Sayed Abolfazl Fatemi
8个回答

249

Java 看起来很混乱,因为所有东西都是按值传递。然而对于一个引用类型的参数(即不是原始类型的参数),它是通过按值传递引用本身的方式进行传递,因此它看起来像是按引用传递(并且人们经常声称它是)。但事实并非如此,如下所示:

Object o = "Hello";
mutate(o)
System.out.println(o);

private void mutate(Object o) { o = "Goodbye"; } //NOT THE SAME o!

将会在控制台上打印Hello。如果想让上面的代码打印Goodbye,那么可以使用显式引用,如下所示:

AtomicReference<Object> ref = new AtomicReference<Object>("Hello");
mutate(ref);
System.out.println(ref.get()); //Goodbye!

private void mutate(AtomicReference<Object> ref) { ref.set("Goodbye"); }

52
另外,如果你真的想让人困惑,那么可以使用长度为1的数组来创建一个引用。 - Christoffer
2
AtomicReference和相应的类(AtomicInteger)是执行此类操作的最佳选择。 - Naveen
5
AtomicReference是杀鸡焉用牛刀,您不一定需要在那里使用内存屏障,而且它可能会造成额外开销。JRuby因为在构建对象时使用了volatile变量而出现了性能问题。在我看来,使用数组作为临时参考选项已经足够好了,我已经见过它被使用多次。 - Elazar Leibovich
我不认为这很令人困惑,但这是正常的行为。值类型存在于堆栈上,引用类型存在于堆上,但对于引用类型,引用也存在于堆栈上。因此,您始终使用在堆栈上的值进行操作。我相信这里的问题是:是否可能传递从指向堆栈上某些其他值的堆栈上的引用?或者传递指向位于堆上的某个值的其他引用的引用? - eomeroff
这是很棒的信息。我一直在使用数组,但这似乎是为此而构建的东西。然而,如果在性能关键的循环中,我不知道这与数组的性能如何。 - steveh
@oxbow_lakes,Java库中是否有AtomicReference的非原子版本? - Pacerier

68

我能在Java中通过引用传递参数吗?

不行。

为什么?Java只有一种传递方法的模式:按值传递。

注意:

对于基本类型,这很容易理解:你得到一个值的副本。

对于其他所有类型,你得到一个引用的副本,这也被称为按值传递。

这就是全部内容:

enter image description here


28

在Java中,与ref相似的语言层面上没有任何东西。在Java中,只有按值传递的语义

出于好奇,您可以通过将对象包装在可变类中来实现类似于ref的语义:

public class Ref<T> {

    private T value;

    public Ref(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

    public void set(T anotherValue) {
        value = anotherValue;
    }

    @Override
    public String toString() {
        return value.toString();
    }

    @Override
    public boolean equals(Object obj) {
        return value.equals(obj);
    }

    @Override
    public int hashCode() {
        return value.hashCode();
    }
}

测试用例:

public void changeRef(Ref<String> ref) {
    ref.set("bbb");
}

// ...
Ref<String> ref = new Ref<String>("aaa");
changeRef(ref);
System.out.println(ref); // prints "bbb"

1
为什么不使用java.lang.ref.Reference呢? - Hardcoded
1
java.lang.ref.Reference没有set方法,它是不可变的。 - dfa
为什么不使用AtomicReference(非原始类型)?或者对于原始类型,使用AtomicSomething(例如:AtomicBoolean)呢? - Arvin
很遗憾,<T> 不接受原始类型。希望能够这样做:Ref<int>。 - jk7
1
@jk7 这真的很重要吗?必须使用int而不是Integerbool而不是Boolean等等?也就是说,包装类(如果您担心语法,它们会自动解包)不起作用吗? - Paul Stelian
显示剩余2条评论

22

来自《Java编程语言》的James Gosling:

"……在Java中,参数传递只有一种方式——按值传递——这使事情保持简单。"


3
语言神的宣示应该被宣布为答案。 - ingyhere
4
“语言神的声明应被宣布为答案”并不准确。无论Java的创造者认为什么,绝对的答案应该来自于语言规范中的引用。 - paercebal
2
实际上,这在JLS第8.4.1节中有所提及,而JLS引用Gosling作为第一作者:“当方法或构造函数被调用(§15.12),实际参数表达式的值将初始化新创建的参数变量,每个声明类型,在方法或构造函数体执行之前。” - ingyhere

11

我认为你不能这样做。你最好的选择可能是将想要通过引用传递的东西封装在另一个类实例中,并通过值传递(外部)类的引用。如果你明白我的意思...

也就是说,你的方法会改变它所传递的对象的内部状态,这个改变会对调用者可见。


9

Java始终按值传递。

当您传递一个基本类型时,它是该值的副本;当您传递一个对象时,它是该引用指针的副本。


5
另一种选择是使用数组,例如: void method(SomeClass[] v) { v[0] = ...; } 但是,1)必须在调用方法之前初始化数组,2)仍然无法以这种方式实现交换方法... JDK中使用了这种方式,例如在java.util.concurrent.atomic.AtomicMarkableReference.get(boolean[])中。

0

请查看我在https://dev59.com/q2DVa4cB1Zd3GeqPeYlC#9324155中的回答。

在那里,我使用了一个更简单的包装类思想。我不喜欢将setter/getter作为标准。当没有理由隐藏一个字段时,我会将其设置为“public”。特别是在这种情况下。

然而,这对于原始类型或多参数/类型返回值除外,其他都可以工作:

public class Ref<T> {
    public T val;
}

虽然,我想你可以只添加更多的类型参数。但是我认为创建一个适合目的的内部静态类会更容易:

public static class MyReturn {
    public String name;
    public int age;
    public double salary;
}

这将用于当您不需要它进行其他操作时。

MyReturn mRtn = new MyReturn();

public void myMethod(final MyReturn mRtn){
    mRtn.name = "Fred Smith";
    mRtn.age = 32;
    mRtn.salary = 100000.00;
}

System.out.println(mRtn.name + " " +mRtn.age + ": $" + mRtn.salary);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接