如何在Java中进行“引用传递”?

35
  1. 如何在Java中实现“按引用传递”?(假设我们使用这个术语的方式与自1960年以来同行评审的计算机科学文献中使用的方式相同;请参见这个维基百科页面以获取简单的解释。)

  2. 由于Java不支持指针,如何在Java中像在C和C++中一样通过引用调用函数?


2
从目前的答案来看,我猜这个问题存在歧义。你能否提供一个 C 或 C++ 的例子来说明你想要实现什么? - Björn Pollex
1
请查看我之前的回答:https://dev59.com/_XA65IYBdhLWcg3wzx9l#3624554 - Sean Patrick Floyd
@StephenC 将参数声明为引用类型的 C++ 函数传递是最经典的按引用传递方式(这是当今常用语言中极少数的方法之一;我能想到的唯一其他例子是 C# 中的 refout 参数,这也可能对调用者施加限制)。C++ 的引用 "类型" 很奇怪,因为它使用普通的变量声明语法,甚至可以声明引用类型的结构成员,但它并不在存储分配意义上创建 "对象"。 - undefined
我不认为大多数人会理解“按引用调用函数”意味着“将函数作为高阶函数按引用传递,然后调用它” - 尽管英语当然允许这种解释,但在我看来,这不是根据原始问题的确切原文的一个合理解释。 - undefined
@KarlKnechtel - 真的不再重要了。我已经表达了我认为OP在两年前提问时的意思。并且基于那个理解回答了(两年前)。OP从未费心澄清。请随意忽略我回答中你认为涉及了对他问题错误解读的部分。 - undefined
显示剩余2条评论
12个回答

43

Java中不能真正实现按引用传递参数。Java将所有东西都按值传递,包括引用。但是你可以使用容器对象来模拟它。

在方法参数中使用以下任何内容:

  • 数组
  • 集合(Collection)
  • AtomicXYZ类

如果在方法中更改其内容,则更改后的内容将在调用上下文中可用。


抱歉,您似乎是指通过引用调用方法。这在Java中也不可能实现,因为在Java中方法不是一级公民。这可能会在JDK 8中发生改变,但目前,您将需要使用接口来解决此限制。

public interface Foo{
    void doSomeThing();
}

public class SomeFoo implements Foo{
    public void doSomeThing(){
       System.out.println("foo");
    }
}

public class OtherFoo implements Foo{
    public void doSomeThing(){
       System.out.println("bar");
    }
}

在你的代码中使用Foo,这样你就可以轻松地将SomeFoo替换为OtherFoo


2
我认为你想说的是按引用传递。我认为OP是在问如何使用对该方法的引用来调用该方法。;) - Peter Lawrey
1
对于涵盖OP问题所有可能含义的全面回答,给予+1! - Oliver Charlesworth

14
在Java中无法实现"按引用调用"。没有任何方法能够接近这个概念。在函数调用中通过值传递引用并不等同于"按引用调用"。正确的术语应该是"按值调用"。
(真正的"按引用调用"允许你做这样的事情:
void swap(ref int i, ref int j) { int tmp = *i; *i = *j; *j = tmp }

int a = 1;
int b = 2;
swap(a, b);
print("a is %s, b is %s\n", a, b);  // -> "a = 2, b = 1"

你在Java中根本做不到那样的事情。
由于Java不支持指针...
尽管从技术上讲这是正确的,但这并不会妨碍你想要做的事情。
Java确实支持引用,它在最重要的方面类似于指针。不同之处在于,你不能通过对引用进行算术运算、将其转换为整数类型等方式将其视为内存地址。
此外,在Java中无法创建对变量的引用,因为这个概念在Java或JVM架构中不存在。这就是为什么在Java中无法实现真正的“按引用传递”的原因。
这部分问题有歧义。如果你再次问的是在Java中是否可以像在C和C++中那样通过引用调用函数,答案仍然是“不可能”,请参考上面的回答。
如果你实际上是在谈论涉及对函数的引用的调用(或者在C/C++中称为函数指针)...
重要说明:这并不是“按引用调用”。这是通过对函数的引用进行调用。一旦你找出了调用的函数,就会应用正常的Java“按值调用”的语义。对于将函数引用作为参数传递给另一个函数调用的情况,也是如此:引用是按值传递的。
在Java 8之前,不支持将函数作为值进行引用。因此,你不能将它们作为参数传递,也不能将它们赋值给变量。但是,你可以定义一个具有一个实例方法的类,并使用该类的实例代替函数引用。其他答案给出了这种方法的示例。
从Java 8开始,可以使用::语法支持方法引用。有4种方法引用的类型:
  • 引用静态方法:ContainingClass::staticMethodName
  • 引用特定对象的实例方法:containingObject::instanceMethodName
  • 引用特定类型的任意对象的实例方法:ContainingType::methodName
  • 引用构造函数:ClassName::new
方法和构造函数引用(以及lambda表达式)都是按值传递的,就像Java中的其他所有内容一样。
1 - 为了简化,我在Java上下文中使用“function”作为“method”、“constructor”或“lambda”的简写。 2 - 从教学的角度来看,不幸的是C和C++都使用“&”符号来表示引用传递和函数指针。然而,从语言设计的角度来看,我并不反对这种设计选择。

2
有些人可能会发现这篇博客文章很有用:http://javadude.com/articles/passbyvalue.htm - Sandman

3
通常在Java中,这个问题是通过使用接口来解决的:
Collections.sort(list, new Comparator() {

  @Override
  public int compareTo(Object o1, Object o2) {
  /// code
  }

});

你唯一能修改 list 的方式是通过 list 的方法。如果参数是 int,接口无法帮助。OP 指的是能够通过形式参数修改实际参数。 - jbruni

2
package jgf;

public class TestJavaParams {

 public static void main(String[] args) {
    int[] counter1 = new int[1];
    counter1[0] = 0;
    System.out.println(counter1[0]);
    doAdd1(counter1);
    System.out.println(counter1[0]);
    int counter2 = 0;
    System.out.println(counter2);
    doAdd2(counter2);
    System.out.println(counter2);   
  }

  public static void doAdd1(int[] counter1) {
    counter1[0] += 1;
  }

  public static void doAdd2(int counter2) {
    counter2 += 1;
  }
}

输出结果如下:
0
1
0
0

1
最好的方法是使用执行操作的接口。
// Pass a Runnable which calls the task() method
executor.submit(new Runnable() {
    public void run() {
        task();
    }
});

public void task() { }

您可以使用反射来调用任何方法

Method method = MyClass.class.getMethod("methodToCall", ParameterType.class);
result = method.invoke(object, args);

1
在Java中,除了原始类型,将对象传递给方法/函数总是按引用传递的。请参见 Oli Charlesworth的答案以获取示例。
对于原始类型,您可以使用数组进行包装: 例如:
public void foo(int[] in){
  in[0] = 2;
}

int[] byRef = new int[1];
byRef[0] = 1;
foo(byRef);
// ==> byRef[0] == 2.

将基本类型包装在数组中是可行的。请参见带有jgf包的代码: - JGFMK
2
很多人会混淆“引用调用”和“对象引用通过值传递”的概念。在Java中,它总是“按值调用”。 - jAckOdE

1

Java仅允许按值调用。但是,通过按值传递对象的引用可以将这些引用传递到被调用的函数中。如果在被调用的函数中使用这些引用来操纵对象的数据,则此更改也将在调用函数中可见。我们不能像指针一样对这些引用进行指针算术运算,但引用可以使用句点运算符指向对象的数据,该运算符在C/C ++中函数为'*'运算符。


0

来自Herbert Shildt的《Java完全参考手册》第9版:“当您将对象传递给方法时,情况会发生巨大变化,因为对象是通过有效的按引用调用方式传递的。请记住,当您创建一个类类型的变量时,您只是创建了一个对对象的引用。因此,当您将此引用传递给方法时,接收它的参数将引用与参数相同的对象。这实际上意味着对象表现得好像是通过按引用调用方式传递给方法的。在方法内部对对象所做的更改确实会影响作为参数使用的对象。”

package objectpassing;

public class PassObjectTest {

    public static void main(String[] args) {
        Obj1 o1 = new Obj1(9);
        System.out.println(o1.getA());
        o1.setA(3);
        System.out.println(o1.getA());
        System.out.println(sendObj1(o1).getA());
        System.out.println(o1.getA());
    }

public static Obj1 sendObj1(Obj1 o)
{
    o.setA(2);
    return o;
}


}

class Obj1
{
    private int a;

    Obj1(int num)
    {
        a=num;
    }

    void setA(int setnum)
    {
        a=setnum;
    }

    int getA()
    {
        return a;
    }
}

操作: 9 3 2 2

最终调用getA()方法显示原始对象字段'a'在调用公共静态Obj1 sendObj1(Obj1 o)方法时已更改。


我现在拥有大约5本关于Java的书。坦率地说,大多数作者写书很糟糕,他们对Java语言的掌握最多也是可疑的。 - Dave Black
写那段引用文字的作者并不理解什么是“按引用调用”。按引用调用意味着传递一个变量的引用。说传递变量的引用和对象的引用“实际上是一回事”,就好像说汽车和自行车“实际上是一回事”一样荒谬。 - Stephen C

0
JAVA允许使用对象进行内部引用。 当写下这个赋值语句时: Obj o1 = new Obj(); Obj o2=o1; 它会使得o1和o2指向同一个地址,操作任何一个对象的空间都会反映在另一个对象上。 因此,可以使用数组、集合等方法来实现这一点,如上所述。

这段话几乎难以理解,似乎并没有提供任何新的信息。我认为它可能是想解释为什么“容器对象”技巧有效,但实际上并没有解释性的价值。 - undefined

0

2
在这个过程中,有一件非常重要的事情需要注意:传递的对象的引用 - Andrew Barber

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