4

Pls tell what is wrong is happening here. I have a Person class which I'm using as a key in TreeMap. I have implemented Comparable also so that TreeMap can do sorting.

public class Person implements Comparable{

private String name;
private int age;
// getters and setters were omitted
@Override
public int compareTo(Object o) {
    return 0;
}
}

Now I created TreeMap and added values in it as:

Map treeMap=new TreeMap<Person,Object>();

treeMap.put(new Person(), "String1");
treeMap.put(new Person(), "String2");
treeMap.put(new Person(), "String3");
treeMap.put(new Person(), "String4");

System.out.println(treeMap);

After printing directly using System.out.println(treeMap); Iam only getting the last inserted value ie

Output:{Person@4aa36c=String4}

I know keys should be different but new operator always create a new object so I think its fine. But Iam helpless to figure out what is wrong going on here.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
user3626306
  • 137
  • 2
  • 2
  • 9

4 Answers4

2

You are likely placing items in the Map backwards and would more than likely want the Person object as the value and the String as the key. But first you need to enhance your Person object with a Constructor to allow you to set the name at least, and maybe the age. Add a constructor to `Person':

public Person(String n, int a){
   this.name = n;
   this.age = a;
}

Then you can reorder how you add elements to the Map:

 Person p1 = new Person("Bob Jones", 32);
 treeMap.put(p1.getName(), person);

Additionally, TreeMap uses the compareTo method to determine where to place the entry in the Map for the keys. So, if you intend to use the Person object as the key, you need to implement the compareTo method. But your compareTo method is not implemented correctly and simply returns 0 for every element, causing your elements to overwrite each others location in the Map:

@Override
public int compareTo(Object o) {
    / TODO Auto-generated method stub
    return 0;
}

You need to properly implement the method contents (Hence the TODO statement).

Maybe add a social security number (SSN) to the Person class:

Long ssn;

public void setSsn(Long value){
    ssn = value;
}

public Long getSsn(){
    return ssn;
}

Then you could compare on ssn:

@Override
public int compareTo(Object o) {
    if(o == null) return 1;
    Person op = (Person)o;
    return ssn.compareTo(op.getSsn());
}

But if you just want to create some type of combination with what you have, maybe concatenate the name and age to try to have uniqueness:

@Override
public int compareTo(Object o) {
    if(o == null) return 1;
    Person op = (Person)o;
    return (name + age).compareTo(op.getName() + op.getAge());
}
pczeus
  • 7,709
  • 4
  • 36
  • 51
  • Implementing the `compareTo` method isn't the answer at all. See my answer. – 4castle Apr 02 '16 at 14:30
  • Yes, i see your point. I have updated the answer to include more information. Regardless, the compareTo method should be implemented and not stubbed out. So, I have added information on that as well. – pczeus Apr 02 '16 at 14:35
  • This still wouldn't work, the default constructor doesn't set a ssn number. Use my answer – 4castle Apr 02 '16 at 15:42
  • You are missing the point. What would be the point of constructing a Person, inline to adding it to a Map, where the person has not information set within it? The OP's example is not sound and yes you are showing how to make it syntactically correct given the poor example, but in no way improving the OP's logic or code. Pointless. – pczeus Apr 02 '16 at 19:42
  • I disagree, the keys could be accessed using `treeMap.keySet()` and further manipulated from there. You don't know how they plan on using the map. It's not pointless. The `compareTo` will be used again as the changes are made. – 4castle Apr 02 '16 at 20:00
2

You overrode toCompare method incorrectly. It always returns 0. So, all objects in the tree will be interpreted as the same and your treeMap always contains only the value that had been added the last.

I suggest you consider the compact (but not general as @4castle noted) solution.

@Override
public int compareTo(Person o) {
    if (age != o.age) return age > o.age ? 1 : -1;
    return name.compareTo(o.name);
}

If you changed the first line of the class declaration to

public class Person implements Comparable<Pesron>
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • This would still delete people with the same name & age. There needs to be a unique field for each `Person` to make this fully robust. – 4castle Apr 02 '16 at 14:59
  • @4castle, I don't take a care about this. It's just a learning example and the real class will not contain only 2 instance members. – Andrew Tobilko Apr 02 '16 at 15:02
  • It wouldn't be that hard to implement, just add a third case for if `name.equals(o.name)` then use an ID number or something. – 4castle Apr 02 '16 at 15:03
  • @4castle, we don't have any others fields (except `name` and `age`) from OP – Andrew Tobilko Apr 02 '16 at 15:10
  • @4castle, so we shouldn't imagine something new – Andrew Tobilko Apr 02 '16 at 15:12
  • The reason this is vital is because currently the default constructor doesn't set a value to any of the fields, which means this still would just collapse all of the items into the same key given how the OP puts items in the map. – 4castle Apr 02 '16 at 15:13
  • I thought about it some more, and at minimum you could use the `Object#toString()` as the last resort for the `compareTo`. That way you could use the memory address to distinguish the objects if all else fails, and you wouldn't have to do anything with the constructor. Until you fix the issue of values not being initialized (which is done in the question), this answer is actually wrong. – 4castle Apr 02 '16 at 16:35
2

TreeMap uses compareTo method of Comparable (not equals method from Object) when it tries to put an element into Map. Since your compareTo method implementation returns 0 for all Person objects, there can only be one element in TreeMap.

So when you try to put multiple Person keys, TreeMap only updates value, since for it all Person objects are same (as per your implementation for compareTo).

Here is code snippet for TreeMap.put from Java-8

    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        do {
            parent = t;
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    else {
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }

So first it checks, whether Comparator is provided, if yes, TreeMap uses compare method to compare keys otherwise it uses compareTo method of Comparable for comparison.

justAbit
  • 4,226
  • 2
  • 19
  • 34
0

Keys aren't unique based on their memory address, they are unique based on having a unique value. All of your Person objects are practically identical, but what's more, all of your Person objects claim to be "equal" to any other Person object given your current compareTo implementation (0 means equal).

The parameter order for the put method is key first, value second. You need to rearrange the put statements so that something unique is in front (the string), and therefore make all the entries unique.

treeMap.put("String1", new Person());
treeMap.put("String2", new Person());
treeMap.put("String3", new Person());
treeMap.put("String4", new Person());

Now there's no need to implement Comparable or have the compareTo method in Person.

If you require that the Person object be a key, you will have to make each one unique by giving it a unique variable with which to use the compareTo method properly. The best solution is to add a required ID number to each Person object, or otherwise use the name and age as the unique value for the object. Add on this code to Person:

private int id;
private static int numPeople = 0;
public Person() {
    numPeople++;
    id = numPeople;
    name = "";
}
@Override
public int compareTo(Person o) {
    if (age != o.age) return age > o.age ? 1 : -1;
    if (!name.equals(o.name) return name.compareTo(o.name);
    return id > o.id ? 1 : -1;
}

with

public class Person implements Comparable<Person>
4castle
  • 32,613
  • 11
  • 69
  • 106
  • I think OP wants to use `Person` as a key, he wrote about that. – Andrew Tobilko Apr 02 '16 at 14:43
  • @AndrewTobilko I've updated my answer to suggest an ID number, because names aren't guaranteed to be unique. – 4castle Apr 02 '16 at 14:57
  • Just a note, currently it's impossible to use your key properly to retrieve the value again using `get`. You will need to override the `equals` method in `Person` in order to find that key again. – 4castle Apr 02 '16 at 16:07