StringBuffer and StringBuilder equals

Hi,
I am reading below paper
http://javapapers.com/core-java/java-string/

Shall we always use equals() method for equality comparison in any type of objects? No. You need to check the implementation in its respective class. StringBuffer and StringBuilder do not have an implementation for equals() method.
i have not understood above statement. Please advise
LVL 7
gudii9Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

dpearsonCommented:
It's just pointing out that StringBuilder (and StringBuffer) don't have a custom .equals() implementation.  So they use the default Object.equals() method which just checks to see if they are the same instance.

So:
StringBuilder a = new StringBuilder("text") ;
StringBuilder b = new StringBuilder("text") ;
boolean same = (a.equals(b)) ; // Will return false
boolean sameString = (a.toString().equals(b.toString()) ; // Will return true

Basically with StringBuilder the idea is you should just use it to build up a String - so you call "append" on it a bunch of times and then "toString()" to get a useful String.

Compare Strings, not StringBuilder objects or you may run into this little quirk.

Doug

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
gudii9Author Commented:
Java String has all attributes marked as final except hash field.

i see below api

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html

i do not see any such hash field. i see only one field which is
Field Summary

Fields
Modifier and Type      Field and Description
static Comparator<String>      CASE_INSENSITIVE_ORDER
A Comparator that orders String objects as by compareToIgnoreCase.


please advise on above sentence
StringField.png
Determine the Perfect Price for Your IT Services

Do you wonder if your IT business is truly profitable or if you should raise your prices? Learn how to calculate your overhead burden with our free interactive tool and use it to determine the right price for your IT services. Download your free eBook now!

gudii9Author Commented:
StringBuilder a = new StringBuilder("text") ;
StringBuilder b = new StringBuilder("text") ;
boolean same = (a.equals(b)) ; // Will return false
boolean sameString = (a.toString().equals(b.toString()) ; // Will return true

Basically with StringBuilder the idea is you should just use it to build up a String - so you call "append" on it a bunch of times and then "toString()" to get a useful String.

So what i got is basically StringBuilder creates new object everytime since they do not have concept of pool etc(like String where if we create new Strring object it check to see if same object there it just points it ). Hence we got false

boolean sameString = (a.toString().equals(b.toString()) ; // Will return true

above returns true because a.toString() converts to "text" string and puts in pool.
Now when we do b.toString() since "text" is there it wont create new "text" but point the reference to the memory address of same "text" object. Hence we got true
gudii9Author Commented:
i see below api

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html

i do not see any such hash field.

Is comparator is a field(class level variable) in String?
i see String implements Comparable interface though.

I see String is Final
but when author said all Fields in String are Final except hash. I have not understood. please advise
dpearsonCommented:
Now when we do b.toString() since "text" is there it wont create new "text" but point the reference to the memory address of same "text" object. Hence we got true
This is almost all correct - except this last sentence isn't quite right.

The equals method for String checks to see if the characters are the same - so it doesn't matter if they are pointing to the same memory address or not here (you're right they probably are).  The equals method is checking if the characters in
a.toString() are the same as the characters in b.toString() and they are, so it matches.

You seem to have asked a second question in the middle of this above Comparators, hash codes etc.  Seems totally unrelated so I'm going to ignore that one.  Please move it to a separate question.

Doug
sarabandeCommented:
This is almost all correct - except this last sentence isn't quite right.
Doug is quite friendly but actually almost all your conclusions are not quite correct or were missing the point (no offense intended).

StringBuilder creates new object everytime since they do not have concept of pool etc(like String where if we create new Strring object it check to see if same object there it just points it )
.
no. there is nothing like a pool of texts used by String objects and was used at runtime. there is a "pool" of literals (text constants) but that was build at compile time and is not dynamic. String and StringBuilder do not behave differently when you create variables. if you define two variables both will create two objects. the only difference for String is that if you create a copy of another String variable, you still have two String objects but both were pointing internally to the same text in memory.

String a = new String("text");   // 1st object
String b = new String(a);          // 2nd object points to same internal "text"
b = "other text";       // now both a and b are pointing to different internal strings

Open in new window

 

this concept is called reference counting since there is a counter stored with the internal text such that it is possible to decide how many String variables were pointing to the same internal text.

but that isn't the main difference between StringBuilder and String. StringBuilder is a helper that allows to build strings. it "IS NOT" a string but you can extract the current string it was building on by calling function toString().

the main difference to String - as explained by Doug above - is that the "equals" operator of StringBuilder doesn't compare the current string value but whether the variables are referencing the same object. if you create a new StringBuilder instance, all other existing StringBuilder instances necessarily are different ("equals" returns false) to the new object, regardless of their string value. contrary to StringBuilder a String has a an "equals" operator which compares texts and not objects though of course same objects also would have same text. so we have that String similar to int or double is a "value type" while StringBuilder is an "object type" and therefore they behave differently regarding equals operator.

it doesn't matter if they are pointing to the same memory address or not here (you're right they probably are).
they probably are not. if you initialize a String with a literal like "text", the String would not use the address of the literal in memory (pointer) to build its internal string object since most (probably all) compilers would handle literals differently and not like objects. because of that the internal string would take a copy of the literal. if you make a copy of the String however, the copy would point to the same internal string as expalined above.

Sara
krakatoaCommented:
It's just pointing out that StringBuilder (and StringBuffer) don't have a custom .equals() implementation.  So they use the default Object.equals() method which just checks to see if they are the same instance.

This is not correct. equals() will check to see if the objects have identical values, not if they are the same object.
dpearsonCommented:
This is not correct. equals() will check to see if the objects have identical values, not if they are the same object.

That's true if .equals() is overridden, but I don't think it is for StringBuilder.  Try this out:

		StringBuilder a = new StringBuilder() ;
		StringBuilder b = new StringBuilder() ;
		a.append("test") ;
		b.append("test") ;
		boolean same = a.equals(b) ;   // Returns false

Open in new window

krakatoaCommented:
This is what I am referring to -

StringBuilder a = new StringBuilder() ;
		StringBuilder b = new StringBuilder() ;
		a.append("test") ;
		b.append("test") ;
		boolean same = a.toString().equals(b.toString()) ;   // Returns true
		System.out.println(same);

Open in new window


(You don't test for object equivalence with "equals()", but with "==").
krakatoaCommented:
What you are doing with the StringBuilder test essentially amounts to this :

StringBuilder aa = new StringBuilder() ;
		StringBuilder bb = new StringBuilder() ;
		aa.append("test") ;
		bb.append("test") ;
		boolean same2 = Integer.toString(aa.hashCode()).equals(Integer.toString(bb.hashCode())) ;   // Returns false
		System.out.println(same2);

Open in new window



... and the test should be:

boolean same2 = Integer.toString(aa.hashCode())==Integer.toString(bb.hashCode()) ;   // Returns false

Open in new window


 . . . sorry forgot to add that in real life, the code should be :
boolean same2 = aa==bb ;   // Returns false

Open in new window

dpearsonCommented:
This is what I am referring to -

StringBuilder a = new StringBuilder() ;
StringBuilder b = new StringBuilder() ;
a.append("test") ;
b.append("test") ;
boolean same = a.toString().equals(b.toString()) ;   // Returns true

@krakatoa - sure that works because now you're doing equals() on String objects which do support object equivalence because String overrides equals.  What I was saying is that StringBuilder itself does not support this.  If you go back to my very first comment in this question I laid out the same two examples.

(You don't test for object equivalence with "equals()", but with "==").
Yes, in general we test for object equivalence with .equals() and object identify with ==.   But if a specific class doesn't implement equals() itself then it falls back to the default equals() implementation in Object and that test is for object identity.

So there are cases where .equals() and == both do the exact same thing and StringBuilder is one such class.

public class Object {
    // Default equals behavior is the same as == behavior
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

Open in new window


That was all I was trying to explain.  String and StringBuilder handle equality very differently.

Doug
krakatoaCommented:
Could you give an example of when you would need to compare anything else about a StringBuilder apart from its String field?

Can you point to the docs which describe the otherness of StringBuilder's handling of equals(), please Doug?
dpearsonCommented:
Could you give an example of when you would need to compare anything else about a StringBuilder apart from its String field?

It seems very unlikely you'd want to do any equality tests on a StringBuilder.  As you say, you should convert to a String and compare that.  I think the reason StringBuilder doesn't override equals is just to make this conversion explicit - you should realize that in order to call equals() on the String you need to convert it (copy all of the characters from the StringBuilder) which is relatively expensive.

To my mind, this all reinforced the intended use of StringBuilder in Java.  Which is just what it's name suggests - a helper class with the goal of producing a String.  You shouldn't keep StringBuilders around for a long time - you just use them to build up a String object and then work with the String.  That's a huge difference when compared to C++ where all Strings were essentially like Java's StringBuilders.

Can you point to the docs which describe the otherness of StringBuilder's handling of equals(), please Doug?
There's not much docs on this - since it's a case of not implementing a method.  The formal docs would be:
https://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html
and scroll down to where it says:
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Unlike String where we have an explicit method:
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#equals(java.lang.Object)

Doug
krakatoaCommented:
Yes, in general we test for object equivalence with .equals() and object identify with ==.

But Doug - this is wrong . . . "object equivalence" is an ambivalent term. "==" tests whether two objects ARE the same object :
boolean same2 = aa==aa;

Open in new window

will return true, correct? If by equivalence you mean that two objects might hold the same data value, which I would agree with, then if those objects are Strings then .equals() is the correct test.

StringBuilder doesnt override because why would you override the reference for objects, when you are interested in the payloads, and when you can test for object identicality with "=="?
krakatoaCommented:
Methods inherited from class java.lang.Object

This is the very same method that String inherits from of course. And the very reason why there is an "extra" method in String - namely .equals() - so that the CharSequence can be interrogated. Object equivalence for String using "==" is the exact same thing as the test you cite for StringBuilder.

This is a circular argument in terms of developing the code logic, and is only required to be broken in two cases - one when Strings are involved, and the other, when numbers are involved. Otherwise equivalence is up to the programmer, since the only equivalence that makes sense from that point forward is object equivalence - in other words, the memory location of the stored entity.

StringBuilder doesnt override equals() because it has no need to do that.
dpearsonCommented:
"object equivalence" is an ambivalent term.
I think most people use "object equivalence" to mean when two objects satisfy some equality test.  In Java that's "equals".
And "object identity" to mean when two objects are the exactly the same - the same memory location.  In Java that's ==.

E.g. Here's a similar take on the difference:
http://mr-ponna.com/Question/345/In-the-context-of-a-comparison-what-is-object-identity-versus-object-equivalence-/

Methods inherited from class java.lang.Object
This is the very same method that String inherits from of course.
I think you're confusing "inherits" and "overrides".  String overrides the equals method from Object (replaces it with its own method).  StringBuilder inherits the method from Object (uses the same method as Object does).

StringBuilder doesnt override equals() because it has no need to do that.
There's nothing stopping Sun/Oracle from choosing to implement equals in StringBuilder.  It's a design decision.  Since I don't work there, I can't say for sure why they chose not to implement it, but I suggested why in my earlier answer.

Doug
krakatoaCommented:
Well I can't admit to being even mildly confused. Sun may have made a design decision, but it was forced on them by natural end-of-sequence logic. The fact is that there is no more fundamental test of absolute equivalence than testing whether an object holds the same memory address, and that is achieved by the "==" operator, which is an entity that cannot be overridden.

So when the stack hierarchy tops out at the Object class, "equals()" is forced to make that test, and so *can only* return an object-for-object comparison by means of implementing the operator "==". But down the call stack - in other derived objects such as String, the opportunity exists to compare the objects via methods. So down the call stack there is latitude to introduce methods (like equals) which can test for actual, bodily equivalence - (is "Smith" the same as "Smith", etc., held in two *different* objects). In one sense then, this method overrides "==", but in another sense it is completely different to it, because it is not concerned with whether "Smith" is in 1, 2 or 50k objects, it just allows you to find out whether it is there at all. But when one comes to the top of the call stack, you can't do that anymore, and you are forced to use "==" because there is nowhere else to go to make a comparison - the memory cells are the final resting place of the truth.

StringBuilder doesnt have that problem and indeed can laugh at those sorts of concerns, because it is simply a convenience class to make String manipulation easier. It has no need of equals() because the String it is building MUST be called by toString(), at which point equals() can be called. And it can also smile at "==" because, even if you ever need to call equals() on a StringBuilder, you will never get true as a result unless they are the same StringBuilder, which, as I've already said, can be done more efficiently using "==".
dpearsonCommented:
OK sounds like we're all clear on the functionality involved here and I'm not sure we're shedding any extra light on the situation with more discussion.  Hope there's no confusion remaining.

Doug
krakatoaCommented:
Yep. So now we see that .equals() never needs to be called on StringBuilder.  Looks like the designers were right all along.
krakatoaCommented:
This is a major article on our friend 'equals()', here.

In particular, the section on 'canEqual()'.

I don't know about you Doug, but I wonder if it is EVER a commendable idea to call .equals() on *any* object, apart from a String or an Integer ? I really do wonder. :0
dpearsonCommented:
I don't know about you Doug, but I wonder if it is EVER a commendable idea to call .equals() on *any* object, apart from a String or an Integer ? I really do wonder. :0
Umm, yes it's fine to implement and call .equals() on lots of objects.  Pretty much any "value" class (where you're just representing immutable data) is a good candidate for this.  If you don't implement .equals() you can't do things like:

List<Cat> cats = new ArrayList<Cat>() ;    // Cat should implement .equals() or this list won't work correctly for all calls to it
Set<Cat> cats = new HashSet<Cat>() ;      // Same here - should implement .hashCode() and hence .equals().

To the point of that article, many people will only allow equality to be valid to the same class - rather than supporting a whole class hierarchy (which can get complicated).  So it may be smart to avoid .equals() on classes that are part of a hierarchy (unless you are very careful), but I'm not a big fan of inheritance anyway (that may be a surprise, but really inheritance is usually the wrong choice, containment is usually better).  But on simple value classes, I think it's fine.

Just leave it to your IDE to write it though :)  Since doing it correctly isn't trivial.

Doug
sarabandeCommented:
StringBuilder very well could be looked on as a "value class" since it has one string value as a data member. so it is less reasonable not to provide a comparison operator which compares by value.  actually, the class StringBuilder itself is a dispensable construct since the functionality it provides easily could be added to the string class.

i am using a string class which additionally provides builder functions and streaming functions(like 's << 123 << "abc" << fvar'). all this makes my string class much more than an array of characters and any of the issues discussed here meaningless.

Just leave it to your IDE to write it though :)  Since doing it correctly isn't trivial.
this way is only second best. it is worth to go for a better solution even if it isn't trivial.

Sara
dpearsonCommented:
StringBuilder very well could be looked on as a "value class" since it has one string value as a data member
Yes I'd agree Sara that StringBuilder only stores a single data member - but to me it's not really what I mean by a "value" class because it's mutable.

Mutable data structures don't make great keys for maps and generally have all kinds of problems with equality because they violate some of the obvious relationships you'd expect since what they are equal to can change over time.  So we might have:
a.equals(b)  // True
b.equals(c)  // True
a.equals(c) // We'd expect True, but can't be sure because one of 'a' or 'c' might have changed - so this breaks transitivity

So the reason String and Integer are such good candidates for equals() implementations and use as keys in maps is because they're immutable.  For me immutability is one of the great improvements in software theory in the last decade or so - the realization that you should make data immutable whenever possible.  It makes many software problems (like writing equality tests) much simpler.

So whenever possible I think we should limit equals() and hashCode() to classes that represent immutable values.

Doug
sarabandeCommented:
assume a, b, c are integer variables with value 123. then the same conclusions can be made that a == b and b == c. of course it is transitive and a == c is also true beside you made a = 999 before. transitivity between variables obviously only can be granted as long as the values keep unchanged.

String and Integer ... they're immutable
of course not. if using them as keys in a map, the map makes a (real) copy and because of that, the container (class) can guarantee immutability. but that would be possible with a real copy of StringBuilder objects as well.

however, my point isn't to upgrade StringBuilder to a string class but to add the string manipulation features to the string class rather than to using a helper.  a good OOD design always should favor real objects before artificial ones.

Sara
CEHJCommented:
if using them as keys in a map, the map makes a (real) copy
I'm not with you there - are you saying Java maps clone their keys?
sarabandeCommented:
i don't know java but how should they prevent you from changing a key (variable) after you added the key-value pair to the map if they would use a reference? there is no other way than to use a copy, same as it is with an integer.

Sara
CEHJCommented:
An interesting scenario, to which i'd have to give some thought. Of course in the case of String and Integer, they are immutable as has already been said above, so the problem wouldn't arise
krakatoaCommented:
i don't know java but how should they prevent you from changing a key (variable)  . . . [] . . . if they would use a reference

I don't see why Java should concern itself with worrying about what the programmer does to a reference's *value*. Are you saying that the reference address changes?

String smith = "Smith";
//now use this as a Map Key.
smith = "Jones";
//now try to find the Entry
smith = "Smith";
//now try to find the Entry
CEHJCommented:
there is no other way than to use a copy,
Yes, logically that's what would happen. Java doesn't use 'real' references, which is why it doesn't support pass by reference (a point that causes much confusion amongst even some quite seasoned Java people) so the problem would never arise. So key 'k' of type K would be used to create an instance of Entry<K, V>, which doesn't need to clone k in its ctor since an external change to k will not be reflected in the Entry instance's copy of k.
sarabandeCommented:
so you do agree that Java makes a clone of the passed key rather than using a reference to the same object that was passed as the key?

that is what i said and what contradicts the assertment of Doug who meant that String and Integer are immutable if used for key in a map and StringBuilder was not. if StringBuilder would have equals and less operator based on string value, it could be used as key for a map same as String since the map would make a copy and hence it doesn't matter if the variable used for insert was changed later.

Sara
CEHJCommented:
No explicit cloning occurs. You will see that if you look at the source of java.util.HashMap for instance
sarabandeCommented:
No explicit cloning occurs.
a copy of a string is as good as a clone even if original and copy were referencing to the same internal string object. if you do a modification at either copy, a new instance of the internal string was created, the reference to the previous internal string was cut and the reference counter of the previous internal string object was decremented. therefore you can change a string key after insert and the string copy used in the map will still use the old string value.

a copy of a StringBuilder instance which has a String object as member would work same way if used as key in a map. the String member of the new instance was not cloned but copied and points to same internal reference string as the original with a reference count of 2. when the original StringBuilder was changed, it would lead to a new reference string and the instance used by the map still points to the original string value which now has a reference count of 1. note, these mechanisms can be explicitly seen in a compiler language  like c++. in java the mechanisms may be encapsulated but surely a key used in a map cannot be changed from outside or the integrity of the container would be violated.

Sara
krakatoaCommented:
in java the mechanisms may be encapsulated but surely a key used in a map cannot be changed from outside or the integrity of the container would be violated.

Did you not read my comment containing the String examples with "Smith" ?
CEHJCommented:
in java the mechanisms may be encapsulated but surely a key used in a map cannot be changed from outside or the integrity of the container would be violated.
It can't, afaik. Imagine a language that has real pointers and references, and the key is a pointer. With no defensive mechanism, ordinarily a mutation in the key object to which the pointer points could cause a problem for the map. That isn't possible in Java.

I hope that addresses an area of doubt concerning key mutation
dpearsonCommented:
if StringBuilder would have equals and less operator based on string value, it could be used as key for a map same as String since the map would make a copy and hence it doesn't matter if the variable used for insert was changed later.
It doesn't matter if the variable used for the insert is changed later, but it does matter if the object being used as the key is mutable and is changed after it is inserted into the map.

I think a simple example may help explain what I'm saying are the risks around using a mutable data type as a key for a map.  Let's create a really simple mutable class with equals() and hashCode() implemented.
(This is analogous to StringBuilder because it also is mutable, unlike String which is not).

	public static class MutableKey {
		private int m_Value ;

		public MutableKey(int value) {
			m_Value = value ;
		}

		public int getValue() {
			return m_Value;
		}

                // This is what makes it mutable - this setter method
		public void setValue(int value) {
			m_Value = value;
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;

			MutableKey that = (MutableKey) o;

			if (m_Value != that.m_Value) return false;

			return true;
		}

		@Override
		public int hashCode() {
			return m_Value;
		}
	}

Open in new window


Now my claim is that putting a key like this into a Map can produce undesirable results and is generally not safe.  Let's see how that can go wrong.  Let's create a Map, 3 potential keys and let's put just one of them into the Map:

		Map<MutableKey, Boolean> map = new HashMap<>() ;
		MutableKey key1a = new MutableKey(1) ;
		MutableKey key1b = new MutableKey(1) ;
		MutableKey key2 = new MutableKey(2) ;
		map.put(key1a, Boolean.TRUE) ;

Open in new window


OK if we check for this value in the map using either key1a or key1b we find it - because the class implements equals() and hashCode() and we've not mutated the key.

		Boolean inMap1a = map.get(key1a) ;
		Boolean inMap1b = map.get(key1b) ;
		Boolean inMap2 = map.get(key2) ;
		System.out.println("InMap1a= " + inMap1a + " inMap1b= " + inMap1b + " inMap2= " + inMap2) ;

Open in new window


Prints
InMap1a= true inMap1b= true inMap2= null
as we'd expect.  1a is found in the map, 1b is also found and 2 is not.

I think we can all agree this is correct and what we'd expect.
Up to this point it doesn't matter if the MutableKey was mutable or not.  But there's more to come...

All is good so far.  But now let's try that again but this time we mutate the key and try to look up the values from the map again:

		key1a.setValue(2);
		Boolean inMap1a = map.get(key1a) ;
		Boolean inMap1b = map.get(key1b) ;
		Boolean inMap2 = map.get(key2) ;
		System.out.println("InMap1a= " + inMap1a + " inMap1b= " + inMap1b + " inMap2= " + inMap2) ;

Open in new window


What would we expect to happen here?

If the key had been copied when added to the map, then map.get(key1b) should return true - since it's passing in a value of 1.  If the key hasn't been copied but has updated to value 2 then maybe 1a or 2 will return true.

Of course in practice all return null, none of these match:
InMap1a= null inMap1b= null inMap2= null

Just to restate that in English - we inserted a key with value 1.
Changed it to value 2.  And now we cannot retrieve it using a key with either value 1 or value 2.  It's in there, but we can't find it.

For 1a, it doesn't match now because when we did the insert the hashCode() was different - so we're looking in the wrong bucket.
For 1b, it doesn't match now because although the hashCode() matches (between 1b now and 1a as it was when inserted) so we find the right bucket, the equals() test fails because 1b no longer equals 1a.
For 2, it doesn't match now because again the hashCode() is different from the value when 1a was added to the map, so we're in the wrong bucket and can't do the equality test.

Just in case there's still any doubt about what's in the map, let's dump the keys:
		key1a.setValue(2);
		System.out.println("Keys=" + map.keySet()) ;

Open in new window

Result:
Keys=[MutableKey{m_Value=2}]

The map stored a reference to the mutable key object and then we changed that object.
The result - a map that has a key inside with value 2 that you can't retrieve successfully.

Anyway - I hope that resolves any remaining confusion about what I was trying to say here.
Implement equals() and hashCode() for keys in maps and stick to immutable types for keys.

Doug
dpearsonCommented:
Occurred to me others might want all of the code, so here it is as a single block, in case you want to mess around with it.

Doug

	public static class MutableKey {
		private int m_Value ;

		public MutableKey(int value) {
			m_Value = value ;
		}

		public int getValue() {
			return m_Value;
		}

		public void setValue(int value) {
			m_Value = value;
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;

			MutableKey that = (MutableKey) o;

			if (m_Value != that.m_Value) return false;

			return true;
		}

		@Override
		public int hashCode() {
			return m_Value;
		}

		@Override
		public String toString() {
			return "MutableKey{" +
					"m_Value=" + m_Value +
					'}';
		}
	}

	public static void main(String args[]) {
		Map<MutableKey, Boolean> map = new HashMap<>() ;
		MutableKey key1a = new MutableKey(1) ;
		MutableKey key1b = new MutableKey(1) ;
		MutableKey key2 = new MutableKey(2) ;
		map.put(key1a, Boolean.TRUE) ;

		key1a.setValue(2);

		Boolean inMap1a = map.get(key1a) ;
		Boolean inMap1b = map.get(key1b) ;
		Boolean inMap2 = map.get(key2) ;
		System.out.println("InMap1a= " + inMap1a + " inMap1b= " + inMap1b + " inMap2= " + inMap2) ;

		System.out.println("Keys=" + map.keySet()) ;
	}

Open in new window

krakatoaCommented:
Doug - code won't compile.
CEHJCommented:
import java.util.HashMap;
import java.util.Map;


public class MutableKey {
    private int m_Value;

    public MutableKey(int value) {
        m_Value = value;
    }

    public int getValue() {
        return m_Value;
    }

    public void setValue(int value) {
        m_Value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if ((o == null) || (getClass() != o.getClass())) {
            return false;
        }

        MutableKey that = (MutableKey) o;

        if (m_Value != that.m_Value) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return m_Value;
    }

    @Override
    public String toString() {
        return "MutableKey{" + "m_Value=" + m_Value + '}';
    }

    public static void main(String[] args) {
        Map<MutableKey, Boolean> map = new HashMap<MutableKey, Boolean>();
        MutableKey key1a = new MutableKey(1);
        MutableKey key1b = new MutableKey(1);
        MutableKey key2 = new MutableKey(2);
        map.put(key1a, Boolean.TRUE);

        key1a.setValue(2);

        Boolean inMap1a = map.get(key1a);
        Boolean inMap1b = map.get(key1b);
        Boolean inMap2 = map.get(key2);
        System.out.println("InMap1a= " + inMap1a + " inMap1b= " + inMap1b +
            " inMap2= " + inMap2);

        System.out.println("Keys=" + map.keySet());
    }
}

Open in new window

krakatoaCommented:
Yes, I made the changes too. ')
krakatoaCommented:
Doug -

your example is saying the same thing as I put in comment here.

Your key 1b never gets anywhere near the Map (same thing as in the OP's other infamous "red Dog" question), and so it is no surprise that the Map can't find it. As for the key you do change, this is exactly what one would expect, whether the discussion is about mutability or not. Why would you expect to get back into your house when someone at work had swapped your keys in your pocket?
sarabandeCommented:
the same thing in c++

#include <iostream>
#include <sstream>
#include <string>
#include <map>

class MutableKey
{
    int m_Value;
public:

    MutableKey(int value) {
        m_Value = value;
    }

    int getValue() const {
        return m_Value;
    }

    void setValue(int value) {
        m_Value = value;
    }

    bool operator==(const MutableKey & mk) const
    {
       return (m_Value == mk.m_Value);
    }

    bool operator<(const MutableKey & mk) const
    {
       return (m_Value < mk.m_Value);
    }


    int hashCode() {
        return m_Value;
    }

    std::string toString() const {
        std::ostringstream oss;
        oss << "MutableKey{m_Value=" << m_Value << '}';
        return oss.str();
    }
};

int main() 
{
    std::map<MutableKey, bool> mymap;
    MutableKey key1a = 1;
    MutableKey key1b = 1;
    MutableKey key2  = 2;
    mymap[key1a] = true;

    key1a.setValue(2);

    bool inMap1a = (mymap.find(key1a) != mymap.end());
    bool inMap1b = (mymap.find(key1b) != mymap.end());
    bool inMap2  = (mymap.find(key2)  != mymap.end());
    std::cout << "InMap1a= " << inMap1a << " inMap1b= " << inMap1b
        << " inMap2= " << inMap2 << std::endl;

    std::cout << "Keys=";
    std::map<MutableKey, bool>::iterator i;
    for (i = mymap.begin(); i != mymap.end(); ++i)
        std::cout << i->first.toString() << std::endl;
    return 0;
}

Open in new window


the output is (as expected):

InMap1a= 0 inMap1b= 1 inMap2= 0
Keys=MutableKey{m_Value=1}

Open in new window


the difference obviously is that the java map stores references while the c++ map stores copies. i could try to get the same behavior in c++ by using member pointers. but even then my class would be simply bad code since for pointer members you have to provide a copy constructor which makes a deep copy and not simply copies the pointer.

sorry that i didn't know of this difference (and still have problems to believe it despite of your evidence).

i wonder why a String in java should not have same issues.

Sara
CEHJCommented:
i wonder why a String in java should not have same issues.
Well as we've said, it's immutable.  Also of course it's final so it's not possible to interfere with its implementation of hashCode() anyway
krakatoaCommented:
There you go.
sarabandeCommented:
Well as we've said, it's immutable.
do you mean, that a String can't be changed (has no setter) after it was used as key in a map?

it's not possible to interfere with its implementation of hashCode()
if using Map rather than HashMap, would MutableKey have the same issues? And String?

sorry for my questions, but i am still puzzled that it so easy to get a corrupted container in java.

Sara
CEHJCommented:
do you mean, that a String can't be changed (has no setter)
Yes. You can only create a different one.

if using Map rather than HashMap, would MutableKey have the same issues? And String?
Map is an interface in Java. It has implementations, of which HashMap is (the principal) one. 'MutableKey' would be problematic for the reasons Doug gave. String wouldn't, for the reasons we discussed, of immutability.

Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a map.

( http://docs.oracle.com/javase/8/docs/api/java/util/Map.html )
sarabandeCommented:
thanks.

Sara
dpearsonCommented:
@CEHJ:
Note: great care must be exercised if mutable objects are used as map keys.

That's great that you found there's an actual reference in the docs about avoiding mutable keys in maps.

Very nice - and thanks for following up with the other clarifications here Charles.

Doug
gudii9Author Commented:
lot of conent here to read, re read and digest
gudii9Author Commented:
this question answers going above my head but closing now.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Programming

From novice to tech pro — start learning today.