Как заставить HashMap работать с массивами как ключом?

Я использую логические массивы как ключи для HashMap. Но проблема в том, что HashMap не получает ключи, когда другой массив передается как ключ, хотя элементы одинаковы. (Так как они разные объекты).

Как я могу заставить его работать с массивами в виде ключей? Вот код:

public class main {
public static HashMap<boolean[], Integer> h;


public static void main(String[] args){
    boolean[] a = {false, false};

    h = new HashMap<boolean[], Integer>();
    h.put(a, 1);


    if(h.containsKey(a)) System.out.println("Found a");

    boolean[] t = {false, false};

    if(h.containsKey(t)) System.out.println("Found t");
    else System.out.println("Couldn't find t");

}

}

Оба массива a и t содержат одни и те же элементы, но HashMap ничего не возвращает для t.

Как мне заставить работать?

Ответы

Ответ 1

Вы не можете этого сделать. Оба t и a будут иметь разные значения hashCode(), потому что метод java.lang.Array.hashCode() наследуется от Object, который использует ссылку для вычисления хэш-кода (реализация по умолчанию). Следовательно, хэш-код для массивов зависит от ссылки, что означает, что вы получите другое значение хеш-кода для t и a. Кроме того, equals не будет работать для двух массивов, потому что это также основано на ссылке.

Единственный способ сделать это - создать пользовательский класс, который сохраняет массив boolean как внутренний член. Затем вам необходимо переопределить equals и hashCode таким образом, чтобы экземпляры, содержащие массивы с одинаковыми значениями, были равны и также имели один и тот же хэш-код.

Более простым вариантом может быть использование List<Boolean> в качестве ключа. В документации реализация hashCode() для List определяется как:

int hashCode = 1;
Iterator<E> i = list.iterator();
while (i.hasNext()) {
    E obj = i.next();
    hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}

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

Ответ 2

Это невозможно сделать с помощью массивов, так как любые два разных массива не сравнивают equals, даже если они имеют одинаковые элементы.

Вам нужно отобразить из класса контейнера, например ArrayList<Boolean> (или просто List<Boolean>. Возможно, BitSet будет еще более уместным.

Ответ 3

Map реализация основана на методах equals и hashCode. Массивы в java напрямую простираются от Object, они используют значения по умолчанию equals и hashCode of Object, которые сравнивают только identity.

Если бы я был вами, я бы создал класс Key

class Key {
    private final boolean flag1;
    private final boolean flag2;

    public Key(boolean flag1, boolean flag2) {
        this.flag1 = flag1;
        this.flag2 = flag2;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Key)) {
            return false;
        }

        Key otherKey = (Key) object;
        return this.flag1 == otherKey.flag1 && this.flag2 == otherKey.flag2;
    }

    @Override
    public int hashCode() {
        int result = 17; // any prime number
        result = 31 * result + Boolean.valueOf(this.flag1).hashCode();
        result = 31 * result + Boolean.valueOf(this.flag2).hashCode();
        return result;
    }
}

После этого вы можете использовать свой ключ с Map:

Map<Key, Integer> map = new HashMap<>();

Key firstKey = new Key(false, false);
map.put(firstKey, 1);

Key secondKey = new Key(false, false) // same key, different instance
int result = map.get(secondKey); // --> result will be 1

Ссылка: хэш-код Java из одного поля

Ответ 4

boolean[] t;
t = a;

Если вы дадите это, вместо boolean[] t = {false, false};, вы получите желаемый результат.

Это связано с тем, что Map хранит reference как key, и в вашем случае, хотя t имеет одинаковые значения, он не имеет такой же ссылки, как a.

Следовательно, когда вы даете t=a, он будет работать.

Его очень похоже на это: -

String a = "ab";
String b = new String("ab");

System.out.println(a==b); // This will give false.

Оба a и b сохраняют одно и то же значение, но имеют разные ссылки. Следовательно, когда вы пытаетесь сравнить ссылку с помощью ==, она дает false.

Но если вы дадите, a = b;, а затем попробуйте сравнить reference, вы получите true.

Ответ 5

Вероятно, это потому, что метод equals() для массива возвращает действия разные, чем вы ожидаете. Вы должны подумать о том, как реализовать свой собственный сбор и переопределить equals() и hashCode().

Ответ 6

Карта использует equals() для проверки того, являются ли ваши ключи одинаковыми.

Стандартная реализация этого метода в Object проверяет ==, то есть ссылочное равенство. Итак, поскольку ваши два массива не являются одним и тем же массивом, equals всегда возвращает false.

Вам нужно сделать вызов карты Arrays.equals на двух массивах, чтобы проверить равенство.

Вы можете создать класс оболочки массива, который использует Arrays.equals, и тогда это будет работать как ожидалось:

public static final class ArrayHolder<T> {

    private final T[] t;

    public ArrayHolder(T[] t) {
        this.t = t;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 23 * hash + Arrays.hashCode(this.t);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ArrayHolder<T> other = (ArrayHolder<T>) obj;
        if (!Arrays.equals(this.t, other.t)) {
            return false;
        }
        return true;
    }
}

public static void main(String[] args) {
    final Map<ArrayHolder<Boolean>, Integer> myMap = new HashMap<>();

    myMap.put(new ArrayHolder<>(new Boolean[]{true, true}), 7);
    System.out.println(myMap.get(new ArrayHolder<>(new Boolean[]{true, true})));
}

Ответ 7

Вы можете создать класс, содержащий массив. Реализует методы hashCode() и equals() для этого класса на основе значений:

public class boolarray {
  boolean array[];

  public boolarray( boolean b[] ) {
     array = b;
  }

  public int hashCode() {
    int hash = 0;
    for (int i = 0; i < array.length; i++)
       if (array[i])
          hash += Math.pow(2, i);
    return hash;
  }

  public boolean equals( Object b ) {
     if (!(b instanceof boolarray))
        return false;
     if ( array.length != ((boolarray)b).array.length )
        return false;
     for (int i = 0; i < array.length; i++ )
        if (array[i] != ((boolarray)b).array[i])
           return false;
     return true;
  }
}

Затем вы можете использовать:

 boolarray a = new boolarray( new boolean[]{ true, true } );
 boolarray b = new boolarray( new boolean[]{ true, true } );
 HashMap<boolarray, Integer> map = new HashMap<boolarray, Integer>();
 map.put(a, 2);
 int c = map.get(b);
 System.out.println(c);

Ответ 8

Вы можете использовать библиотеку, которая принимает внешнюю стратегию хэширования и сравнения (trove).

class MyHashingStrategy implements HashingStrategy<boolean[]> {

    @Override
    public int computeHashCode(boolean[] pTableau) {
        return Arrays.hashCode(pTableau);
    }

    @Override
    public boolean equals(boolean[] o1, boolean[] o2) {
        return Arrays.equals(o1, o2);
    }
}


Map<boolean[], T> map = new TCustomHashMap<boolean[],T>(new MyHashingStrategy());

Ответ 9

Это должно работать для массивов любого типа:

class ArrayHolder<T> {
    private final T[] array;
    @SafeVarargs
    ArrayHolder(T... ts) { array = ts; }
    @Override public int hashCode() { return Arrays.hashCode(array); }
    @Override public boolean equals(Object other) {
        if (array == other) { return true; }
        if (! (other instanceof ArrayHolder) ) {
            return false;
        }
        //noinspection unchecked
        return Arrays.equals(array, ((ArrayHolder) other).array);
    }
}

Вот ваш конкретный пример, преобразованный для использования ArrayHolder:

// boolean[] a = {false, false};
ArrayHolder<Boolean> a = new ArrayHolder<>(false, false);

// h = new HashMap<boolean[], Integer>();
Map<ArrayHolder<Boolean>, Integer> h = new HashMap<>();

h.put(a, 1);

// if(h.containsKey(a)) System.out.println("Found a");
assertTrue(h.containsKey(a));

// boolean[] t = {false, false};
ArrayHolder<Boolean> t = new ArrayHolder<>(false, false);

// if(h.containsKey(t)) System.out.println("Found t");
assertTrue(h.containsKey(t));

assertFalse(h.containsKey(new ArrayHolder<>(true, false)));

Я использовал Java 8, но я думаю, что у Java 7 есть все, что вам нужно для этого. Я тестировал hashCode и равнялся с помощью TestUtils.

Еще одна мысль - Джошуа Блох. Пункт 25: "Предпочитает списки для массивов".