背景
final Set<AbstractMetadataCard> datas = new HashSet<>(hits.length);
Arrays.asList(hits).forEach(hit -> {
final AllFiledMetadata data = JSONObject.parseObject(hit.getSourceAsString(), AllFiledMetadata.class);
final AbstractMetadataCard card = data.parse(search);
if (card == null) {
return;
}
datas.add(card);
});
在拿到一批数据的时候需要进行解析+去重+返回接口数据。以下代码中AbstractMetadataCard类是父类,派生了10个子类,每个解析的逻辑在子类中分别实现,最终子类解析出的返回值都是父类类型,由于去重逻辑相同,所以hashCode和equals方法我写在了父类中。
特殊的点是我的子类中没有一个属性,只有parse的业务逻辑,属性都定义在父类中。下面列举其中一个子类和父类的代码:
父类:
@Data
public abstract class AbstractMetadataCard implements Serializable {
private static final long serialVersionUID = 1717719806335115651L;
/**
* 类型
*/
private Integer type;
/**
* 主信息
*/
private String masterName;
/**
* 附属信息
*/
private String slaveName;
public abstract AbstractMetadataCard parse(final AllFiledMetadata metadata, String search);
@Override
public boolean equals(final Object data) {
if (this == data) {
return true;
}
if (!(data instanceof AbstractMetadataCard)) {
return false;
}
final AbstractMetadataCard target = (AbstractMetadataCard) data;
if (Objects.equals(target.getMasterName(), this.getMasterName())
&& Objects.equals(target.getSlaveName(), this.getSlaveName()) &&
Objects.equals(target.getType(), this.getType())) {
return true;
}
return false;
}
@Override
public int hashCode() {
if (masterName != null && slaveName != null && type != null) {
return masterName.hashCode() + slaveName.hashCode() + type.hashCode();
}
return masterName.hashCode() + type.hashCode();
}
子类:
@Data
public class ApplicationCard extends AbstractMetadataCard {
private static final long serialVersionUID = 5449872075497237059L;
@Override
public ApplicationCard parse(final AllFiledMetadata metadata, final String search) {
if (!Objects.equals(metadata.getMwtype(), APPLICATION.getValue())) {
return null;
}
final ApplicationCard applicationMetadata = new ApplicationCard();
applicationMetadata.setSlaveName(metadata.getDevMaster());
applicationMetadata.setMasterName(metadata.getApplication());
applicationMetadata.setType(APPLICATION.getValue());
return applicationMetadata;
}}
现象
我查询出来的数据有很多条,但是最终接口返回都是1条数据,很明显是被错误去重了,检查了下父类的equals和hashCode方法都没有问题,思考了半天不知道什么原因,debug父类的equals和hashCode方法都没有被调用,甚至还跟了下hashMap的源码,也没解决。
结论
最终我编译了一下子类然后再反编译出来查看了下代码,我发现子类里面怎么多了4个方法,equals、hashCode、toString、canEqual
public class ApplicationCard extends AbstractMetadataCard {
private static final long serialVersionUID = 5449872075497237059L;
public ApplicationCard parse(final AllFiledMetadata metadata, final String search) {
if (!Objects.equals(metadata.getMwtype(), MetadataSourceTypeEnum.APPLICATION.getValue())) {
return null;
} else {
ApplicationCard applicationMetadata = new ApplicationCard();
applicationMetadata.setSlaveName(metadata.getDevMaster());
applicationMetadata.setMasterName(metadata.getApplication());
applicationMetadata.setType(MetadataSourceTypeEnum.APPLICATION.getValue());
return applicationMetadata;
}
}
public ApplicationCard() {
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof ApplicationCard)) {
return false;
} else {
ApplicationCard other = (ApplicationCard)o;
return other.canEqual(this);
}
}
protected boolean canEqual(final Object other) {
return other instanceof ApplicationCard;
}
public int hashCode() {
int result = true;
return 1;
}
public String toString() {
return "ApplicationCard()";
}
}
原来,lombok的@Data注解会帮你自动生成这几个方法,但是大家可以看下我生成的这个hashCode方法和equals和canEqual方法,hashmap在进行key去重的时候,会先比较对象中的hashCode方法,很明显,这里永远返回true,如果hashCode返回的值相等,那就说明hash冲突了,冲突以后会去比较equals方法,如果相等则替换,如果不等,则以链表或者红黑树的形式存在,很明显,这里equals方法判断都是这个子类对象的时候,就是相等的。
那为什么我父类重写的hashCode方法没有被调用呢?因为子类lombok已经帮我写了,优先调用子类本身的方法。
总结
出现此问题的场景有一个比较特殊的场景:我的子类没有任何属性,所以lombok写了一个相当于永远返回true的几个方法。而且其实idea在编译的时候会做提示,因为注解在类上面,所以我没有注意到。
公众号
微信搜索“PPShare”,关注公众号。