【发布时间】:2021-07-17 10:25:28
【问题描述】:
Tomcat 9.0.39 在 JDK 11.0.10 上运行。
或在 JDK 1.8.0_271 上运行 Tomcat 8.5.65。
以下代码使用 JDK 1.8.0_271 编译,运行良好。
final Set<A> a = _a.stream().collect(Collectors.toSet());
a.removeIf(b ->
Objects.nonNull(b.getOne())
&& Objects.nonNull(b.getTwo())
&& Objects.nonNull(b.getThree())
&& Objects.nonNull(b.getFour())
);
但是,removeIf 部分未使用以下配置执行(下一个操作已在 _a 列表的所有元素上执行):
- Tomcat 8.5.59 在 JDK 1.8.0_202-b08 上运行。
- Tomcat 8.5.34 在 JDK 1.8.0_191 上运行。
为了在任何地方达到预期的结果,removeIf 已被替换为过滤器:
final Set<A> a = _a.stream()
.filter(b ->
Objects.isNull(b.getOne())
&& Objects.isNull(b.getTwo())
&& Objects.isNull(b.getThree())
&& Objects.isNull(b.getFour())
)
.collect(Collectors.toSet());
或与供应商合作:
final Set<A> a = _a.stream()
.collect(Collectors.toCollection(TreeSet::new));
a.removeIf(b ->
Objects.nonNull(b.getOne())
&& Objects.nonNull(b.getTwo())
&& Objects.nonNull(b.getThree())
&& Objects.nonNull(b.getFour())
);
我读过Collectors#toSet 或Collectors#toList :
返回一个收集器,它将输入元素累积到一个新的集合/列表中。不保证返回的 Set / List 的类型、可变性、可序列化性或线程安全性;如果需要对返回的 Set / List 进行更多控制,请使用 toCollection(Supplier)。
但是这里没有并行流,流是通过终端操作在最终集合中收集的,Collection#removeIf 是在 JDK 1.8 中引入的。
所以我的问题是:
为什么它在不同的 JDK 次要版本上表现不同?
(或者至少它在 JDK
问/答
如果替换 _a.stream().collect(Collectors.toSet()); 会发生什么使用新的 HashSet(_a)?
同样的事情,在工作配置上工作,而不是在另一个工作上,打印不应该留在集合中的元素。
final Set<A> a = new HashSet<>(_a);
这种行为是“随机的”吗(如标题所示),这意味着这种情况有时会发生,但并非总是如此;或者只是出乎意料,这意味着这总是在特定的 JDK 版本上发生,但您不确定为什么?
这总是在特定的 JDK 版本上发生,但我不知道为什么。在 1.8.0_271 之前,removeIf 永远不会被执行(多次尝试确定),在此版本之后或使用此版本时,它总是过滤元素(也尝试多次)。
A 是否正确实现了 equals 和 hashCode?strong>
A 类正在像这样实现java.lang.Comparable<A>:
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
boolean result = false;
if (o instanceof A) {
A a = (A o;
result =
getId1() == a.getId1()
&& getId2() == a.getId2()
&& getId3() == a.getId3();
}
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
StringBuilder sb = new StringBuilder();
sb.append(getId1());
sb.append(getId2());
sb.append(getId3());
return sb.hashCode();
}
/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(A a) {
return
getId1() == a.getId1()
&& getId2() == a.getId2()
&& getId3() == a.getId3()
? 1 : 0;
}
如何判断代码没有被执行?
我使用的第一种方法是生产数据库中的表,它更新了所有元素,而它应该只更新缩减集的元素。这向我显示了错误。
然后,我曾经确定的第二种方法是像这样的简单printf,在注释更新表的数据库方法之后。
a.stream()
.filter(b ->
Objects.nonNull(b.getOne())
|| Objects.nonNull(b.getTwo())
|| Objects.nonNull(b.getThree())
|| Objects.nonNull(b.getFour())
)
.forEach(a ->
System.out.printf(
"%nbad elements remaining in the set : %s%n",
a.getId1()
)
);
并且打印的结果在它不起作用的配置(生产)上是不同的:有时更新了 300 个项目,有时更新了 400 个。 在工作配置(开发)上,没有打印任何内容。这指出了它在 JDK 上的工作方式不同。
只需使用 Objects.hash(getId1(), getId2(), getId3()); 没有理由使用昂贵的字符串连接。但是您的 compareTo 方法也被破坏了。比较方法必须是对称的(你的不是),传递的(你的不是),为所有对象创建一个总顺序(你的甚至没有尝试)。你确定这个中间步骤是否做了它应该做的事情的方式也是不够的。在应用过滤器之前,您甚至没有验证该集合是否具有预期的内容。
我已经替换了java.lang.Comparable<A>#compareTo 和java.lang.Object#hashCode 的以下实现。
hashCode
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
/*@Override
public int hashCode() {
StringBuilder sb = new StringBuilder();
sb.append(getId1());
sb.append(getId2());
sb.append(getId3());
return sb.hashCode();
}*/
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(getId1(), getId2(), getId3());
}
compareTo
/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
/*@Override
public int compareTo(A a) {
return
getId1() == a.getId1()
&& getId2() == a.getId2()
&& getId3() == a.getId3()
? 1 : 0;
}*/
/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(A a) {
int result;
if (a.getId2() != getId2()) {
throw new ClassCastException(
String.format(
"a A with id2 euqals to %s cannot be compared with "
+ "another A with id2 equals to %s",
getId2(),
a.getId2()
)
);
}
if (
getId1() == a.getId1()
&& getId2() == a.getId2()
&& getId3() == a.getId3()
) {
result = 0;
}
else if (getId1() < a.getId1()) {
result = 1;
}
else {
result = -1;
}
return result;
}
我可以确认它现在在每个 JDK(1.8.0_191、1.8.0_202、1.8.0_271、11.0.10)和每个版本的 Tomcat(8.5.34、8.5.59、8.5.65、 9.0.39)。
虽然这并不能解释为什么它在 JDK 1.8.0_271 及更高版本上工作(如果我可以这么说的话),但这种奇怪行为的答案是不好的java.lang.Object#hashCode 和 java.lang.Comparable#compareTo 方法的实现。
【问题讨论】:
-
A是否正确实现了equals和hashCode?如果不这样做阻止HashSet能够迭代其所有元素(或删除元素),我会感到惊讶;但这是HashSet和TreeSet之间最明显的区别。 -
另外:这种行为是“随机的”(如标题所示),这意味着这种情况有时会发生,但并非总是如此;或者只是出乎意料,这意味着这种情况总是发生在特定的 JDK 版本上,但您只是不确定为什么?
-
Collectors.toSet()不保证返回的集合是可变的。但是由于在这些实现中它总是HashSet,所以实际的问题是:“你如何确定代码没有被执行?”。而您的filter谓词不是对removeIf谓词的否定。 -
貌似A::hashCode依赖于StringBuilder::hashCode,其实就是继承自Object::hashCode的身份哈希码。也就是说,SB 的 hashCode 是基于对象身份的,而不是基于其内容的。由于 A::hashCode 每次调用时都会分配一个新的 SB,因此每次它可能会返回不同的值。身份哈希码可能会因运行和 JDK 版本而异,这可能会导致 HashSet 出现一堆奇怪的行为。
-
只需使用
Objects.hash(getId1(), getId2(), getId3());没有理由使用昂贵的字符串连接。但是你的compareTo方法也坏了。比较方法必须是对称的(你的不是),传递的(你的不是),为所有对象创建一个总顺序(你的甚至没有尝试)。你确定这个中间步骤是否做了它应该做的事情的方式也是不够的。在应用文件管理器之前,您甚至没有验证集合是否具有预期的内容。
标签: java collections java-8