У меня есть класс, который имеет два поля строк. Любой из них (но не оба) может быть нулевым.Возможно ли реализовать метод 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;
}
}
}
Да, мне нужно переосмыслить свой «equals» –
Я изменил код, но тест на транзитивность не удался. Что мне нужно сделать? Мне нужно 'equals' только для [contains] (http://docs.oracle.com/javase/7/docs/api/java/util/List.html#contains (java.lang.Object)), и те тесты передаются. Пожалуйста, взгляните на ** Обновление № 1 ** –
@ МаксимДмитриев: Принципиально ваше отношение равенства не является транзитивным. Это не проблема * реализации *, это проблема * design *. Для начала вам нужно разработать свое отношение равенства к транзитивному, и, хотя оно основано на «этой части равно * или *, эта часть равна», она не будет транзитивной. –