2013-10-26 3 views
0

У меня есть класс, который имеет два поля строк. Любой из них (но не оба) может быть нулевым.Возможно ли реализовать метод hashCode()?

public class SimpleBluetoothDevice { 

    final String macAddress; 
    final String name; 

    public SimpleBluetoothDevice(String name, String macAddress) { 
     this.macAddress = macAddress; 
     this.name = name; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (o == this) { 
      return true; 
     } 
     if (!(o instanceof SimpleBluetoothDevice)) { 
      return false; 
     } 
     SimpleBluetoothDevice otherDevice = (SimpleBluetoothDevice) o; 
     if (name == null || otherDevice.name == null) { 
      return otherDevice.macAddress.equalsIgnoreCase(macAddress); 
     } 
     if (macAddress == null || otherDevice.macAddress == null) { 
      return otherDevice.name.equals(name); 
     } 
     return name.equals(otherDevice.name) || macAddress.equalsIgnoreCase(otherDevice.macAddress); 
    } 

    @Override 
    public int hashCode() { 
     int hash = 1; 
     hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); 
     hash = 31 * hash + ((macAddress == null) ? 0 : macAddress.toLowerCase(Locale.US).hashCode()); 
     return hash; 
    } } 

Тестирование

public class Main { 

    private static final List<SimpleBluetoothDevice> DEVICE_LIST = new ArrayList<SimpleBluetoothDevice>(); 
    private static final Set<SimpleBluetoothDevice> DEVICE_SET = new HashSet<SimpleBluetoothDevice>(); 

    static { 
     DEVICE_LIST.add(new SimpleBluetoothDevice(null, "11-22-33-44-55-aa")); 
     DEVICE_LIST.add(new SimpleBluetoothDevice("iPad", "11-22-33-44-55-BB")); 

     DEVICE_SET.add(new SimpleBluetoothDevice(null, "11-22-33-44-55-aa")); 
     DEVICE_SET.add(new SimpleBluetoothDevice("iPad", "11-22-33-44-55-BB")); 
    } 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     SimpleBluetoothDevice bluetoothDevice = new SimpleBluetoothDevice("Android", "11-22-33-44-55-AA"); 
     System.out.println(DEVICE_LIST.contains(bluetoothDevice)); // TRUE 
     System.out.println(DEVICE_SET.contains(bluetoothDevice)); // FALSE 
    } 

} 

Set действительно содержит bluetoothDevice, а значение false был возвращен из-за некорректной реализации hashCode().

Можно ли использовать hashCode здесь, чтобы использовать коллекции на основе хэша? Два устройства будут равны, если их MAC-адреса или имена будут равны (или MAC-адреса и имена равны соответственно).

Обновление № 1.

public class Main { 

    private static final List<SimpleBluetoothDevice> BLUETOOTH_DEVICES = new ArrayList<SimpleBluetoothDevice>(); 

    static { 
     BLUETOOTH_DEVICES.add(new SimpleBluetoothDevice(null, "38:ec:e4:d7:ad:a2")); 
     BLUETOOTH_DEVICES.add(new SimpleBluetoothDevice("Nokia N9", "40:98:4E:48:1D:B0")); 
     BLUETOOTH_DEVICES.add(new SimpleBluetoothDevice("Galaxy S4", "08:FC:88:AD:4A:62")); 
    } 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     SimpleBluetoothDevice one = new SimpleBluetoothDevice(null, "38:ec:e4:d7:ad:a2"); 
     SimpleBluetoothDevice two = new SimpleBluetoothDevice("GT-I9003", "38:ec:e4:d7:ad:a2"); 
     SimpleBluetoothDevice three = new SimpleBluetoothDevice("GT-I9003", "123"); 
     System.out.println(one.equals(two)); 
     System.out.println(two.equals(three)); 
     System.out.println("Transitivity test. " + one.equals(three)); 
     System.out.println("Contains test. " + BLUETOOTH_DEVICES.contains(one)); 
     System.out.println("Contains test. " + BLUETOOTH_DEVICES.contains(two)); 
     System.out.println("Contains test. " + BLUETOOTH_DEVICES.contains(three)); 
    } 

    /** 
    * This class only contains two text fields: MAC address and name. 
    * 
    * @author Maxim Dmitriev 
    * 
    */ 
    private static final class SimpleBluetoothDevice { 

     final String macAddress; 
     final String name; 

     SimpleBluetoothDevice(String name, String macAddress) { 
      this.macAddress = macAddress; 
      this.name = name; 
     } 

     @Override 
     public String toString() { 
      return "Name: " + name + ", MAC address: " + macAddress; 
     } 

     @Override 
     public boolean equals(Object o) { 
      if (o == this) { 
       return true; 
      } 
      if (!(o instanceof SimpleBluetoothDevice)) { 
       return false; 
      } 
      SimpleBluetoothDevice otherDevice = (SimpleBluetoothDevice) o; 
      if (name == null) { 
       return macAddress.equalsIgnoreCase(otherDevice.macAddress); 
      } else if (macAddress == null) { 
       return name.equals(otherDevice.name); 
      } else { 
       return name.equals(otherDevice.name) || macAddress.equalsIgnoreCase(otherDevice.macAddress); 
      } 
     } 

     /** 
     * It is recommended to override {@link Object#hashCode()} in every class that overrides 
     * {@link Object#equals(Object)}. <br><br> But two instances of this class will be equal, if 
     * their MAC addresses (the 
     * case of the characters is ignored) or names are equal. Collections, such as 
     * {@link HashSet}, {@link HashMap}, cannot be used because the hash codes of logically 
     * equal instances are not the same. 
     * 
     */ 
     @Override 
     public int hashCode() { 
      return 1; 
     } 
    } 
} 

Я изменил код. Таким образом, два объекта считаются равными, если

  • их имена равны
  • их МАС-адреса являются равноправными пренебрегая соображения случае
  • оба условия выполнены

Update # 2.

Без equals

public class Main { 

    private static final Set<SimpleBluetoothDevice> BLUETOOTH_DEVICES = new HashSet<SimpleBluetoothDevice>(); 

    static { 
     BLUETOOTH_DEVICES.add(new SimpleBluetoothDevice("G1", "38:ec:e4:d7:ad:a2")); 
     BLUETOOTH_DEVICES.add(new SimpleBluetoothDevice("Nokia N9", "40:98:4E:48:1D:B0")); 
     BLUETOOTH_DEVICES.add(new SimpleBluetoothDevice("Galaxy S4", "08:FC:88:AD:4A:62")); 
    } 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     SimpleBluetoothDevice myDevice = new SimpleBluetoothDevice(null, "38:ec:e4:d7:ad:a2"); 
     for (SimpleBluetoothDevice device : BLUETOOTH_DEVICES) { 
      if (myDevice.macAddress == null || device.macAddress == null) { 
       if (myDevice.name.equals(device.name)) { 
        System.out.println("Name"); 
        break; 
       } 
      } else if (myDevice.name == null || device.name == null) { 
       if (myDevice.macAddress.equalsIgnoreCase(device.macAddress)) { 
        System.out.println("MAC"); 
        break; 
       } 
      } else { 
       if (myDevice.macAddress.equalsIgnoreCase(device.macAddress) || myDevice.name.equals(device.name)) { 
        System.out.println("Either of them"); 
        break; 
       } 
      } 
     } 
    } 

    /** 
    * This class only contains two text fields: MAC address and name. 
    * 
    * @author Maxim Dmitriev 
    * 
    */ 
    private static final class SimpleBluetoothDevice { 

     final String macAddress; 
     final String name; 

     /** 
     * 
     * @param name 
     * @param macAddress 
     * 
     * Throws an {@link IllegalArgumentException} if both parameters are null 
     */ 
     SimpleBluetoothDevice(String name, String macAddress) { 
      if (name == null && macAddress == null) { 
       throw new IllegalArgumentException("Both a name and a MAC address cannot be null"); 
      } 
      this.name = name; 
      this.macAddress = macAddress; 
     } 
    } 
} 

ответ

3

В настоящее время вы можете не реализовать даже equals таким образом, подчиняющийся contract of Object.equals который statesstates:

Равных метод реализует отношение эквивалентности на не- ссылки на нулевые объекты:

...

  • Это транзитивно: для любых ненулевых опорных значений x, y и z, если x.equals (y) возвращает true и y.equals (z) возвращает true, то x.equals (z) должен вернуть истинный

Рассмотрим эти три объекта:

x: Name=foo MacAddress=m1 
y: Name=bar MacAddress=m1 
z: Name=bar MacAddress=m2 

Теперь x.equals(y) будет справедливо, и y.equals(z) будет верно, что должно означать, что x.equals(z) верно ... но это не так ,

Пока вы не разработали форму равенства, которая удовлетворяет контракту транзитивности, не стоит беспокоиться о hashCode. Если ничего больше, реализация hashCode, которая всегда возвращает 0, всегда «правильная» (хотя явно не полезная с точки зрения производительности). Это не поможет вам, пока ваша проверка равенства будет нарушена.

+0

Да, мне нужно переосмыслить свой «equals» –

+0

Я изменил код, но тест на транзитивность не удался. Что мне нужно сделать? Мне нужно 'equals' только для [contains] (http://docs.oracle.com/javase/7/docs/api/java/util/List.html#contains (java.lang.Object)), и те тесты передаются. Пожалуйста, взгляните на ** Обновление № 1 ** –

+1

@ МаксимДмитриев: Принципиально ваше отношение равенства не является транзитивным. Это не проблема * реализации *, это проблема * design *. Для начала вам нужно разработать свое отношение равенства к транзитивному, и, хотя оно основано на «этой части равно * или *, эта часть равна», она не будет транзитивной. –

0

Для создания множества коллекций требуется, чтобы equals выполнял отношение эквивалентности, которое позволяет разделять объекты на множества эквивалентности (некоторые из которых могут содержать только один элемент), так что каждый объект внутри набора будет сравниваться равным, и ни один объект не будет сравните с любым объектом другого набора. Иногда бывает полезно выполнять «нечеткие» сравнения, которые могут находить не только объекты в том же наборе, что и данный объект, но и те, которые в некотором смысле «находятся рядом». Невозможно, чтобы один запрос мог найти элемент, который хранится только один раз, но может быть возможно достичь нечеткого соответствия, если один избыточно хранит элементы или использует несколько запросов (не обязательно очень много). Например, в вашем случае вы можете определить свою операцию равенства, чтобы оба поля совпадали, но при добавлении в таблицу элемента (X, Y), где ни одно значение не равно null, добавьте (X, null) и (null, Y). В качестве альтернативы вы могли бы иметь таблицу, которая отображает MAC-адреса для имен и другую, которая сопоставляет имена MAC-адресам; если задана одна форма идентификатора, но не другая, используйте соответствующую таблицу, чтобы получить другую, а затем посмотрите, как пара.

Смежные вопросы