1

I want to implement a Comparator for StringBuffer that compares the StringBuffer and adds to TreeSet accordingly. This is purely for learning purposes only. I know having a mutable object in a Hasable collection is a bad idea. but the purpose here is how to implement comparator for existing StringBuffer class of Java and use it to create a TreeSet. My current code is below. The code does not compile. kindly help me out here. Thanks

public class ComparatorExample {


    public class SbufferComparator implements Comparator<StringBuffer> {

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

        }

    }


        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>(new SbufferComparator());
                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);
            }
        }
eagertoLearn
  • 9,772
  • 23
  • 80
  • 122

4 Answers4

4

The inner class SbufferComparator must be static, or must be refactored to a top-level class.

Otherwise, it needs an enclosing object instance to be constructed:

ComparatorExample ce = new ComparatorExample();
SbufferComparator comparator = ce.new SbufferComparator();

Leaving it as a non-static inner class doesn't make much sense, since it doesn't use any instance field or method from its enclosing class. So make it static or top-level.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • making the inner class static does not help. I still get `unresolved compilation problem: TreeSet cannot be resolved to a type` – eagertoLearn Aug 22 '13 at 22:05
  • Ah. So you finally read the error message! This means that you forgot to import `java.util.TreeSet`. – JB Nizet Aug 22 '13 at 22:13
  • I have copied and pasted your code, added `import java.util.*;` at the top of the code, and made the inner class static, and it compiles fine. You're probably not compiling the right file. – JB Nizet Aug 22 '13 at 22:27
3

You should create the comparator class in a separate file not in the same class or make it static instead if you want to keep it inner.

    import java.util.Comparator;
    import java.util.Set;
    import java.util.TreeSet;

public class ComparatorExample {
private static class SbufferComparator implements Comparator<StringBuffer> {

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

        }

}


    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>(new SbufferComparator());
            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);
        }
    }

Pay attention to the import statements!

Java Panter
  • 307
  • 2
  • 12
  • I don't believe the `final` is necessary, only the `static`. Since the main method is static, it can't create instances of the comparator unless the comparator is available statically as well. – dimo414 Aug 22 '13 at 21:57
  • It is not necessary but as a safe measure it is always recommend to have the accessibility as low as possible,and the class will never be extended anyway. – Java Panter Aug 22 '13 at 21:59
  • If you want the *accessibility* to be as low as possible, it should be private, not final. final is not an accessibility modifier. I know you know that, but your terminology is incorrect. – JB Nizet Aug 22 '13 at 22:01
  • Yes you're right.It should be private but not necessary final. – Java Panter Aug 22 '13 at 22:04
  • making the inner class static does not help. I still get `unresolved compilation problem: TreeSet cannot be resolved to a type` – eagertoLearn Aug 22 '13 at 22:07
  • It runes for me. I'll post you the code.Maybe you forgot the to import Java.util.TreeSet; – Java Panter Aug 22 '13 at 22:09
1

Comparing or storing buffers is dangerous, for a few reasons.

  1. StringBuffer is unnecessarily threadsafe, and essentially deprecated in favor of StringBuilder. Use that class instead.
  2. Both StringBuffer and StringBuilder are mutable, meaning they cannot be safely inserted into a TreeSet, as they could be changed (for instance, sb.insert(0, 'z') would make the string start with z, changing the result of any successive comparisons) and therefore break the TreeSet.
  3. There's little benefit to storing such an object, it's trivial to construct a new StringBuilder later if you need to keep using it.
  4. Your comparator is slow, it needs to reconstruct the string every time the comparator is called, which is O(m log n) where m is the buffer length and n is the tree size.

I would strongly suggest simply storing the strings in your TreeSet directly instead. This will work cleaner, be faster, and avoid dangerous edge cases like rendering the TreeSet out of order.


Here's an example that demonstrates how using mutable objects like buffers in a TreeSet breaks all expectations:

public static void main(String[] args) {
    TreeSet<StringBuilder> tree = new TreeSet<>(new Comparator<StringBuilder>() {
        @Override
        public int compare(StringBuilder one, StringBuilder two) {
            return one.toString().compareTo(two.toString());
        }});

    char from = 'a', to = 'm'; // change to adjust map size
    char holdChar = 'd'; // change holdChar to adjust error location
    StringBuilder hold = null;

    for(char c = from; c <= to; c++) {
        StringBuilder sb = new StringBuilder().append(c).append(c).append(c);
        tree.add(sb);
        if(c == holdChar) {
            hold = sb;
        }
    }
    System.out.println(tree);

    hold.insert(0, to);

    for(char c = from; c <= to; c++) {
        StringBuilder sb = new StringBuilder().append(c).append(c).append(c);
        if(c == holdChar) {
            sb.insert(0, to);
        }
        System.out.println("Tree contains "+sb+(tree.contains(sb) ? "" : " NOPE!!"));
    }
    System.out.println(tree);
}

In theory, every StringBuilder being tested in the second for loop exists in the map, but the map is no longer able to accurately determine this:

[aaa, bbb, ccc, ddd, eee, fff, ggg, hhh, iii, jjj, kkk, lll, mmm]
Tree contains aaa
Tree contains bbb
Tree contains ccc
Tree contains mddd
Tree contains eee NOPE!!
Tree contains fff NOPE!!
Tree contains ggg NOPE!!
Tree contains hhh NOPE!!
Tree contains iii NOPE!!
Tree contains jjj NOPE!!
Tree contains kkk NOPE!!
Tree contains lll NOPE!!
Tree contains mmm
[aaa, bbb, ccc, mddd, eee, fff, ggg, hhh, iii, jjj, kkk, lll, mmm]

Even worse, due to the underlying tree structure, exactly which fields are or are not correctly found is dependant on the map size and the error point. Play with the from/to/holdChar values, and you'll see arbitrarily different results. Try holdChar = 'f'; for example.

dimo414
  • 47,227
  • 18
  • 148
  • 244
  • Thanks for your comments. my intention is here to learn how to implement Comparator for exisiting Java class. I will keep your suggestions in mind when I enter the real programming world! – eagertoLearn Aug 22 '13 at 22:09
  • There are plenty of other ways to use a comparator than with `StringBuffer`. Using it with a buffer is both dangerous and inefficient. If you're simply experimenting, consider trying to create a case-insentive comparator, or one which sorts on string length rather than lexicon. – dimo414 Aug 22 '13 at 22:35
0

right now you have a inner class called SbufferComparator. Since it is non-static, you need an instance to access its method. you could simply make it static and that work!

brain storm
  • 30,124
  • 69
  • 225
  • 393