2012-04-19 9 views
7

Mam następujący HashMap o właściwościach kluczy i wartości:Java: porównanie HashMap <String, Object> Jeśli wartość może być Object []

private HashMap<String, Object> prop_values; 

muszę sprawdzić, czy jedna instancja jest równa inny. W przeszłości, po prostu to zrobił:

if (prop_values_1.equals(prop_values_2)){ 
    // do something 
} 

I to działało, dopóki mam Object[] jako wartość. Tak więc moje poprzednie wyrażenie zawsze zwracało false na takiej HashMap z dowolną wartością Object[].

Więc mam do wdrożenia tej metody:

private boolean isPropValuesEquals(HashMap<String, Object> pv1, HashMap<String, Object> pv2){ 
    boolean isEquals = true; 

    if (pv1 == null || pv2 == null){ 
     return false; 
    } 

    if (!pv1.keySet().equals(pv2.keySet())){ 
     return false; 
    } 

    for (String key : pv1.keySet()){ 

     Object cur_pv1 = pv1.get(key); 
     Object cur_pv2 = pv2.get(key); 

     if (cur_pv1 instanceof Object[]){ 
     if (cur_pv2 instanceof Object[]){ 
      isEquals = Arrays.equals((Object[])cur_pv1, (Object[])cur_pv2); 
     } else { 
      return false; 
     } 
     } else { 
     isEquals = isEquals && cur_pv1.equals(cur_pv2); 
     } 

     if (!isEquals){ 
     return false; 
     } 
    } 

    return isEquals; 

} 

to działa, ale wydaje się być pewnego rodzaju włamania, i nie jestem pewien, że to najlepszy sposób, aby osiągnąć to, czego potrzebuję.

Tak więc, oto dwa pytania.

  • dlaczego Object [] equals() nie jest taka sama jak Arrays.equals()? To wydaje się być bolesne.

  • Czy jest jakiś lepszy sposób na porównanie HashMap<String, Object>, jeśli wartościami mogą być Object[]?

+1

Użyj Listy zamiast Obiektu [], a wszystko będzie działać tak, jak oczekujesz. –

+0

@JBNizet, istnieje inny rodzaj bólu z ostrzeżeniem o bezpieczeństwie typu podczas rzutowania z 'obiektu' na typ sparametryzowany, taki jak' List '. –

+0

Masz ostrzeżenia, ale nie jest to mniej bezpieczne niż rzutowanie na obiekt []. Po prostu obuduj wszystko poprawnie i upewnij się, że masz solidny kod. –

Odpowiedz

3

Głębokie problemem jest to, że nie ma sposobu, aby zastąpić equals() tablicy. Dlaczego nie jest napisane jako "równe elementy w tej samej kolejności", nie mam pojęcia. Zdecydowanie mogło to być (o ile nie ma jakiegoś niejasnego powodu, aby tego nie robić, nie mogę o niczym pomyśleć, jeśli chcesz sprawdzić dla odniesienia równości, użyjesz ==, więc co by działało na uszkodzenie?).

Twoje rozwiązanie jest droga. Zaledwie kilka szczegółów do rozważenia:

  • Zamiast x instanceof Object[], można użyć x.getClass().isArray(), więc będzie pracować dla innych tablic, jak również, takie jak int[] (co jest nie podklasą Object[]). Wada: być może trzeba osobno sprawdzić, czy x jest null.

  • Jeśli tablice mogą zawierać zagnieżdżone tablice, należy rozważyć użycie Arrays.deepEquals().

wykazanie, że prymitywne tablice nie są Object[] s:

Object a = new int[1]; 
    System.out.println("" + (a instanceof Object[])); // false 
    System.out.println("" + a.getClass().isArray()); // true 

Kolejny ból w dupie to, że nawet jeśli okaże się, że x jest tablicą, trzeba jeszcze obsłużyć osobno przypadki dla wszystkich różnych typów elementów pierwotnych. Nie ma sposobu, aby obsłużyć je w sposób ogólny w systemie typu Java. Oczywiście, jeśli nie masz prymitywnych tablic w naszej mapie, nie musisz zajmować się tą sprawą.

+1

Jest to nieco niejasny aspekt Javy, ale w rzeczywistości wszystkie tablice są "instanceof Object []"! BTW, jeśli 'equals' został zaimplementowany w inny sposób niż jest, to złamałoby więcej rzeczy niż naprawę, jak sądzę. Po pierwsze, tablice powinny być surowym, niezwykle wydajnym rodzajem obiektu. –

+0

dla mnie '(new String [] {" foo "," bar "} instanceof Object [])' zwraca 'true', testowałem go przed implementacją tej metody. Tak więc 'String []' ** jest ** podklasą 'Object []'. Czy mógłbyś to wyjaśnić? Może nie wszystkie wersje Java mają takie zachowanie? I dzięki za 'deepEquals()'. –

+0

Mogę zapewnić, że ** wszystkie ** wersje Java zachowują się w ten sposób. Kiedy studiowałem Scalę i jej kowariancję/kontrawariancję, przeczytałem kiedyś wycinek Goslinga, który powiedział, że owszem, byli świadomi faktu, że kowariancja typu ma niewielki sens dla tablicy, ale oni zrobili to w taki sposób, aby ułatwić taki rodzaj W takim przypadku możesz przekazać dowolną tablicę jako argument. –

0

Równość może być różna: czy to samo odniesienie (równość referencyjna)? A może ma tę samą zawartość?

Problem z tablicami jest to, że są one zmienne. equals i hashCode obiektu musi zawsze przychodzić jako para, a kod skrótu nie powinien się zmieniać (w przeciwnym razie obiektów nie można znaleźć w tabelach mieszania). Dlatego normalny equals (i hashCode) nie można wykorzystać zawartość macierze mają opierać się na innym (nie zmienny) właściwości układu, który stanowi odniesienie.

+2

Nic nie zabrania zmiany hashCode. BTW, dwie listy są równe, jeśli zawierają równe obiekty w tej samej kolejności. To samo można było zrobić z tablicami, ale nie ma (i prawdopodobnie nigdy nie będzie, z powodu kompatybilności wstecznej). –

+0

@JBNizet Dodaj obiekt A do zaszyfrowanej kolekcji. Jeśli mieszanie A zmieni się, będzie ono w niewłaściwym segmencie, a wszystkie operacje w haszowanej kolekcji nie powiedzie się dla tego obiektu (na przykład "zawiera" itd.). – Lucero

+0

Tak, ale to nie znaczy, że A.hashCode jest błędny. To sprawia, że ​​korzystasz z A buggy. –

0
why Object[].equals() is not the same as Arrays.equals()? 
  • Object1.equals(Object2) jest taka sama jak Object1 == Object2, tj porównywaniu adresów obiektow (nie jest to oczekiwane przez wielu programistów, ale to jest niestety prawda)
  • Arrays.equals (array1, tablica2) porównuje zawartość tablic.

Dla pierwszego punktu:
Jest to najbardziej streszczenie equals() metody, tak samo jak wszystkich klas rozciągają się od obiektu. String.equals(String) to zastąpiony przypadek tej metody, w której działa ona tak jak Arrays.equals(array1, array2). Robiąc to, utorowali drogę innym programistom, aby nadpisali to, aby ich cele były jak najbardziej elastyczne.

Lepiej używać List<Object> zamiast Object[], ponieważ ma wszystkie gotowe metody i może zaspokoić wszystkie potrzeby.

+0

Tak, ale * dlaczego *? –

+1

To jest to, co społeczność programistów java zakodowała, to są ich zasady. – GingerHead

+0

Tak, ale * dlaczego * tak to zakodowali? Czy jest jakieś uzasadnienie, czy jest to tylko arbitralna decyzja? –

0

Metoda równa równa dla klasy Obiekt implementuje najbardziej rozróżniającą możliwą relację równoważności dla obiektów; oznacza to, że dla dowolnych zerowych wartości odniesienia x i y ta metoda zwraca wartość true wtedy i tylko wtedy, gdy x i y odnoszą się do tego samego obiektu (x == y ma wartość true).

0

Miły, ale także nieefektywne realizacja jest

  • kopia mapa
  • zastępuje żadnych tablic z owiniętym listy
  • zrobić porównanie za pomocą standardowych .equals(), to będzie działać, ponieważ na liście wrappery wykonają właściwe głębokie wyrównanie na podstawowej macierzy.

Kod

public static Map<String, Object> arraysToLists(Map<String, Object> map) { 
    Map<String, Object> copy = new HashMap<String, Object>(map); 
    for (Entry<String, Object> entry : copy.entrySet()) { 
     if (entry.getValue() instanceof Object[]) { 
      entry.setValue(Arrays.asList((Object[]) entry.getValue())); 
     } 
    } 
    return copy; 
} 

public static boolean mapCompare(Map<String, Object> map1, 
     Map<String, Object> map2) { 
    return arraysToLists(map1).equals(arraysToLists(map2)); 
} 

przypadek testowy

public static void main(String[] args) { 
    Map<String, Object> testA = new HashMap<String, Object>(); 
    testA.put("foo", new Object[] { "1", "2" }); 
    Map<String, Object> testB = new HashMap<String, Object>(); 
    testB.put("foo", new Object[] { "1" }); 
    Map<String, Object> testC = new HashMap<String, Object>(); 
    testC.put("foo", new Object[] { "1", "2" }); 

    // demonstrate equals() broken with Object arrays 
    System.out.println(testA.equals(testB)); // expect false 
    System.out.println(testA.equals(testC)); // expect true 

    // demonstrate new function works OK 
    System.out.println(mapCompare(testA, testB)); // expect false 
    System.out.println(mapCompare(testA, testC)); // expect true 
} 

Wyjście

false 
false 
false 
true 
0

Joonas jest poprawne, jednak chciałbym byłaby kod będzie następujący. Możesz sprawdzić to samodzielnie, aby znaleźć różnice.

private static boolean isPropValuesEquals(Map<String, Object> pv1, Map<String, Object> pv2) { 
    if (pv1 == null || pv2 == null || pv1.size() != pv2.size()) 
     return false; 

    for (Map.Entry<String, Object> entry : pv1.entrySet()) { 
     Object cur_pv1 = entry.getValue(); 
     Object cur_pv2 = pv2.get(entry.getKey()); 

     if (cur_pv1 == null^cur_pv2 == null) // if exactly one is null they're "not equal" 
      return false; 

     if (cur_pv1 != null) { 
      if (cur_pv1.getClass().isArray()) { 
       if (Arrays.deepEquals((Object[]) cur_pv1, (Object[]) cur_pv2)) 
        return false; 
      } else { 
       if (!cur_pv1.equals(cur_pv2)) { 
        return false; 
       } 
      } 
     } 
    } 

    return true; 
} 
Powiązane problemy