0

This was a question on an exam. Luckily I picked the right answer, but I still can't see why it's right.

Consider this program:

class D {
  protected C c;
  public D(C c) {
    this.c = new C(c);
  }
  public C getC() {
    return c;
  }
  public void setC(C c) {
    this.c = c;
  }
}
class C {
  protected String s;
  public C(String s) {
    this.s = s;
  }
  public C(C c) {
    this(c.s);
  }
  public String getS() {
    return s;
  }
  public void setS(String s) {
    this.s = s;
  }

  public static void main(String[] args) {
    C c1 = new C("1");
    C c2 = new C("2");
    D[] d = {
      new D(c1), new D(c1), new D(c2), new D(c2)
    };
    d[0] = d[3];
    c1.setS("3");
    String r = "";
    for (D i: d) {
      r += i.getC().getS();
    }
    System.out.println(r);
  }

}

It'll print 2122. I would expect 2322 however (I'm clearly wrong when you run the code). My reasoning behind that:

In the third line of the main method, four instances of D get initialized. The constructor of D makes a new instance of C. An instance of C has a String variable which points somewhere to a spot in the memory. Now the instance variable c, let's call it c3, of the object in d[1] has a instance variable (type String), let's call it s3, pointing to the same memory as the String s1, variable of c1.

So when we change s1, I'd expect the value of s3 also to change, since it's pointing to the same spot in the memory.

On a side note, if you change the constructor of D, see below, you'll get 2322 instead. Which I'd expect, since now the variable c3 in d[1] is pointing directly towards the memory location of c1.

public D(C c) {
  this.c = c;
}

My thoughts so far on the explanation (could be wrong):

  1. When initializing the instance variable s1/s3, new String objects get made (so far I assumed they were pointing towards "1" in the String pool, since the constructor of C makes it look that way)
  2. When changing s1, it's pointer will be redirected towards "3" in the String pool. Rather than "1" becoming "3" in the pool.

Could anyone explain this behaviour? What are the errors in my (faulty) reasoning?

Kevin Panko
  • 8,356
  • 19
  • 50
  • 61
flisk
  • 31
  • 2
  • possible duplicate of [Is Java "pass-by-reference" or "pass-by-value"?](http://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value) – njzk2 Aug 19 '14 at 17:44
  • 1
    @njzk2 it's not a duplicate. But that helps understanding the result of this program. – Luiggi Mendoza Aug 19 '14 at 17:46
  • @LuiggiMendoza `So when we change s1, I'd expect the value of s3 also to change, since it's pointing to the same spot in the memory.` To me the OP is confused by the way variables are passed and referred to in java. – njzk2 Aug 19 '14 at 17:54
  • @njzk2 yes. And providing that link as the only answer would not probably suffice to explain this behavior. That's why I provided an answer to explain this in detail. – Luiggi Mendoza Aug 19 '14 at 17:56

1 Answers1

2

This is not related to String pooling at all. Main answer: Is Java "pass-by-reference" or "pass-by-value"?

That's because D creates a new instance of C based on C#c. This mean that the instance of D#c is not the same instance as parameter C passed in constructor D, thus modifying that instance won't affect the current instance in D#c.


Re explaining all this in nice terms.

Here's what you're testing:

class Surprise {
    String item;
    public Surprise(String item) {
        this.item = item;
    }
    //this is called copy constructor
    //because you receive an object from the same class
    //and copy the values of the fields into the current instance
    //this way you can have a "copy" of the object sent as parameter
    //and these two object references are not tied by any mean
    public Surprise(Surprise another) {
        //here you just copy the value of the object reference of another#item
        //into this#item
        this.item = another.item;
    }
}

class Box {
    Surprise surprise;
    public Box(Surprise surprise) {
        //here you create a totally new instance of Surprise
        //that is not tied to the parameter surprise by any mean
        this.surprise = new Surprise(surprise);
    }

    public static void main(String[] args) {
        Surprise surprise1 = new Surprise("1");
        Surprise surprise2 = new Surprise("2");
        Box[] boxes = {
            new Box(surprise1),
            new Box(surprise1),
            new Box(surprise2),
            new Box(surprise2)
        };
        boxes[0] = boxes[3];
        //you update surprise1 state
        //but the state of Box#surprise in the boxes that used surprise1
        //won't get affected because it is not the same object reference
        surprise1.item = "3";
        //print everything...
        System.out.println("Boxes full of surprises");
        //this code does the same as the printing above
        for (Box box : boxes) {
            System.out.print(box.surprise.item);
        }
        System.out.println();
    }
}
Community
  • 1
  • 1
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • Thanks for the answer first of all. I know the general idea of pass by value/reference. This example would make sense to me if the constructor in Surprise would make a new String i.e.: this.item=new String(another.item), instead of re-using the same one of 'another'. I can see why it are different instances of Surprise, but I have trouble seeing why there would be different Strings. – flisk Aug 19 '14 at 17:50
  • @flisk because you only copy the value of the reference of the String. So, then you update `surprise1#item` and affects only to that variable. The same could happen with `int`, `Integer` or even a mutable class because you associate `surprise1#item` with a totally new reference. – Luiggi Mendoza Aug 19 '14 at 17:52
  • Ah, so basically, suprise1#item changes it's pointer? Also intersesting, put System.out.println(d[1].getC().getS()==c1.s); the line after d[0]=d[3]. There you can see, the strings are still the same(just before the change), because it prints true. – flisk Aug 19 '14 at 18:05
  • Ok nice, I think I fully understand it now. I've replaced all String with StringBuffer and changed c1 this way: c1.setS(c1.s.replace(0, 1, "3")); and the program did what I expected. Because StringBuffer is mutable, it doesn't need to make a new instance (i.e. change pointer) when it's value is changed. This way it printed "2322". Thanks! – flisk Aug 19 '14 at 20:20
  • @flisk note that you're modifying the state. If you use `c1.s = new StringBuffer("3");` then this will print "2122" again. Again, this is about passing by value. And if you use `new` keyword it won't matter if your object is immutable or not. – Luiggi Mendoza Aug 19 '14 at 20:22
  • Yes, that's why I used a mutator method. Using 'new' creates a new object. When that happens, the pointer associated with c1 would change towards the new object's storage adress in the memory. The pointer assosiated with the String of d[1] would still be pointing at the storage adress of unchanged object "1", so you'd get "2122". I've learned alot from this question :D – flisk Aug 19 '14 at 20:33
  • @flisk now you reflect that you understood this. Cool! – Luiggi Mendoza Aug 19 '14 at 20:34