35

I have Set of that structure. I do not have duplicates but when I call: set.add(element) -> and there is already exact element I would like the old to be replaced.

import java.io.*;

public class WordInfo implements Serializable {
    File plik;
    Integer wystapienia;

    public WordInfo(File plik, Integer wystapienia) {
        this.plik = plik;
        this.wystapienia = wystapienia;
    }

    public String toString() {
    //  if (plik.getAbsolutePath().contains("src") && wystapienia != 0)
            return plik.getAbsolutePath() + "\tWYSTAPIEN " + wystapienia;
    //  return "";
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(!(obj instanceof WordInfo)) return false;
        return this.plik.equals(((WordInfo) obj).plik);
    }

    @Override
    public int hashCode() {        
        return this.plik.hashCode();
    }
}
Lii
  • 11,553
  • 8
  • 64
  • 88
Yoda
  • 17,363
  • 67
  • 204
  • 344
  • Unable to understand the relation between the code snippet and the question. Can you explain, where is the Set it in your code? Also, if your objects are exactly same, then why you want to replace? – Yogendra Singh Nov 19 '12 at 04:15
  • @YogendraSingh - OP wants to be able to replace an old `WordInfo` in a set with a newer one that `equals()` the old one but is not the same object. (Note that the `equals()` test ignores the value of `wystapienia`.) – Ted Hopp Nov 19 '12 at 04:27
  • @TedHopp: Thanks, but still `return this.plik.equals(((WordInfo) obj).plik);` will make it to return `true`, no?? – Yogendra Singh Nov 19 '12 at 04:32
  • @YogendraSingh - My point was that two `WordInfo` objects can be `equals()` without being `==`. When two are `equals()` but not `==`, OP wants to be able to replace one with the other in a `Set`. OP's question is: how to make the replace happen. – Ted Hopp Nov 19 '12 at 05:01
  • @TedHopp: I am trying to understand the question itself with the OP, i.e. `why?`, which I am still not convinced(may be this was pseudo example). I see your answer for `how`, which is good. I will leave it now. Thanks for your inputs. – Yogendra Singh Nov 19 '12 at 05:08

5 Answers5

65

Do a remove before each add:

 someSet.remove(myObject);
 someSet.add(myObject);

The remove will remove any object that is equal to myObject. Alternatively, you can check the add result:

 if(!someSet.add(myObject)) {
     someSet.remove(myObject);
     someSet.add(myObject);
 }

Which would be more efficient depends on how often you have collisions. If they are rare, the second form will usually do only one operation, but when there is a collision it does three. The first form always does two.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
  • This causes me `Exception in thread "main" java.util.ConcurrentModificationException` when used with an `Iterator` –  Jun 18 '15 at 15:35
  • 2
    @ThreaT That is a separate problem from this question, which was about changing the behavior of `add` in a situation in which it could be called. I suggest asking it as a new question, with more background than you can put in a comment, unless you can find existing questions that help you. – Patricia Shanahan Jun 18 '15 at 16:03
  • hmm, then we can use simple `List` instead of `Set` – YTerle Apr 16 '18 at 12:03
  • The if statement from the second block of code could be simplified to `if(someSet.remove(myObject))...` as the `remove` method also returns true if `myObject` exists in the set. I would also think `contains` would be more efficient for the if, but less efficient on a whole when running the contents of the if statement. – dzimney Feb 23 '21 at 23:27
6

If the set already contains an element that equals() the element you are trying to add, the new element won't be added and won't replace the existing element. To guarantee that the new element is added, simply remove it from the set first:

set.remove(aWordInfo);
set.add(aWordInfo);
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
1

I was working on a problem where I had a set then I wanted to replace/override some of the objects with objects from another set.

In my case what I ended up doing was creating a new set and putting the overrides in first then adding the current objects second. This works because a set won't replace any existing objects when adding new objects.

If you have:

Set<WordInfo> currentInfo;
Set<WorldInfo> overrides;

Instead of:

for each override, replace the object in current info

I did:

Set<WordInfo> updated = new HashSet<>();
updated.addAll(overrides);
updated.addAll(currentInfo);
Cadell Christo
  • 3,105
  • 3
  • 21
  • 19
0

Try something as follows (this will only make sense if the equals and hashCode depends on one field, but the other fields could have different values):

if(!set.add(obj)) {
    //set already contains the element (not the same object though) 
    set.remove(obj); //remove the one in  the set
    set.add(obj); //add the new one
}

Check out the documentation for the Set.add method

If this set already contains the element, the call leaves the set unchanged and returns false.

Bhesh Gurung
  • 50,430
  • 22
  • 93
  • 142
-2

Check the HashSet code within the JDK. When an element is added and is a duplicate, the old value is replaced. Folk think that the new element is discarded, it's wrong. So, you need no additional code in your case.

UPDATED---------------------

I re-read the code in JDK, and admit a mistake that I've made.

When put is made, the VALUE is replaced not the KEY from an HashMap.

Why am I talking about Hashmap??!! Because if you look at the HashSet code, you will notice:

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

So the PRESENT value is replaced with the new one as shown in this portion of code:

      public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

But I agree, the key isn't replaced, and since the keys represent the HashSet's values, this one is said to be "untouched".

Mik378
  • 21,881
  • 15
  • 82
  • 180
  • 2
    That is an internal implementation detail that should ***not*** be relied on. – Perception Nov 19 '12 at 04:17
  • If the replacement happens, then HashSet is not following the Set contract for add: "the call leaves the set unchanged and returns false" – Patricia Shanahan Nov 19 '12 at 04:20
  • 3
    -1 this is just plain wrong: same hashcode does not mean same object. The new element **is** discarded! See [javadoc](http://docs.oracle.com/javase/6/docs/api/java/util/HashSet.html#add(E)): `If this set already contains the element, the call leaves the set unchanged and returns false` – Bohemian Nov 19 '12 at 04:20
  • @Bohemian I've never said that I'm talking about same hashcodes. Talking about same objects. – Mik378 Nov 19 '12 at 04:24
  • 1
    I've run a test, and I'm glad to confirm that HashSet does indeed conform to the Set contract, keeping the old object in case of an attempt to add an object that is equal to an existing element of the Set. – Patricia Shanahan Nov 19 '12 at 04:31
  • @Bohemian By reading this code (from HashMap.java), I understand that the value is effectively replaced as the javadoc (in code) says. I didn't invent it. (HashSet using HashMap as collaborator) See here: https://gist.github.com/a54404f49d8ecf5d6d79 – Mik378 Nov 19 '12 at 04:31
  • @Mik378 Map and Set are different interfaces. The contract for Map.put is not the contract for Set.add. The question is about Set, and mentions specifically HashSet. – Patricia Shanahan Nov 19 '12 at 04:34
  • @Patricia Shanahan HashSet is based on HashMap (see the code in JDK). – Mik378 Nov 19 '12 at 04:35
  • @Mik378 some good research by everyone. I removed the downvote. – Bohemian Nov 19 '12 at 18:02