Как заставить 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: "Предпочитает списки для массивов".