HashMap создает дубликаты ключей?

#java #hashmap #directed-graph

Вопрос:

У меня есть класс Vertex , определенный как:

 class Vertex {
    private String s;

    public Vertex(String s) {
        this.s = s;
    }

    public String getName() {
        return s;
    }

    public void setName(String s) {
        this.s = s;
    }

    public boolean equals(Vertex o) {
        return s.equals(o.getName());
    }
    
    @Override
    public String toString() {
        return s;
    }
}
 

и класс DirectedGraph со следующими методами:

 private HashMap<Vertex, HashSet<Vertex>> adjacencyMap = new HashMap<>();

public boolean addVertex(Vertex v) {
    if (!this.adjacencyMap.containsKey(v)) {
        this.adjacencyMap.put(v, new HashSet<>());
        return true;
    }
    return false;
}

public boolean addEdge(Vertex x, Vertex y) {
    if (adjacencyMap.containsKey(x) amp;amp; adjacencyMap.containsKey(y)) {
        if (!adjacencyMap.get(x).contains(y)) {
            adjacencyMap.get(x).add(y);
            edgeCount  ;
            return true;
        }
    }
    return false;
}

public DirectedGraph(File f) throws FileNotFoundException {
    Scanner s = new Scanner(f);
    while (s.hasNextLine()) {
        String l = s.nextLine();
        int i = 0;
        Vertex parent = null;
        for (String t : l.split(" ")) {
            if (i == 0) {
                parent = new Vertex(t);
                if (root == null) {
                    root = parent;
                }
                addVertex(parent);
            } else {
                Vertex v = new Vertex(t);
                addVertex(v);
                if (addEdge(parent, v) != true) System.out.println(parent   ", "   v   "failed");
                // System.out.println("adding edge: "   parent   ", "   v);
            }
            i  ;
        }
    }
    s.close();
    for (Vertex v : adjacencyMap.keySet()) {
        System.out.println(v   ": n    "   adjacencyMap.get(v));
    }
}
 

как вы можете видеть, он берет файл и сканирует его строка за строкой, предполагая, что первый узел является «родительским» узлом, а следующие зависят от него. Если я использую следующий входной текстовый файл:

 B D G
C A
E B F H
J B
I C
 

My issue is that my output is:

 A: 
    []
J: 
    []
F: 
    []
C: 
    []
J: 
    [B]
B: 
    []
C: 
    []
G: 
    []
E: 
    [F, B, H]
B: 
    []
H: 
    []
A: 
    [J, C, E]
I: 
    [C]
B: 
    [G, D]
D: 
    []
E: 
    []
C: 
    [A]
 

Как вы можете видеть, у моего HashMap есть несколько дубликатов ключей, потому что я не должен понимать, как containsKey это работает. У меня есть две идеи для решения моей проблемы:
во-первых, и более пещерно, это просто повторить keySet() , вручную сравнить имена вершин и объединить .get() s или
обновить addVertex или addEdge, в зависимости от того, что является основным нарушителем, для распознавания дубликатов ключей.

Я тоже не уверен, как это сделать, так что толчок в правильном направлении был бы наиболее полезен.

Ответ №1:

HashMap использует функцию хеширования для вычисления индексов для объектов, используемых в качестве key . Для этой функции, чтобы работать правильно, вы должны предоставить как equals() и hashCode() функции для класса, который экземпляры, которые вы планируете использовать в качестве ключей в хэш-карте.
Это означает, что вы должны переопределить public int hashCode() метод, который теперь по наследству прямо из Object суперкласса, и поставить там какие-нибудь расчеты, которые возвращают одно и то же число для объектов, которые считаются «одинаковый». К счастью, вы сравниваете String s в качестве этого критерия, поэтому вы можете использовать String метод hashCode , полученный с:

 @Override
public int hashCode() {
    return s.hashCode();
}
 

Вторая проблема, которую я ранее упустил, заключается в том, что ваша реализация equals() неверна. Вы должны override Object equals() иметь подпись public boolean equals( Object o ) , и вы реализовали ее в Vertex качестве аргумента, что привело к тому, что при добавлении Object в хэш-карту «s equals was used, as you didn't override by overload равно . That's one of the reasons that annotation @Переопределить should be used, compiler would tell you that you don't override anything and you would know that you're doing something wrong. Now, going back to how to implement равно» должным образом….

 @Override
public boolean equals(Object o) {
    if( o instanceof Vertex ) {
        return s.equals((( Vertex)o).getName());
    } else { 
        return false;
    }
}
    
 

Теперь экземпляры Vertex будут вести себя правильно при использовании в качестве ключа в хэш-карте.

В качестве дополнительного замечания, помещать всю логику, включая открытие, разбор файла в конструктор, на мой вкус, немного чересчур, вместо этого подумайте о том, чтобы разделить его на несколько методов.

Комментарии:

1. вывод теперь в алфавитном порядке, но дубликаты ключей все еще там.

2. @ganondork смотрите обновленный ответ, в следующий раз предоставьте образец компилируемого кода, который облегчит запуск вашего кода и проверку вашей проблемы и возможного решения.

3. Он отлично компилируется в Eclipse. Компилятор был тем, кто сказал мне либо удалить переопределение, либо вернуть его к исходной подписи, я просто выбрал один из вариантов, не понимая, что это будет следствием. Спасибо, что разъяснили мне это!