java - 删除映射条目会导致映射条目中的对象引用可选更改
问题描述
当我从地图中检索地图条目时,将其存储在可选中,然后使用 remove(entry.getKey()) 从地图中删除相同的条目,然后Optional
突然开始指向地图中可用的下一个地图条目。
让我进一步解释一下:
我有一堆想要排序的评论对象。评论列表应始终以被接受为答案的评论开头,它应该是列表中的第一个元素。sort 方法以 map 开始,并使用 entrySet 上的流来检索acceptedAnswer
布尔值设置为的第一个评论true
。
Map<Long, CommentDTO> sortedAndLinkedCommentDTOMap = sortCommentsAndLinkCommentRepliesWithOwningComments(commentDTOSet);
Optional<Map.Entry<Long, CommentDTO>> acceptedAnswerCommentOptional = sortedAndLinkedCommentDTOMap.entrySet().stream()
.filter(entry -> entry.getValue().isAcceptedAsAnswer()).findFirst();
让我们假设Map
包含 3 条带有 ids 的评论3, 6, and 11
。关键始终是评论的 id,评论始终是价值。标记为答案的评论有 id 6
。在这种情况下,将执行以下代码:
if(acceptedAnswerCommentOptional.isPresent()){
Map.Entry<Long, CommentDTO> commentDTOEntry = acceptedAnswerCommentOptional.get();
sortedAndLinkedCommentDTOMap.remove(commentDTOEntry.getKey());
}
当使用它commentDTOEntry
的值进行初始化时,acceptedAnswerCommentOptional
它具有对 id 6 的已接受答案的引用。现在,当我从sortedAndLinkedCommentDTOMap
对已接受答案的引用中删除该条目时,评论不仅会sortedAndLinkedCommentDTOMap
从acceptedAnswerCommentOptional
! acceptedAnswerCommentOptional
但是现在开始指向下一个条目,sortedAndLinkedCommentDTOMap
即带有的条目,而不是变为 null key 11
。
我不明白是什么导致了这种奇怪的行为。acceptedAnswerCommentOptional
为什么简单成为的价值不null
?当我从地图中删除已接受的答案评论时,为什么无法acceptedAnswerCommentOptional
保持对已接受答案评论的引用?
您可以在使用调试模式在 intellij IDEA 中运行代码时自己看到此行为,只要该remove
方法被称为 commentDTOEntry 旁边的解释性调试标签acceptedAnswerCommentOptional
从翻转6 -> ....
到11 -> ....
编辑:我根据 WJS 的意愿做了一个可重现的例子。这是代码:
import java.util.*;
import java.math.BigInteger;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.function.Function;
class CommentDTO implements Comparable<CommentDTO> {
private BigInteger id;
private BigInteger owningCommentId;
private BigInteger commenterId;
private Long owningEntityId;
private String commenterName;
private String commenterRole;
private String country;
private String thumbnailImageUrl;
private String content;
private String commentDateVerbalized;
private boolean flagged;
private Integer flagCount;
private boolean deleted;
private boolean liked;
private Integer likeCount;
private String lastEditedOnVerbalized;
private boolean acceptedAsAnswer;
private boolean rightToLeft;
private TreeSet<CommentDTO> replies = new TreeSet<>();
public CommentDTO() {
}
public CommentDTO(boolean acceptedAsAnswer, BigInteger id){
this.acceptedAsAnswer = acceptedAsAnswer;
this.id = id;
}
public CommentDTO(boolean acceptedAsAnswer, BigInteger id, BigInteger owningCommentId){
this.acceptedAsAnswer = acceptedAsAnswer;
this.id = id;
this.owningCommentId = owningCommentId;
}
public BigInteger getId() {
return id;
}
public void setId(BigInteger id) {
this.id = id;
}
public BigInteger getOwningCommentId() {
return owningCommentId;
}
public void setOwningCommentId(BigInteger owningCommentId) {
this.owningCommentId = owningCommentId;
}
public BigInteger getCommenterId() {
return commenterId;
}
public void setCommenterId(BigInteger commenterId) {
this.commenterId = commenterId;
}
public Long getOwningEntityId() {
return owningEntityId;
}
public void setOwningEntityId(Long owningEntityId) {
this.owningEntityId = owningEntityId;
}
public String getCommenterName() {
return commenterName;
}
public void setCommenterName(String commenterName) {
this.commenterName = commenterName;
}
public String getCommenterRole() {
return commenterRole;
}
public void setCommenterRole(String commenterRole) {
this.commenterRole = commenterRole;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getCommentDateVerbalized() {
return commentDateVerbalized;
}
public void setCommentDateVerbalized(String commentDateVerbalized) {
this.commentDateVerbalized = commentDateVerbalized;
}
public boolean isFlagged() {
return flagged;
}
public void setFlagged(boolean flagged) {
this.flagged = flagged;
}
public Integer getFlagCount() {
return flagCount;
}
public void setFlagCount(Integer flagCount) {
this.flagCount = flagCount;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public boolean isLiked() {
return liked;
}
public void setLiked(boolean liked) {
this.liked = liked;
}
public Integer getLikeCount() {
return likeCount;
}
public void setLikeCount(Integer likeCount) {
this.likeCount = likeCount;
}
public TreeSet<CommentDTO> getReplies() {
return replies;
}
public void setReplies(TreeSet<CommentDTO> replies) {
this.replies = replies;
}
public String getLastEditedOnVerbalized() {
return lastEditedOnVerbalized;
}
public void setLastEditedOnVerbalized(String lastEditedOnVerbalized) {
this.lastEditedOnVerbalized = lastEditedOnVerbalized;
}
public String getThumbnailImageUrl() {
return thumbnailImageUrl;
}
public void setThumbnailImageUrl(String thumbnailImageUrl) {
this.thumbnailImageUrl = thumbnailImageUrl;
}
public boolean isAcceptedAsAnswer() {
return acceptedAsAnswer;
}
public void setAcceptedAsAnswer(boolean acceptedAsAnswer) {
this.acceptedAsAnswer = acceptedAsAnswer;
}
public boolean isRightToLeft() {
return rightToLeft;
}
public void setRightToLeft(boolean rightToLeft) {
this.rightToLeft = rightToLeft;
}
@Override
public int compareTo(CommentDTO o) {
return this.id.compareTo(o.id);
}
@Override
public String toString() {
return "CommentDTO{" +
"id=" + id +
", owningCommentId=" + owningCommentId +
", commenterId=" + commenterId +
", owningEntityId=" + owningEntityId +
", commenterName='" + commenterName + '\'' +
", commenterRole='" + commenterRole + '\'' +
", country='" + country + '\'' +
", thumbnailImageUrl='" + thumbnailImageUrl + '\'' +
", content='" + content + '\'' +
", commentDateVerbalized='" + commentDateVerbalized + '\'' +
", flagged=" + flagged +
", flagCount=" + flagCount +
", deleted=" + deleted +
", liked=" + liked +
", likeCount=" + likeCount +
", lastEditedOnVerbalized='" + lastEditedOnVerbalized + '\'' +
", acceptedAsAnswer=" + acceptedAsAnswer +
", rightToLeft=" + rightToLeft +
", replies=" + replies +
'}';
}
}
public class HelloWorld implements Comparable<HelloWorld> {
private Long id;
private boolean acceptedAsAnswer;
public HelloWorld(){}
public HelloWorld(boolean acceptedAsAnswer, Long id){
this.acceptedAsAnswer = acceptedAsAnswer;
this.id = id;
}
@Override
public String toString() {
return "id= " + id + " acceptedAsAnswer= " + acceptedAsAnswer;
}
public boolean isAcceptedAsAnswer(){
return acceptedAsAnswer;
}
public long getId(){
return id;
}
public static void main(String []args){
HelloWorld helloWorld = new HelloWorld();
helloWorld.doTest();
}
@Override
public int compareTo(HelloWorld o) {
return this.id.compareTo(o.id);
}
public void doTest(){
Set<CommentDTO> commentDTOSet = new HashSet<>();
commentDTOSet.add( new CommentDTO(false, BigInteger.valueOf(3)));
commentDTOSet.add( new CommentDTO(true, BigInteger.valueOf(6)));
commentDTOSet.add( new CommentDTO(false, BigInteger.valueOf(11)));
commentDTOSet.add( new CommentDTO(true, BigInteger.valueOf(7), BigInteger.valueOf(6)));
commentDTOSet.add( new CommentDTO(true, BigInteger.valueOf(8), BigInteger.valueOf(6)));
Map<Long, CommentDTO> sortedAndLinkedCommentDTOMap = sortCommentsAndLinkCommentRepliesWithOwningComments(commentDTOSet);
Optional<Map.Entry<Long, CommentDTO>> acceptedAnswerCommentOptional = sortedAndLinkedCommentDTOMap.entrySet().stream()
.filter(entry -> entry.getValue().isAcceptedAsAnswer()).findFirst();
if(acceptedAnswerCommentOptional.isPresent()){
Map.Entry<Long, CommentDTO> commentDTOEntry = acceptedAnswerCommentOptional.get();
System.out.println(commentDTOEntry.toString());
sortedAndLinkedCommentDTOMap.remove(commentDTOEntry.getKey());
System.out.println(commentDTOEntry.toString());
}
}
private Map<Long, CommentDTO> sortCommentsAndLinkCommentRepliesWithOwningComments(Set<CommentDTO> commentDTOSet){
Map<Long, CommentDTO> commentDTOMap = commentDTOSet.stream()
.collect(Collectors.toMap(comment -> comment.getId().longValueExact(), Function.identity(), (v1,v2) -> v1, TreeMap::new));
commentDTOSet.forEach(commentDTO -> {
BigInteger owningCommentId = commentDTO.getOwningCommentId();
if(owningCommentId != null){
CommentDTO owningCommentDTO = commentDTOMap.get(owningCommentId.longValueExact());
owningCommentDTO.getReplies().add(commentDTO);
}
});
commentDTOMap.values().removeIf(commentDTO -> commentDTO.getOwningCommentId() != null);
return commentDTOMap;
}
}
你可以在这里运行上面的代码:https ://www.tutorialspoint.com/compile_java_online.php
编辑 2:示例代码现在重现了我的问题。
编辑 3:这行代码
commentDTOMap.values().removeIf(commentDTO -> commentDTO.getOwningCommentId() != null);
导致观察到的行为。接受的答案(id 为 6 的commentDTO)有 2 个回复。这 2 条评论(id 为 7 和 8)由 CommentDTO 6“拥有”,并且也被 CommentDTO 6 中的replies
列表引用。最后,sortCommentsAndLinkCommentRepliesWithOwningComments()
我删除了所有CommentDTOs
可以被视为对另一条评论的回复的内容owningCommentId != null
。我这样做是因为这些评论现在是从replies
拥有评论列表中引用的。如果我将它们留在原始地图中,那么这些回复将出现两次。因此我删除了它们,但这会导致意外行为。我想知道为什么会这样。
解决方案
发生这种情况是因为您使用的地图是TreeMap
.
ATreeMap
被实现为一个红黑树,它是一个自平衡二叉树。
映射的条目用作树的节点。
如果您删除一个条目,则树必须重新平衡自身,并且可能会使用该条目指向取代它的节点。
由于TreeMap.entrySet()
有地图支持,因此更改会反映在集合中。
更改还取决于您要删除的节点,例如,如果它是叶子,那么它可能只是从树中取消链接并且条目不受影响。
如果您使用像 an 这样的另一个地图实现,HashMap
那么您将不会得到这种行为。
顺便说一下,这是一个更简单的示例,它甚至不涉及Optional
或自定义类:
Map<Long, String> map = new TreeMap<>();
map.put(1L, "a");
map.put(2L, "b");
map.put(3L, "c");
map.put(4L, "d");
map.put(5L, "e");
map.put(6L, "f");
Map.Entry<Long, String> entry = map.entrySet().stream()
.filter(e -> e.getKey().equals(4L))
.findFirst()
.get();
System.out.println(entry); // prints 4=d
map.remove(entry.getKey());
System.out.println(entry); // prints 5=e
推荐阅读
- java - 泽西还没有准备好
- c# - 通过 resharper 进入 nuget-package 代码 c#
- javascript - 使用 Javascript 删除添加的列表项
- c# - C#我遍历一个任务队列并获得空值,为什么会这样?
- ionic-framework - 如何检查 ionic 3 模板中的文件扩展名
- android-studio - 如果 Android Studio 在控制面板中不可见并且卸载程序不在文件位置,如何卸载它
- ruby-on-rails - nil:NilClass 的未定义方法“bytesize”
- c# - 剃须刀组件的源生成器不工作
- html - Bootstrap 4.5.3 工具提示放置“自动左”不起作用
- reactjs - map 不是 reactJS 中的函数