Java有类似于C#的ref和out关键字吗?

142

类似以下内容:

参考示例:

void changeString(ref String str) {
    str = "def";
}

void main() {
    String abc = "abc";
    changeString(ref abc);
    System.out.println(abc); //prints "def"
}

输出示例:

void changeString(out String str) {
    str = "def";
}

void main() {
    String abc;
    changeString(out abc);
    System.out.println(abc); //prints "def"
}

2
可能是在Java中我可以通过引用传递参数吗?的重复问题。 - Robert Harvey
5
按照要求翻译如下:在我看来,你并没有错过什么。在C#中,我只有在使用TryParse()这样的模式时才会使用refout,其中方法返回一个布尔结果,通过使用refout是获取解析值的唯一方法。 - Robert Harvey
5
猜猜看,这正是我需要使用的!;) - devoured elysium
2
另一种方法是返回一个复合对象,其中包含状态和可空值。但我承认这有点像鲁布·戈尔德伯格式的方式。 - Robert Harvey
2
如果有一个预定义的可用对象(即元组),返回一个复合对象是没有问题的。但是等等,这需要非擦除泛型与基本类型一起工作才能高效 :) - Pavel Minaev
显示剩余6条评论
7个回答

126

不,Java没有像C#的refout关键字来进行按引用传递的方法。

在Java中只能进行按值传递。即使是引用也是按值传递。详见Jon SkeetJava参数传递方面的说明。

要实现类似于refout的功能,您需要将参数包装在另一个对象内,并将该对象引用作为参数传递。


5
需要进一步扩展。只能传递基本类型(int、short、char等)作为值。而且,没有“out”。 - Corey Sunwold
18
这并非完全正确。如果你传递一个数组或者一个类,其引用是按值传递的。你可以更改数组或对象的内部,这将反映在调用者中。 - Romain Hippeau
13
除非您使用ref,否则我无法给出更多上下文。 - Robert Harvey
2
@fearofawhackplanet: "引用参数需要在声明和调用时使用ref修饰符 - 这意味着当您通过引用传递某些内容时,它总是很清晰的。" http://www.yoda.arachsys.com/csharp/parameters.html - Mark Byers
5
就CLR而言,你确实是通过值传递一个托管引用(T&)。但C#有自己的术语,它明确不包括ref T类型的值——从C#的角度来看,ref严格地说是一个参数修饰符,因此谈到“通过值传递引用”就没有意义了。 - Pavel Minaev
显示剩余6条评论

32

直接回答:不行

但是您可以通过包装器模拟 引用

然后执行以下操作:

void changeString( _<String> str ) {
    str.s("def");
}

void testRef() {
     _<String> abc = new _<String>("abc");
     changeString( abc );
     out.println( abc ); // prints def
}

输出

void setString( _<String> ref ) {
    str.s( "def" );
}
void testOut(){
    _<String> abc = _<String>();
    setString( abc );
    out.println(abc); // prints def
}

而基本上包括任何其他类型,例如:

_<Integer> one = new <Integer>(1);
addOneTo( one );

out.println( one ); // May print 2

47
我从未说过,“你可以用这种优雅的方式做它” :P - OscarRyz
那么为什么以下代码不起作用:private static void ParseLine(String newline, String[] aWrapper, Integer[] bWrapper) { StringTokenizer st = new StringTokenizer(newline); aWrapper[0] = st.nextToken(); bWrapper[0]= new Integer(st.nextToken()); }ParseLine(newline, new String[] {a}, new Integer[] {b}); - Elad Benda
虽然这很恶心,但我仍然会点赞,因为这是我目前能想到的Java中最“干净”的解决问题的方式... - Pangamma
为什么在基类库中已经有一个类AtomicReference<T>的情况下,您还要为此引入一个新类_呢? - Dennie

9
实际上,据我所知,在Java语言中并没有ref或out关键字的等效物。然而,我刚刚将一个使用out参数的C#代码转换为了Java,并会告诉你我刚刚做了什么。您应该将任何对象包装到包装类中,并像下面这样传递封装在包装对象实例中的值;

使用包装器的简单示例

这是包装器类

public class Wrapper {
    public Object ref1; // use this as ref
    public Object ref2; // use this as out

    public Wrapper(Object ref1) {
        this.ref1 = ref1;
    }
}

这里是测试代码:

public class Test {

    public static void main(String[] args) {
        String abc = "abc";
        changeString(abc);
        System.out.println("Initial object: " + abc); //wont print "def"

        Wrapper w = new Wrapper(abc);
        changeStringWithWrapper(w);
        System.out.println("Updated object: " + w.ref1);
        System.out.println("Out     object: " + w.ref2);
    }

    // This won't work
    public static void changeString(String str) {
        str = "def";
    }

    // This will work
    public static void changeStringWithWrapper(Wrapper w) {
        w.ref1 = "def";
        w.ref2 = "And this should be used as out!";
    }

}

一个现实世界的例子

使用out参数的C#.NET方法

这里有一个使用out关键字的C#.NET方法;

public bool Contains(T value)
{
    BinaryTreeNode<T> parent;
    return FindWithParent(value, out parent) != null;
}

private BinaryTreeNode<T> FindWithParent(T value, out BinaryTreeNode<T> parent)
{
    BinaryTreeNode<T> current = _head;
    parent = null;

    while(current != null)
    {
        int result = current.CompareTo(value);

        if (result > 0)
        {
            parent = current;
            current = current.Left;
        }
        else if (result < 0)
        {
            parent = current;
            current = current.Right;
        }
        else
        {
            break;
        }
    }

    return current;
}

使用out参数的C#代码对应的Java代码

借助包装类,下面是这个方法的Java版本

public boolean contains(T value) {
    BinaryTreeNodeGeneration<T> result = findWithParent(value);

    return (result != null);
}

private BinaryTreeNodeGeneration<T> findWithParent(T value) {
    BinaryTreeNode<T> current = head;
    BinaryTreeNode<T> parent = null;
    BinaryTreeNodeGeneration<T> resultGeneration = new BinaryTreeNodeGeneration<T>();
    resultGeneration.setParentNode(null);

    while(current != null) {
        int result = current.compareTo(value);

        if(result >0) {
            parent = current;
            current = current.left;
        } else if(result < 0) {
            parent = current;
            current = current.right;
        } else {
            break;
        }
    }

    resultGeneration.setChildNode(current);
    resultGeneration.setParentNode(parent);

    return resultGeneration;
}

包装类

这段Java代码中使用的包装类如下所示:

public class BinaryTreeNodeGeneration<TNode extends Comparable<TNode>>  {

    private BinaryTreeNode<TNode>   parentNode;
    private BinaryTreeNode<TNode>   childNode;

    public BinaryTreeNodeGeneration() {
        this.parentNode = null;
        this.childNode = null;
    }

    public BinaryTreeNode<TNode> getParentNode() {
        return parentNode;
    }

    public void setParentNode(BinaryTreeNode<TNode> parentNode) {
        this.parentNode = parentNode;
    }

    public BinaryTreeNode<TNode> getChildNode() {
        return childNode;
    }

    public void setChildNode(BinaryTreeNode<TNode> childNode) {
        this.childNode = childNode;
    }

}

see former answers. - pashute
给一个详细的回答打负分是很有趣的。我查看了SO,没有找到一个带有演示代码的精确答案,这就是为什么我尽我所能记得的写下了这个答案。如果你等待一个单一的答案并且对每一个其他的答案都打负分,那么SO应该禁止除了得到问题所有者确认的答案之外的所有答案。 - Levent Divilioglu
好的,如果您编辑答案,我可以删除负面投票。因此,请阅读其他答案并解释为什么您不想使用那些解决方案。一个包装器(特别是一个REF和OUT包装器只是为了好玩)。5个人给出了简短和完整的答案__带有示例__,只有Eyal基本上写道:“不,你不能”。 - pashute
1
这个答案看起来很好。它与顶部答案类似,但是提供了如何在Java中使用包装类的完整详细示例。 - hubatish

9

Java以值传递方式传递参数,没有任何机制允许按引用传递。这意味着每当传递参数时,它的会被复制到处理调用的堆栈帧。

这里所说的需要一点澄清。在Java中,我们有两种变量-原始类型和对象。原始类型的值是原始类型本身,而对象的值是其引用(而不是被引用的对象的状态)。因此,对方法内部值的任何更改都只会更改堆栈中值的副本,并且不会被调用者看到。例如,没有任何方法可以实现一个真正的交换方法,它接收两个引用并交换它们(而不是它们的内容!)。


6

和许多人一样,我需要将一个C#项目转换成Java。我在网上没有找到关于outref修饰符的完整解决方案。但是,我能够利用我找到的信息,并扩展它来创建我的自己的类以满足要求。我想要区分代码清晰度的refout参数。通过以下类,这是可能的。希望这些信息可以为其他人节省时间和精力。

下面的代码中包含一个示例。

//*******************************************************************************************
//XOUT CLASS
//*******************************************************************************************
public class XOUT<T>
{
    public XOBJ<T> Obj = null;

    public XOUT(T value)
    {
        Obj = new XOBJ<T>(value);
    }

    public XOUT()
    {
      Obj = new XOBJ<T>();
    }

    public XOUT<T> Out()
    {
        return(this);
    }

    public XREF<T> Ref()
    {
        return(Obj.Ref());
    }
};

//*******************************************************************************************
//XREF CLASS
//*******************************************************************************************

public class XREF<T>
{
    public XOBJ<T> Obj = null;

    public XREF(T value)
    {
        Obj = new XOBJ<T>(value);
    }

    public XREF()
    {
      Obj = new XOBJ<T>();
    }

    public XOUT<T> Out()
    {
        return(Obj.Out());
    }

    public XREF<T> Ref()
    {
        return(this);
    }
};

//*******************************************************************************************
//XOBJ CLASS
//*******************************************************************************************
/**
 *
 * @author jsimms
 */
/*
    XOBJ is the base object that houses the value. XREF and XOUT are classes that
    internally use XOBJ. The classes XOBJ, XREF, and XOUT have methods that allow
    the object to be used as XREF or XOUT parameter; This is important, because
    objects of these types are interchangeable.

    See Method:
       XXX.Ref()
       XXX.Out()

    The below example shows how to use XOBJ, XREF, and XOUT;
    //
    // Reference parameter example
    //
    void AddToTotal(int a, XREF<Integer> Total)
    {
       Total.Obj.Value += a;
    }

    //
    // out parameter example
    //
    void Add(int a, int b, XOUT<Integer> ParmOut)
    {
       ParmOut.Obj.Value = a+b;
    }

    //
    // XOBJ example
    //
    int XObjTest()
    {
       XOBJ<Integer> Total = new XOBJ<>(0);
       Add(1, 2, Total.Out());    // Example of using out parameter
       AddToTotal(1,Total.Ref()); // Example of using ref parameter
       return(Total.Value);
    }
*/


public class XOBJ<T> {

    public T Value;

    public  XOBJ() {

    }

    public XOBJ(T value) {
        this.Value = value;
    }

    //
    // Method: Ref()
    // Purpose: returns a Reference Parameter object using the XOBJ value
    //
    public XREF<T> Ref()
    {
        XREF<T> ref = new XREF<T>();
        ref.Obj = this;
        return(ref);
    }

    //
    // Method: Out()
    // Purpose: returns an Out Parameter Object using the XOBJ value
    //
    public XOUT<T> Out()
    {
        XOUT<T> out = new XOUT<T>();
        out.Obj = this;
        return(out);
    }

    //
    // Method get()
    // Purpose: returns the value
    // Note: Because this is combersome to edit in the code,
    // the Value object has been made public
    //
    public T get() {
        return Value;
    }

    //
    // Method get()
    // Purpose: sets the value
    // Note: Because this is combersome to edit in the code,
    // the Value object has been made public
    //
    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();
    }
}

2

-1

Java没有标准的方法来实现它。大多数交换将在打包在类中的列表上进行。但是有一种非官方的方法可以实现:

package Example;

import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;



 public class Test{


private static <T> void SetValue(T obj,T value){
    try {
        Field f = obj.getClass().getDeclaredField("value");
        f.setAccessible(true);
        f.set(obj,value);
        } catch (IllegalAccessException | IllegalArgumentException | 
            NoSuchFieldException | SecurityException ex) {
            Logger.getLogger(CautrucjavaCanBan.class.getName()).log(Level.SEVERE, 
       null, ex);
        }
}
private  static  void permutation(Integer a,Integer b){
    Integer tmp = new Integer(a);
    SetValue(a, b);
    SetValue(b, tmp);
}
 private  static  void permutation(String a,String b){
    char[] tmp = a.toCharArray();
    SetValue(a, b.toCharArray());
    SetValue(b, tmp);
}
public static void main(String[] args) {
    {
        Integer d = 9;
        Integer e = 8;
        HoanVi(d, e);
        System.out.println(d+" "+ e);
    }
    {
        String d = "tai nguyen";
        String e = "Thai nguyen";
        permutation(d, e);
        System.out.println(d+" "+ e);
    }
}

}

2
这并不是为参数提供引用语义,而只是使用反射来改变一个被设计为不可变的对象,这是一个非常糟糕的想法,并且仍然不能为参数提供引用语义。 - Servy

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