最近在用到removeAll时遇到的疑问。
分析问题
实体类
@Data
public class EntityVo extends Entity {
.....
}
通过Collection
集合去实现删除包含的元素
collection.removeAll(arr);
代码一执行,全部都被删了
原因解析: 先看API
boolean removeAll(Collection<?> c)
从列表中移除指定 collection 中包含的其所有元素(可选操作)。
没错,就是移除子集合包含的元素 再看API说明,移除所包含的其所有元素,注意这个字眼:包含! 因为在执行removeAll方法时,会先对集合元素进行比较,如果元素相等才执行移除操作,说到这,相信很多人都已经明白是怎么回事了,因为相等(equals),所以执行移除。
查看源码进一步证实上述猜测,remove和removeAll的方法实现在:
java.util.AbstractCollection<E>
具体代码为:
public boolean removeAll(Collection<?> c) {
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
得出问题原因
可以看到在调用removeAll方法时,实际上是循环调用了remove方法,而remove方法中有一段关键的代码:if (o.equals(it.next())) !
So,得出结论,因为上述例子实体类中加入了lombok @Data
!而在执行removeAll
方法时是通过equals
方法来判断集合元素是否相等的,EqualsAndHashCode 不会调用父类的 equals
方法,所以会出现上述问题!
@Data
相当于 @Getter
@Setter
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
这5个注解的合集。 通过官方文档,可以得知,当使用@Data注解时,则有了@EqualsAndHashCode注解,那么就会在此类中存在equals(Object other)
和 hashCode()
方法,且不会使用父类的属性,这就导致了可能的问题。
比如,有多个类有相同的部分属性,把它们定义到父类中,恰好id(数据库主键)也在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other)
和 hashCode()
方法判定为相等,从而导致出错。
解决办法
修复此问题的方法很简单:
- 使用
@Getter
@Setter
@ToString
代替@Data
并且自定义equals(Object other)
和hashCode()
方法,比如有些类只需要判断主键id是否相等即足矣。 - 或者使用在使用
@Data
时同时加上@EqualsAndHashCode(callSuper=true)
注解。
@Data
+ @EqualsAndHashCode(callSuper=true)
public class Entity {
.....
}