Использование интерфейса в качестве ключа в хэш-карте

#java #generics #hashmap

#java #общие сведения #хэш-карта

Вопрос:

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

 interface Foo {
        void function();
}
static class Bar implements Foo {

      private int id;
    
      public Bar(int id) {
          this.id = id;
      }
    
      @Override
      public void function() {
          System.out.println("this is bar");
      }
    
      @Override
      public boolean equals(Object o) {
           if (this == o) return true;
           if (o == null || getClass() != o.getClass()) return false;
           Bar bar = (Bar) o;
           return id == bar.id;
       }
    
       @Override
       public int hashCode() {
          return Objects.hash(id);
       }
}
    
public static Map<Foo, Integer> map = new HashMap<>();
    
    
     
static class Baz implements Foo {
    String name;
    public Baz(String name) {
        this.name = name;
    }

    @Override
    public void function() {
       System.out.println("this is Baz");
    }
    @Override
    public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Baz baz = (Baz) o;
       return name.equals(baz.name);
    }

    @Override
    public int hashCode() {
       return Objects.hash(name);
    }
}

public static void main(String[] args) {
    Bar bar = new Bar(123);
    Baz baz = new Baz("some name");

    map.put(bar, 10);
    map.put(baz, 20);
  
    System.out.println(map.get(bar));
 }
  

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

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

1. Звучит немного странно иметь разнородный набор ключей. Что, если у вас есть class Babble extends Bar Если кто-то вставил в ваш Bar’s ошибку, они не могут быть равны, даже если у них одинаковый идентификатор. Может быть, использовать, IsAssignableFrom

2. @matt: Я полагаю, вы имеете в виду реализацию equals / hashCode и наследование. Хороший момент. Я не стремился к тому, чтобы Bar быть расширенным, но, с другой стороны, я не думаю, что смог бы принудительно использовать все классы, используемые в качестве ключей, для final

3. «Есть ли случай, когда использование интерфейса в качестве ключа приведет к сбою?». Конечно, если кто-то реализует ваш интерфейс с неверным хэш-кодом / equals. Или просто изменяемый класс.

Ответ №1:

Единственное, что немного необычно, это то, что методы equals должны сравнивать объекты Bar и Baz. Когда карта содержит только один тип объектов, метод check this.getClass() == that.getClass in equals никогда не возвращает false . Однако вы реализовали это правильно, так что вам не о чем беспокоиться.

Вы можете получить больше коллизий хэшей, чем ожидаете. Представьте, что у вас есть два класса, которые оба имеют поле идентификатора int и реализуют hashCode с помощью Objects.hash(id) — теперь объекты разных классов с одинаковым идентификатором имеют одинаковый хэш-код. Если ожидается такой вариант использования, вы можете изменить хэш уникальным для каждого класса способом, например, путем подмешивания к хэшу константы, специфичной для класса:

 @Override
public int hashCode() {
   return Objects.hash(1, id);
}

...

@Override
public int hashCode() {
   return Objects.hash(2, name);
}
  

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

1. Отличная мысль! Теперь я понимаю. Но будет ли работать такая константа, как 1/2? Я имею в виду, не должно ли это быть какое-то простое или случайное число для каждого класса?

2. Objects.hash Метод позаботится об этом. Текущая реализация объединяет два значения путем умножения первого значения на простое и добавления второго значения.

Ответ №2:

Теоретически могут возникнуть проблемы с потенциально большим количеством коллизий хэшей, приводящих к снижению производительности из-за различных реализаций hashCode , поэтому вам нужно быть осторожным и протестировать его с реальными данными. В остальном это допустимый вариант использования.

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

1. Разве в наши дни не все генерируют hashCode через IDE? Все еще вызывает беспокойство реализация?

2. @Jim Если hashCode реализация подходит для одного типа, вы можете ожидать хорошего распределения хэшей и хорошей HashMap производительности. Сложнее рассуждать о распределении хэшей для нескольких типов. В любом случае, если вы не собираетесь HashMap хранить значительное количество ключей (> 10000) и ваши hashCode методы в порядке, все должно работать нормально.

3. It's harder to reason about hash distribution for multiple types. это очень интересно. Не могли бы вы, пожалуйста, пояснить?