2

If objects' non-primitive containing fields are passed as object handles that reference that field's object, is it susceptible to being changed after the fact if the originally passed field is updated/changed?

public class MutableDog
{
    public String name;
    public String color;

    public MutableDog(String name, String color)
    {
        this.name = name;
        this.color = color;
    }
}

public class ImmutableDog // are fields of these objects truly safe from changing?
{
    private final String name;
    private final String color;

    public ImmutableDog(MutableDog doggy)
    {
        this.name = doggy.name;
        this.color = doggy.color;
    }

    public String getColor()
    {
        return this.color;
    }
}

public static void main(String[] args)
{
    MutableDog aMutableDog = new MutableDog("Courage", "Pink");

    ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);

    aMutableDog.color = "Pink/Black";

    anImmutableDog.getColor().equals(aMutableDog.color); // true or false?
}

Essentially, is ImmutableDog truly immutable? In the example, Strings are used. Would using a mutable object, such as a Collection, make a difference?

This question is in response to this answer.

Suman
  • 818
  • 6
  • 17
Snap
  • 492
  • 5
  • 14
  • String literals are always stored in memory because "String" datatype is immutable in java. So the output of `nImmutableDog.getColor().equals(aMutableDog.color)` is `false`. But instead of String, if we have mutable class, then it will also change because it is passed by reference. – Suman Sep 03 '20 at 06:32

3 Answers3

2

ImmutableDog is truly immutable, even though it can receive strings from a mutable object. This is because Strings are immutable. This also demonstrate one of the great benefits of immutability - you can pass immutable objects around without worrying that they will suddenly change.

You might be thinking that the fields in ImmutableDog could be somehow changed by setting the fields of the MutableDog instance:

aMutableDog.color = "Pink/Black";

However, "Pink/Black" is a different string instance from the one assigned to ImmutableDog here, so ImmutableDog won't change.


On the other hand, if ImmutableDog has a field of a mutable type, then it is not truly immutable anymore.

For example, here's your same code, but with StringBuilder:

public class MutableDog
{
    public StringBuilder name;
    public StringBuilder color;

    public MutableDog(StringBuilder name, StringBuilder color)
    {
        this.name = name;
        this.color = color;
    }
}

public class ImmutableDog // are fields of these objects truly safe from changing?
{
    private final StringBuilder name;
    private final StringBuilder color;

    public ImmutableDog(MutableDog doggy)
    {
        this.name = doggy.name;
        this.color = doggy.color;
    }

    public String getColor()
    {
        return this.color.toString();
    }
}

public static void main(String[] args)
{
    MutableDog aMutableDog = new MutableDog("Courage", "Pink");

    ImmutableDog anImmutableDog = new ImmutableDog(aMutableDog);

    aMutableDog.color.append(" and Black");

    anImmutableDog.getColor().equals(aMutableDog.color);
}

Now the immutable dog's color will appear to change. You can still defend against this by copying the string builder in the constructor:

public ImmutableDog(MutableDog doggy)
{
    this.name = new StringBuilder(doggy.name);
    this.color = new StringBuilder(doggy.color);
}

However, this will still allow you to (accidentally) modify the string builder inside the ImmutableDog class.

So just don't store mutable classes in immutable classes. :)

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • So if i'm understanding correctly, if the `MutableDog` field gets reassigned after being used to instantiate the `ImmutableDog` field, that doesn't change the original String that the `ImmutableDog` field is pointing to. But if the original String the `ImmutableDog` field is pointing to gets changed (which isn't possible in reality but let's say it is), then that change would be reflected in the `ImmutableDog` field? – Snap Sep 03 '20 at 06:43
2

It is very simple: any reference type in java, in the end, points to some object.

If that object has mutable state, then any reference to it can be used to change that state.

Thus, you are correct: just putting private final before each field declaration doesn't necessarily make that class itself immutable.

In your example, the String class is immutable (besides very obscure tricks using Unsafe). After name and color are assigned, the references can't be changed, and the objects they point to can't be changed either.

But of course: if the type was List for example, it would very well be possible that the underlying object is changed elsewhere. If you want to prevent that, you have to create a copy of that incoming list for example, and keep a reference to that.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • The whole Java is "pass-by-value"...except object handles are reference *values* to objects kinda confuses me. Fortunately I think I understand it or mostly understand it after reading your's and other answers here, I just wanna be extra careful that i'm making safe objects :) Thanks for the reply! – Snap Sep 03 '20 at 06:48
1

Is it truly immutable in all senses and situations? No. Is it immutable to a certain degree? Yes.

As long as you're doing only variable assignment, the immutable dog is indeed always going to remain unchanged. This is generally due to the pass-by-value semantics, which is greatly explained here.

When you copy the reference of the color in the ImmutableDog dog, you essentially copy the handle. When you then modify the color through the mutable dog by assigning the Pink/Black value to it, the handle of the mutable dog changes, but immutable dog still holds the original handle, pointing to original color.

With the String type, this goes a step further, since strings are immutable. Therefore calling any method on the string is guaranteed not to modify the original value. So in terms of string, yes, the immutable dog is truly immutable and can be trusted.

Collections DO make a difference. If we change the design of your classes slightly:

public class MutableDog {
    public String name;
    public List<String> acceptedMeals;

    public MutableDog(String name, List<String> acceptedMeals) {
        this.name = name;
        this.acceptedMeals = new ArrayList<>(acceptedMeals);
    }
}

public class ImmutableDog {
    private final String name;
    private final Iterable<String> acceptedMeals;

    public ImmutableDog(MutableDog doggy) {
        this.name = doggy.name;
        this.acceptedMeals = doggy.acceptedMeals;
    }

    public Iterable<String> getAcceptedMeals() {
        return this.acceptedMeals;
    }
}

and execute the following code:

public static void main(String[] args) throws IOException {
    MutableDog mutableDog = new MutableDog("Spot", Collections.singletonList("Chicken"));
    ImmutableDog immutableDog = new ImmutableDog(mutableDog);

    immutableDog.getAcceptedMeals().forEach(System.out::println);

    mutableDog.acceptedMeals.add("Pasta");

    immutableDog.getAcceptedMeals().forEach(System.out::println);
}

the following is printed out:

Chicken
Chicken
Pasta

This is due to the ImmutableDog's constructor copying handle to the acceptedMeals collection, but the new handle points to the same location as the original collection. So when you call modification through the mutable dog, since the ImmutableDog points to the same place in memory, its values are also presumably modified.

In this specific case, you could circumvent this side effect by performing a deep copy of the collection, rather than simply copying the reference handle:

public ImmutableDog(MutableDog doggy) {
    this.name = doggy.name;
    this.acceptedMeals = new ArrayList<>(doggy.acceptedMeals);
}

By doing this, the same main method presented above prints only the following:

Chicken
Chicken
Andy
  • 1,127
  • 2
  • 12
  • 25
  • There are lots of good responses in this thread, bu I love your third paragraph. I guess not seeing the word `new` anywhere makes me uneasy about it heheh. Thanks for taking the time to reply! – Snap Sep 03 '20 at 06:57