Как обновить ключ HashMap из значения в классе? - PullRequest
1 голос
/ 15 марта 2020

Я пытаюсь динамически обновить ключи в HashMap.

Я создал экземпляр класса и установил ключ для получения значения из класса.

Я пытаюсь получить значение ключа, которое нужно изменить при обновлении значения в класс. Программа, для которой я пытаюсь это сделать, имеет несколько больших хэш-карт. Однако я упростил это до примера ниже.

Основной класс

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
    }
}

Другой класс

class OtherClass {
    int id;

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

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

    int getId() {
        return id;
    }
}

При обновлении значения в классе ключ в HashMap не меняется.

Возможно ли то, что я пытаюсь сделать, и если нет, то как мне этого добиться?

1 Ответ

4 голосов
/ 15 марта 2020

Если вы хотите, чтобы Map автоматически обновлялся при изменении id объекта OtherClass, вам нужно написать код для этого самостоятельно.

Если только карта и объект тесно связан, вы должны сохранить логику c отделенной, например, путем отслеживания изменений свойств.

Я бы рекомендовал построить его вокруг класса PropertyChangeSupport в Java Библиотека времени выполнения.


OtherClass

Сначала необходимо включить отслеживание изменений свойств.

Я добавил свойство name, чтобы улучшить вывод тестового кода в конце этого ответа .

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

Далее необходимо инкапсулировать Map, чтобы обработчик изменения свойства можно было правильно обработать.

Чтобы предотвратить утечки памяти, важно clear() OtherMap, когда он больше не нужен, в противном случае ссылка на один объект OtherMap, находящийся в OtherMap, сохранит карту и все объекты на карте живыми в памяти. Чтобы помочь с этим, я создал объект AutoCloseable, чтобы его можно было использовать с оператором try-with-resources, а анализаторы кода помогли выделить необходимость закрыть / очистить карту.

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

Тест

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);

Выход

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]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...