How to update HashMap key from value in a class?

Matthew :

I am trying to dynamically update the keys in a HashMap.

I have created an instance of a class and set the key to get the value from the class.

I am trying to get the value of the key to change when I update the value in the class. The program I am trying to do this for has multiple large hashmaps. However, I have simplified it to the example below.

Main class

import java.util.HashMap;

public class Main {

    public static void main(String[] args) {
        HashMap<Integer, String> hashMap = new HashMap<>();
        OtherClass otherClass = new OtherClass(5);

        hashMap.put(otherClass.getId(), "a string");
        otherClass.setId(0);   // update value in class

        System.out.println(hashMap.keySet());   // outputs 5 not 0
    }
}

Other class

class OtherClass {
    int id;

    OtherClass (int id) {
        this.id = id;
    }

    void setId(int id) {
        this.id = id;
    }

    int getId() {
        return id;
    }
}

When I update the value in the class the key in the HashMap does not change.

Is what I'm trying to do even possible and if not how could I achieve this?

Andreas :

If you want the Map to auto-update when the id of an OtherClass object is modified, then you need to write the code for that yourself.

Unless the map and the object is tightly coupled, you should keep the logic decoupled, e.g. by implementing property change tracking.

I would recommend building it around the PropertyChangeSupport class in the Java Runtime Library.


OtherClass

First you need to enable property change tracking.

I added name property to improve test code output at end of this answer.

public final class OtherClass {
    private final transient PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private int id;
    private String name;

    public OtherClass(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(listener);
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        int oldId = this.id;
        this.id = id;
        this.pcs.firePropertyChange("id", oldId, id);
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        String oldName = this.name;
        this.name = name;
        this.pcs.firePropertyChange("name", oldName, name);
    }

    @Override
    public String toString() {
        return "OtherClass[" + this.id + ", " + this.name + "]";
    }
}

OtherMap

Next you need to encapsulate the Map so the property change listener can be correctly handled.

To prevent memory leaks, it's important to clear() the OtherMap when it is no longer needed, otherwise a reference to a single OtherMap object that is in the OtherMap will keep the map and all the objects in the map alive in memory. To help with that, I made the object AutoCloseable, so it could be used with a try-with-resources statement, and to make code analyzers help highlight the need to close/clear the map.

final class OtherMap implements AutoCloseable {
    private final PropertyChangeListener listener = this::onPropertyChange;
    private Map<Integer, OtherClass> map = new HashMap<>();

    public OtherMap() {
    }

    public Set<Integer> keys() {
        return Collections.unmodifiableSet(this.map.keySet());
    }

    public Collection<OtherClass> values() {
        return Collections.unmodifiableCollection(this.map.values());
    }

    public OtherClass get(int id) {
        return this.map.get(id);
    }

    public OtherClass add(OtherClass other) {
        OtherClass prev = this.map.put(other.getId(), other);
        if (prev != null)
            prev.removePropertyChangeListener(this.listener);
        other.addPropertyChangeListener(this.listener);
        return prev;
    }

    public OtherClass remove(int id) {
        OtherClass other = this.map.remove(id);
        if (other != null)
            other.removePropertyChangeListener(this.listener);
        return other;
    }

    public void clear() {
        this.map.values().forEach(other -> other.removePropertyChangeListener(this.listener));
        this.map.clear();
    }

    private void onPropertyChange(PropertyChangeEvent evt) {
        if (! "id".equals(evt.getPropertyName()))
            return;
        Integer oldId = (Integer) evt.getOldValue();
        Integer newId = (Integer) evt.getNewValue();
        if (oldId.equals(newId))
            return;
        OtherClass other = (OtherClass) evt.getSource();
        if (this.map.putIfAbsent(newId, other) != null)
            throw new IllegalStateException("Duplicate key");
        if (! this.map.remove(oldId, other)) {
            this.map.remove(newId);
            throw new IllegalStateException();
        }
    }

    @Override
    public String toString() {
        return this.map.toString();
    }

    @Override
    public void close() {
        clear();
    }
}

Test

OtherClass eeny  = new OtherClass(3, "Eeny");
OtherClass meeny = new OtherClass(5, "Meeny");
OtherClass miny  = new OtherClass(7, "Miny");
OtherClass moe   = new OtherClass(9, "Moe");

OtherMap otherMap = new OtherMap();
otherMap.add(eeny);
otherMap.add(meeny);
otherMap.add(miny);
otherMap.add(moe);
System.out.println("Before: " + otherMap);

meeny.setId(2);
otherMap.remove(miny.getId());
miny.setId(4);
System.out.println("After: " + otherMap);

Output

Before: {3=OtherClass[3, Eeny], 5=OtherClass[5, Meeny], 7=OtherClass[7, Miny], 9=OtherClass[9, Moe]}
After: {2=OtherClass[2, Meeny], 3=OtherClass[3, Eeny], 9=OtherClass[9, Moe]}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=297526&siteId=1