将对象存储在集合中时,同一对象永远不能添加两次非常重要。 这是集合的核心定义。 在Java中,使用两种方法来确定两个引用的对象是否相同或它们是否可以同时存在于同一Set中。 equals()和hashCode()。 在本文中,我将解释平等与同一性之间的区别,并探讨它们在彼此之间所具有的一些优势。
Java提供了这两种方法的标准实现。 标准的equals()方法被定义为“身份”比较方法。 这意味着它将比较两个内存引用以确定它们是否相同。 因此,存储在内存中不同位置的两个相同的对象将被视为不相等。 如果使用Object类的源代码,可以使用==-operator进行比较。
public boolean equals(Object obj) {return (this == obj);
}
hashCode()方法由虚拟机作为本机操作实现,因此在代码中不可见,但通常将其实现为简单地返回内存引用(在32位体系结构上)或以32位模表示内存引用(在64位体系结构上)。
许多程序员在设计类时选择要做的一件事就是用不同的相等性定义覆盖此方法,在该方法中,您不查看内存引用,而是查看两个实例的值以查看它们是否可以相等。 这是一个例子:
import java.util.Objects;
import static java.util.Objects.requireNonNull;public final class Person {private final String firstname;private final String lastname;public Person(String firstname, String lastname) {this.firstname = requireNonNull(firstname);this.lastname = requireNonNull(lastname);}@Overridepublic int hashCode() {int hash = 7;hash = 83 * hash + Objects.hashCode(this.firstname);hash = 83 * hash + Objects.hashCode(this.lastname);return hash;}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null) return false;if (getClass() != obj.getClass()) return false;final Person other = (Person) obj;if (!Objects.equals(this.firstname, other.firstname)) {return false;} else return Objects.equals(this.lastname, other.lastname);}
}
这种比较称为“平等”(与之前的“身份”相比)。 只要两个人的名字和姓氏相同,就将被视为相等。 例如,这可以用于从输入流中筛选出重复项。 请记住,如果您覆盖equals()方法,则还应该始终覆盖hashCode()方法!
平等
现在,如果您选择平等而不是身份,那么您将需要考虑一些事项。 您必须问自己的第一件事是:具有相同属性的此类的两个实例是否一定相同? 对于上述人员,我会拒绝。 有一天,您的系统中很有可能会有两个姓氏和名字相同的人。 即使您继续添加更多的个人信息,例如生日或喜欢的颜色,您迟早也会发生冲突。 另一方面,如果您的系统正在处理汽车,并且每辆汽车都包含对“模型”的引用,则可以安全地假设,如果两辆汽车都是黑色Tesla模型S,则即使对象是相同的,它们也可能是同一模型。存储在内存中的不同位置。 这是平等可能很好的一个例子。
import java.util.Objects;
import static java.util.Objects.requireNonNull;public final class Car {public static final class Model {private final String name;private final String version;public Model(String name, String version) {this.name = requireNonNull(name);this.version = requireNonNull(version);}@Overridepublic int hashCode() {int hash = 5;hash = 23 * hash + Objects.hashCode(this.name);hash = 23 * hash + Objects.hashCode(this.version);return hash;}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null) return false;if (getClass() != obj.getClass()) return false;final Model other = (Model) obj;if (!Objects.equals(this.name, other.name)) {return false;} else return Objects.equals(this.version, other.version);}}private final String color;private final Model model;public Car(String color, Model model) {this.color = requireNonNull(color);this.model = requireNonNull(model);}public Model getModel() {return model;}
}
如果两辆汽车的内存地址相同,则它们被认为是相同的。 另一方面,只要它们具有相同的名称和版本,则认为它们的模型相同。 这是一个例子:
final Car a = new Car("black", new Car.Model("Tesla", "Model S"));
final Car b = new Car("black", new Car.Model("Tesla", "Model S"));System.out.println("Is a and b the same car? " + a.equals(b));
System.out.println("Is a and b the same model? " + a.getModel().equals(b.getModel()));// Prints the following:
// Is a and b the same car? false
// Is a and b the same model? true
身分识别
选择平等而不是身份的一种风险是,它可能会导致分配比堆上更多的对象。 只需看上面的汽车示例。 对于我们创建的每辆汽车,我们还为模型分配内存中的空间。 即使Java通常优化了字符串分配以防止重复,对于始终相同的对象仍然是一定的浪费。 将内部对象转换为可以使用身份比较方法进行比较并且同时避免不必要的对象分配的一个简单技巧是将其替换为枚举:
public final class Car {public enum Model {TESLA_MODEL_S ("Tesla", "Model S"),VOLVO_V70 ("Volvo", "V70");private final String name;private final String version;Model(String name, String version) {this.name = name;this.version = version;}}private final String color;private final Model model;public Car(String color, Model model) {this.color = requireNonNull(color);this.model = requireNonNull(model);}public Model getModel() {return model;}
}
现在我们可以确定每种模型只会在内存中的某个位置存在,因此可以使用身份比较安全地进行比较。 然而,与此有关的一个问题是,这实际上限制了我们的可扩展性。 在此之前,可以在不修改Car.java文件中的源代码的情况下即时定义新模型的方法,但是现在我们已经将自己锁定在一个枚举中,该枚举通常应该保持不变。 如果需要这些属性,那么对等比较可能更适合您。
最后一点,如果您重写了类的equals()和hashCode()方法,后来又想根据身份将其存储在Map中,则始终可以使用IdentityHashMap结构。 即使equals()和hashCode()方法已被覆盖,它也将使用内存地址来引用其键。
翻译自: https://www.javacodegeeks.com/2016/03/equality-vs-identity.html