List.contains() returns 对于违反 hashCode 约定的对象为真
List.contains() returns true for object that violated hashCode contract
拥有这些实体:
Review.java:
@Entity
@Setter
@Getter
public class Review {
@Id @GeneratedValue
private Long id;
private String comment;
@ManyToOne
@JoinColumn(name = "fk_book")
private Book book;
public Review() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Review review = (Review) o;
return Objects.equals(id, review.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Book.java:
@Entity
@Getter
@Setter
public class Book {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "book")
private List<Review> reviews = new ArrayList<>();
public Book() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(id, book.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
最后 Main.java:
public class Main {
private static EntityManagerFactory getEmf(){
return Persistence.createEntityManagerFactory("h0");
}
public static void main(String[] args) {
EntityManager em = getEmf().createEntityManager();
em.getTransaction().begin();
Book b = em.find(Book.class, 1);
Review r = new Review();
r.setComment("This is a comment");
r.setBook(b);
//Object STATE 1
//before the `r` is added, its r.getId() == `null`
b.getReviews().add(r);
//Object STATE 2
//contract violation: now its r.getId() == `1`
em.persist(r);
em.getTransaction().commit();
em.close();
em = getEmf().createEntityManager();
em.getTransaction().begin();
b = em.find(Book.class, 1);
List<Review> reviews = b.getReviews();
System.out.println(reviews.get(0).getBook().getTitle());
//Object with STATE 2
//yet the contains() method returns true even with changed id
System.out.println(reviews.contains(r));
em.getTransaction().commit();
em.close();
}
}
输出:
Some Book
true
class List
returns 的 contains()
方法怎么可能在其对象的状态(评论 r
,即在用于 reading/writing 的 hashCode 计算中使用其 id
到该集合中)是否已更改?从而违反了hashCode合约?
根据 List.contains
的 Javadoc:
returns true if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e))
.
所以,hashCode
和equals
不一致也没关系。
诚然,Set.contains
在其文档中有相同的内容,hashCode
的实施始终如一。
两者的实现方式不同:HashSet
使用 hashCode
计算元素的“查找位置”,因此可能找不到不一致的元素哈希码;像 ArrayList
这样的 List
只会线性探测所有元素来找到它,而不必先检查哈希码。
拥有这些实体:
Review.java:
@Entity
@Setter
@Getter
public class Review {
@Id @GeneratedValue
private Long id;
private String comment;
@ManyToOne
@JoinColumn(name = "fk_book")
private Book book;
public Review() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Review review = (Review) o;
return Objects.equals(id, review.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Book.java:
@Entity
@Getter
@Setter
public class Book {
@Id @GeneratedValue
private Long id;
private String title;
@OneToMany(mappedBy = "book")
private List<Review> reviews = new ArrayList<>();
public Book() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(id, book.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
最后 Main.java:
public class Main {
private static EntityManagerFactory getEmf(){
return Persistence.createEntityManagerFactory("h0");
}
public static void main(String[] args) {
EntityManager em = getEmf().createEntityManager();
em.getTransaction().begin();
Book b = em.find(Book.class, 1);
Review r = new Review();
r.setComment("This is a comment");
r.setBook(b);
//Object STATE 1
//before the `r` is added, its r.getId() == `null`
b.getReviews().add(r);
//Object STATE 2
//contract violation: now its r.getId() == `1`
em.persist(r);
em.getTransaction().commit();
em.close();
em = getEmf().createEntityManager();
em.getTransaction().begin();
b = em.find(Book.class, 1);
List<Review> reviews = b.getReviews();
System.out.println(reviews.get(0).getBook().getTitle());
//Object with STATE 2
//yet the contains() method returns true even with changed id
System.out.println(reviews.contains(r));
em.getTransaction().commit();
em.close();
}
}
输出:
Some Book
true
class List
returns 的 contains()
方法怎么可能在其对象的状态(评论 r
,即在用于 reading/writing 的 hashCode 计算中使用其 id
到该集合中)是否已更改?从而违反了hashCode合约?
根据 List.contains
的 Javadoc:
returns true if and only if this list contains at least one element e such that
(o==null ? e==null : o.equals(e))
.
所以,hashCode
和equals
不一致也没关系。
诚然,Set.contains
在其文档中有相同的内容,hashCode
的实施始终如一。
两者的实现方式不同:HashSet
使用 hashCode
计算元素的“查找位置”,因此可能找不到不一致的元素哈希码;像 ArrayList
这样的 List
只会线性探测所有元素来找到它,而不必先检查哈希码。