将对象传递给java中的方法似乎是通过引用(而Java是通过val)

Posted

技术标签:

【中文标题】将对象传递给java中的方法似乎是通过引用(而Java是通过val)【英文标题】:Passing object to method in java appears to be by reference (and Java is by val) 【发布时间】:2011-04-19 02:20:09 【问题描述】:

我认为当您将对象传递给 Java 中的方法时,它们应该是按值传递的。

这是我的代码:

public class MyClass
    int mRows;
    int mCols;
    Tile mTiles[][];     //Custom class

    //Constructor
    public MyClass(Tile[][] tiles, int rows, int cols) 
        mRows = rows;
        mCols = cols;

        mTiles = new Tile[mRows][mCols];
        for (int i=0; i < mRows; i++) 
            for (int j=0; j < mCols; j++) 
                mTiles[i][j] = new Tile();
                mTiles[i][j] = tiles[i][j];
            
        
    

此时,对mTiles 对象的任何更改都会反映回tiles 对象。任何想法如何解决这个问题?

谢谢大家。

【问题讨论】:

已经问过:***.com/questions/514478/… 【参考方案1】:

这是通过 val,但是在谈论对象时,传递的是引用的值,因此,如果您的对象是可变的(如在您的示例中)通过引用副本修改对象,将修改底层对象.

要解决它,您必须复制对象并传递新的参考值。

在您的代码中,您在“tiles”和“mTiles”中使用相同的参考值

mTiles[i][j] = new Tile(); // <---this line is useless by the way
mTiles[i][j] = tiles[i][j] // because you then assign the same value here

你必须像这样创建一个新的:

mTiles[i][j] = new Tile(tiles[i][j]);

或者

mTiles[i][j] = tiles[i][j].clone();

或者

mTiles[i][j] = Tile.newFrom( tiles[i][j] );

或者类似的东西,所以你可以创建一个新的,而不是使用相同的 ref。

我希望这会有所帮助。

编辑

当你在pass-by-val中改变ref时,原来的不受影响,即:

String s = "hi"; 
changeIt(s);
....
void changeIt(String s) 
    s = "bye" // assigning to the copy a new ref value of "bye"

在这之后,原来的“hi”还是“hi”。在 pass-by-ref 中它将是“再见”

这里有一些链接:

http://www.javaranch.com/campfire/StoryPassBy.jsp

Can someone explain to me what the reasoning behind passing by "value" and not by "reference" in Java is?

Pass by reference or pass by value?

http://www.javaworld.com/javaworld/javaqa/2000-05/03-qa-0526-pass.html

http://academic.regis.edu/dbahr/GeneralPages/IntroToProgramming/JavaPassByValue.htm

http://www.jguru.com/faq/view.jsp?EID=430996

最终:

http://www.google.com.mx/search?sourceid=chrome&ie=UTF-8&q=java+is+pass+by+value

【讨论】:

我不确定我是否理解。那和pass-by-ref有什么区别。 酷,谢谢。我相信我已经掌握了这个概念,但是,您的解决方案都不起作用……可能是因为我的类对象没有实现任何这些方法。我确实尝试重载 Tile 构造函数,以使用您的第一个建议但没有成功。我感到非常沮丧,这是如此困难。克隆方法是否易于添加到我的课程中?然后我要在深与浅的斗争中挣扎……/叹息。【参考方案2】:

你不传递对象,你传递一个对对象的引用,它是按值复制的。

【讨论】:

【参考方案3】:

以下是 Java 参数传递语义的一些诊断示例:

对于对象类型:

void changeIt(String s) 
    s = "bye";


String s = "hi"; 
changeIt(s);
System.out.println(s);  // would print "bye" for call by reference
                        // but actually prints "hi"

对于原始类型:

void changeIt(int i) 
    i = 42;


int i = 0; 
changeIt(i);
System.out.println(i);  // would print "42" for call by reference
                        // but actually prints "0"

事实上,在这两个例子中,changeIt 方法中的赋值只影响各自方法的局部变量,实际打印的值将是“hi”和“0”。

编辑

而且由于 OP 仍然不相信我......这里有一个诊断示例,表明 Java 也是可变对象的按值调用。

public class Mutable 
    int field;
    public Mutable(int field)  this.field = field; 
    public void setField(int field)  this.field = field; 
    public int getField()  return field; 


void changeIt(Mutable m, Mutable m2) 
    m = m2;  // or 'm = new Mutable(42);' or 'm = null;'


Mutable m = new Mutable(0); 
Mutable m2 = new Mutable(42); 
changeIt(m, m2);
System.out.println(m.getField());  
                        // would print "42" for call by reference
                        // but actually prints "0"

相比之下,此示例将为引用调用和按值调用语义给出相同的答案。它不能证明任何关于参数传递语义的东西。

void changeIt2(Mutable m) 
    m.setField(42);


Mutable m = new Mutable(); 
changeIt2(m);
System.out.println(m.getField());  
                        // prints "42" for both call-by reference
                        // and call-by-value

相信我,我已经使用 Java 编程超过 10 年,并且我在大学阶段教过比较编程语言课程。

【讨论】:

谢谢。根据另一个答案,对象的行为似乎有所不同。 实际上,(真正的)Java 的参数传递语义对于对象和原始类型是相同的,我的示例代码清楚地证明了这一点。此外,另一个答案没有说明对象和原始类型的行为不同。如果你认为它暗示了这一点,那么你显然不明白它在说什么。 字符串是不可变的,因此其行为与其他可变对象不同。我认为您的示例不适用于可变对象。 我真的很想相信你,因为它会让我的生活变得更轻松,我不会在我的程序中处于我的位置。请随时帮助我纠正我的程序,因为它不能按照您所说的方式工作。我很确定另一个人在这方面是正确的,这意味着我并没有完全传递值,而是传递了引用的值。 奥斯卡的答案是正确的,我也是。问题是仍然不明白我们如何才能同时是正确的。不,我不会为你编写代码。顺便说一句,Oscar 对问题的建议解决方案都是合理的,但不是复制粘贴的答案。【参考方案4】:

事实上,Java 是按值传递的。但它也有“引用数组”!这就是为什么许多人认为 Java 对对象(至少对数组)是按引用传递,而对基元来说只是按值传递。

这是一个简短的测试:

String[] array = new String[10];
array[0] = "111";
ArrayList one = new ArrayList(); 
one.add(array);
ArrayList two = (ArrayList) one.clone(); //Alternate with: ArrayList two = one;
String[] stringarray1 = (String[]) one.get(0);
String[] stringarray2 = (String[]) two.get(0);
System.out.println("Array: "+one+" with value: "+stringarray1[0]);
System.out.println("Array: "+one+" with value: "+stringarray2[0]);
array[0] = "999";
String[] stringarray3 = (String[]) one.get(0);
String[] stringarray4 = (String[]) two.get(0);
System.out.println("Array: "+one+" with value: "+stringarray3[0]);
System.out.println("Array: "+two+" with value: "+stringarray4[0]);

无论你是克隆还是使用 =,System.out.print 都会是这样的:

Array: [[Ljava.lang.String;@addbf1] with value: 111
Array: [[Ljava.lang.String;@addbf1] with value: 111
Array: [[Ljava.lang.String;@addbf1] with value: 999
Array: [[Ljava.lang.String;@addbf1] with value: 999

这证明了克隆和数组是有害的组合,因为数组只存储指针!我仍然需要测试这是否也适用于无数组对象......因为这意味着 Java 总是“按引用存储”(并且“克隆”功能对于任何对象来说都只是一个糟糕的笑话包含数组),而只有基元是真实值,没有引用!

因为我们知道逻辑:storage-by-reference x pass-by-value == "storage by value x pass-by-reference"(对于对象!),

虽然我们从学校开始就已经知道:按值存储 x 按值传递(对于基元)

那么,我们是否都被我们的编程老师欺骗了(即使是在大学里)?也许吧,但他们至少没有犯任何逻辑错误……所以这不是谎言,只是错了。

编辑

我用一个类写了和上面一样的代码,首先是数据结构:

public class Foobar implements Cloneable 
    String[] array;

    public Foobar() 
        this.array = new String[10];
    

    public String getValue()
        return array[0];
    

    public String[] getArray()
        return array;
    

    public void setArray(String[] array)
        this.array = array;
    

    @Override
    public Object clone()
        try
            Foobar foobar = (Foobar) super.clone();
            foobar.setArray(array);
            return foobar;
        
        catch(Exception e)
            return null;
        
    

现在是控制器:

String[] array = new String[10];
array[0] = "111";
Foobar foo1 = new Foobar();  
foo1.setArray(array);
Foobar foo2 = foo1; //Alternation: Foobar foo2 = (Foobar) foo1.clone();  
System.out.println("Instance: "+foo1.getArray()+" with value: "+foo1.getValue());
System.out.println("Instance: "+foo2.getArray()+" with value: "+foo2.getValue());
array[0] = "999";
System.out.println("Instance: "+foo1.getArray()+" with value: "+foo1.getValue());
System.out.println("Instance: "+foo2.getArray()+" with value: "+foo2.getValue());

测试结果总是这样——无论我使用 = 还是 clone():

Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 111
Instance: [Ljava.lang.String;@42e816 with value: 999
Instance: [Ljava.lang.String;@42e816 with value: 999

现在我的口袋里有了“主阵列”,我可以立即统治所有物体! (这真的不是一件好事)

我一直对 Java 数组感到不安,但我说不出它是什么。现在我知道了,我感觉很好,因为从那以后我只使用数组作为对象的容器......只是发现自己非常惊讶它们在 php 等脚本语言中的重要性!

不过,Java 数组非常适合线程之间的同步,因为您可以轻松地传递它们并且仍然可以访问共享值。但是来自 PHP 或 C++ 或其他地方的程序员可能确实会遇到 Java 数组的一些问题。 ;D

哦,我喜欢这篇文章:http://javadude.com/articles/passbyvalue.htm

更新:我找到了一个很好的解决方案来复制任何包含数组的对象,请在此处查看我的评论:Bug in using Object.clone()

【讨论】:

以上是关于将对象传递给java中的方法似乎是通过引用(而Java是通过val)的主要内容,如果未能解决你的问题,请参考以下文章

Java/Android - 在资源打开的情况下将对象传递给方法

检测何时将对象传递给 C++ 中的新线程?

将对象传递给 Java 中的不同库

C++ 将对象传递给函数

.NET Remoting,将对象传递给方法

创建将对象传递给实例方法的 NSPredicate