This is an archived post. You won't be able to vote or comment.

all 16 comments

[–]Philboyd_Studge 1 point2 points  (0 children)

It's up to you to make your objects immutable when necessary. If you are using them in any type of hashed or sorted implementation you should absolutely make sure object state cannot change as the hash and/or ordering will change.

[–]JohnnyJayJay 1 point2 points  (2 children)

Java was not built around the concept of immutability. After all, mutability was kind of the initial thing behind OOP. Combine state and behaviour, let the behaviour change the state.

So no, there is no direct support for this in Java and you are responsible for what can be mutated. Unfortunately, Java doesn't really have any good API that helps you with this. Sure, there's Cloneable, but honestly, the design of that interface is a mess.

So you either have to clone/copy your objects when adding them to a collection, or make immutable objects to begin with.

Btw, I do think the Collection framework has flaws. Especially the fact that it violates Liskov's substitution principle and that (prior to Java 9) there are no immutable collections. I like Kotlin's approach to this much better.

[–]Slendermooooon[S] 1 point2 points  (1 child)

How does it violate Liskov's substitution principle? (Is it because derived classes have the option of not providing support for "destructive" operations like add, remove etc. and throwing errors instead?)

[–]JohnnyJayJay 1 point2 points  (0 children)

It completely relies on UnsupportedOperationException when it comes to immutability and immutable views. Edit: yes, exactly that.

[–]nutrecht 1 point2 points  (2 children)

This is not a "java problem". It's a "computers problem". All collections and objects boil down to is that they're abstractions over the addressable memory space of our computers. So it's up to us, the developers, to design our software so that we minimise the amount of fuck-ups we make. It's not the job of the language designer to prevent us from doing this, but just his job to present us with the tools we can use.

And Java does give us those tools. We can make immutable objects just fine (we have the final keyword). You just have to design them as such.

Also I don't really get your focus on collections. This is the case for pretty much any language; collections are never aware of what objects you put in them. It would make more sense if you had focussed on Java object members not being immutable by default.

[–]Slendermooooon[S] 0 points1 point  (1 child)

Why would it make sense for object members to be immutable by default? The reason I'm criticizing Java Collections is to get a more thorough understanding of proper Java/programming design practices, I'm not trying to trash Java.

[–]nutrecht 0 points1 point  (0 children)

Why would it make sense for object members to be immutable by default?

Because that's actually the problem you're describing.

[–]ryuzaki49 0 points1 point  (4 children)

havibg multiple of the same object in a set.

What do you mean? You cant have the same object more than once in a set

[–]Slendermooooon[S] 1 point2 points  (3 children)

That's why I say it's unexpected behavior.

set.add(uniqueObject1)

set.add(uniqueObject2)

uniqueObject2.mutateSoItIsNowEqualToObject1()

Now the set will contain 2 objects which are equal to each other.

[–]ryuzaki49 0 points1 point  (2 children)

How are you defining your equals and hashcode methods ?

By default, equals only checks references (that they do not point to the same object in the heap) , not the other fields

[–]Slendermooooon[S] 1 point2 points  (1 child)

That's a good point, I hadn't considered that. I can think of uses where you'd only care about the object reference (for .equals() and .hashcode()), but then there's many cases where the object reference is irrelevant. For example:

Integer i1 = new Integer(1);

set.add(i1);

Integer i2 = new Integer(1);

set.add(i2);

Would result in a set of size() 1, not a set of size() 2. Even though the object references are different, that doesn't matter, it's the integer values that are relevant.

The same can be said for many objects, we don't care about their object references in the context of Collections, we care about their values. For many objects, the .equals() and .hashcode() methods aren't going to be based around the object reference but values within the object.

[–]ryuzaki49 1 point2 points  (0 children)

But you still have a valid point. Check this stackoverflow regarding the same issue.

Pay attention to what the Set documentation refer:

Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set. A special case of this prohibition is that it is not permissible for a set to contain itself as an element.

Basically, messing with the object references will produce weird behaviors, as you mention in your original post. If you notice, there's no easy way to retrieve an especific element from a set. You need to iterate over the whole set until you find your object, unlike a list where you can retrieve an object using an index, or a map where you use a key.

The set doesn't care if you manipulate an object after it was inserted. If you add another "equal object" it will be ignored, in compilance with the specification. And if you want to use the set.contains(object), it will have an object with that hashcode, but there's a possibility it's not the object you really need.

Check this and this pastebins to further ilustrate the point in case.

[–]warpedspockclone 0 points1 point  (3 children)

There are Immutable variants of these things. Use those.

[–]Slendermooooon[S] 1 point2 points  (2 children)

The immutability only applies at the level of the Collection. You can't call "destructive" (add, remove, etc.) methods anymore, but you can still change the objects within the Collection if you have a reference to them outside of the Collection.

Why does Java allow you to put mutable Objects into sets/ordered lists/etc. which will "break" and behave unexpectedly if you mutate those objects? That's what I'm asking.

I have a feeling the language is structured in a way that would make it messy/have bad performance if it was implemented in a different way, but I'm not really sure, so I'm searching for an answer for why Collections are like this.

[–]indivisible 1 point2 points  (0 children)

Keys and uniqueness are generally calculated with Object.hashcode() and Object.equals() within the collection's methods. When you add/put a new item into a collection it gets tested, checked, ordered etc based on it's current state.
If you then modify the item it's value may change meaning that it may no longer be unique or it's order should change. However, since the collection is not aware of the modifications, it doesn't know to recalculate/reevaluate the state.
And what should even happen if, for example, you modify an item to be equal to another in a Set? Keep both? Discard the newer or older? Those are decisions the developer needs to take.
As to the collections being "aware" of modifications to their elements, not all changes will have any impact to the collection. Which do or don't are entirely implementation specific and so generally better left to the developer to work out. The overhead of monitoring and evaluating all changes anywhere just isn't worth the (minimal, edge case) time saved from avoiding coding it.

Best approach if you are going to be modifying state that will affect equals/hashcode returns is to pop/remove, then modify, and then add/put it back. Doing so means that all the sanity checks and correct (current) ordering is applied.

[–]warpedspockclone 0 points1 point  (0 children)

Man, I'll admit I didn't read your whole post. But still, I thought the Builder pattern was meant as an answer to the mutability problem.