Question

I have the following code where I am trying to put the StringBuffer objects as keys in a TreeSet. The reason I do this is to see if I can put mutable objects as keys. I do not get any compile error. but when I run this code, I get the error that is below the code. specially, I get this java.lang.StringBuffer cannot be cast to java.lang.Comparable. what does this error indicate?

from javadoc I see that StringBuffer class is declared final (public final class StringBuffer), doesn't that mean it is immutable and hence hashable?

I am a newbie to the hashing and immutable stuff, so kindly help me out here.

Thanks

import java.util.*;
class MutableKeys {
public static void main(String[] args) {
        StringBuffer one = new StringBuffer("one");
        StringBuffer  two = new StringBuffer("two");
        StringBuffer three = new StringBuffer("three");
        Set<StringBuffer> sb=new TreeSet<StringBuffer>();
        sb.add(one);
        sb.add(two);
        sb.add(three);
        System.out.println("set before change: "+ sb);
        one.append("onemore");
        System.out.println("set After change: "+ sb);
    }
}

Exception in thread "main" java.lang.ClassCastException: java.lang.StringBuffer cannot be cast to java.lang.Comparable
    at java.util.TreeMap.put(TreeMap.java:542)
    at java.util.TreeSet.add(TreeSet.java:238)
    at inheritance.MutableKeys.main
Was it helpful?

Solution 2

just add a comparator class and then use it in your TreeSet as follows:

class Comparatorbuff implements Comparator<StringBuffer> {

        @Override
        public int compare(StringBuffer s1, StringBuffer s2) {
            return s1.toString().compareTo(s2.toString());

        }

}

in your main method: modify as follows
Set<StringBuffer> sb=new TreeSet<StringBuffer>(new Comparatorbuff());

OTHER TIPS

  1. The fact that StringBuffer is public final class StringBuffer means you can't subclass it. StringBuffer is quite mutable (that's the point, you can modify the contents of the buffer.)

  2. You don't want to use something that is mutable as the key because then after the object is modified, its equals() and hashcode() methods will return different results and you won't be able to find it in the Map anymore.

  3. If you really wanted to use StringBuffer in a TreeSet, you would have to provide your own Comparator since StringBuffer doesn't implement Comparable.

The issue is that TreeSet sorts the items you put in it. Because StringBuffer doesn't implement Comparable, the TreeSet doesn't know how to sort them. You should pass in a Comparator when you create the TreeSet. Your comparator will tell the TreeSet how to sort the StringBuffers. Either that, or you can use a HashSet, which does not sort the elements.

As far as immutability goes: the final keyword on a class declaration means you can't subclass (extend) it. It does not, in and of itself, make the class immutable. Immutable means that the state of the object cannot be changed once it has been created. StringBuffers definitely can have their state changed after they are created, so they are not immutable.

Declaring a class final doesn't mean that it's immutable, it means that no class is allowed to subclass it. In fact, StringBuffer is very mutable; that's the point of the class.

Because StringBuffer is not Comparable, your TreeSet doesn't know how to sort your StringBuffers. However, it's a bad idea to have a mutable object be a key in any kind of Set (or Map). If you must use a TreeSet, then create and use a custom Comparator object that compares StringBuffer objects.

TreeSet takes only Comparable objects where as StringBuffer is not Comaprable object.

TreeSet#add

Throws-ClassCastException - if the specified object cannot be compared with the elements currently in this set.

You can use String object(Since String is Comparable) instead of StringBuffer object.
For example:

    Set<String> sb=new TreeSet<String>();
    sb.add(one.toString());
    sb.add(two.toString());
    sb.add(three.toString());
    System.out.println("set before change: "+ sb);
    System.out.println("set After change: "+ sb);

You are asking several questions:

  1. general question: "can you have a mutable key for a hash"
  2. Specific question: "Can StringBuffer be used as a key for a TreeSet"

You have somethings confused and I will help you to sort them out

There are 2 identifying strategies used with Maps in Java (more-or-less).

  1. Hashing: An input "Foo" is converted into a best-as-possible attempt to generate a number that uniquely accesses an index into an array. (Purists, please don't abuse me, I am intentionally simplifying). This index is where your value is stored. There is the likely possibility that "Foo" and "Bar" actually generate the same index value meaning they would both be mapped to the same array position. Obviously this can't work and so that's where the "equals()" method comes in; it is used to disambiguate

  2. Comparison: By using a comparative method you don't need this extra disambiguation step because comparison NEVER produces this collision in the first place. The only key that "Foo" is equal to is "Foo". A really good idea though is if you can is to define "equals()" as compareTo() == 0; for consistency sake. Not a requirement.

Now to your general question:

  1. Can a key to a map be mutable. Answer: Yes, very very bad and dumb. Example: Map.put(k,v); k.modifyInternalHash(); Map.get(k) = null; // bad here
    In reality this happens through carelessness of hashing. Though this can occur with Comparative Maps it will be a much easier problem to diagnos.

  2. Can a StringBuffer be used as a key to a TreeMap/Set ? Yes. Use the alternative constructor: TreeSet(Comparator< T > comparator) and define your own comparison method for StringBuffer

Good luck

Yes, you can but as the above answers state, you must write a Comparator.

But the real question is why would you want to? The purpose of a StringBuffer is to modify the state while creating a string. Since it is a key in your SortedMap you shouldn't be modifying the key, so there is no point in saving the StringBuffer. What you want to do is call StringBuffer.toString() which returns a String and use the String as your key.

We are depending on the Default natural sorting order compulsory the object should be homogenous and comparable otherwise we will get run time exception saying Class cast exception. An object is said to be comparable if and only if Corresopding class implement the comparable interface. String class and all wrapper classes already implement a comparable interface but String Buffer class does not implement a comparable interface. Hence it will give class cost exception on the above code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top