我正在使用Java 8(版本1.8.0_25),Netbeans 8.0.2,并将一些Java 8功能集成到现有应用程序中。排序和.forEach不起作用,所以我创建了一些测试代码,以确保我理解lambdas等,并诊断问题。下面是新代码和代码的混合,以便与我的系统中的数据进行交互:
public void test(Registration reg) {
/* new code */
List<String> family = new ArrayList<>();
family.add("Mom");
family.add("Dad");
family.add("Brother");
family.add("Sister");
family.forEach(p -> System.out.println(p));
Collections.sort(family, (p1,p2) -> {
System.out.println(p1 + " <==> "+ p2);
return p1.compareToIgnoreCase(p2);
});
family.forEach(p -> System.out.println(p));
/* code to test with my system data */
List<RegistrationItem> item = new ArrayList<>();
List<RegistrationItem> regI = reg.getRegistrationItem();
regI.forEach(p -> {
System.out.println(p.toString());
item.add(p);
});
Collections.sort(regI, (r1,r2) -> {
System.out.println(r1.toString() + r2.toString());
return r1.getId().compareTo(r2.getId());
});
for (RegistrationItem r : regI) {
item.add(r);
}
}
注册是反映事件数据的POJO,其中包括RegisterItem列表,这是另一个POJO的详细信息。在此测试中,列表大小为4。
标有新代码的部分完美无缺。它打印出列表,在排序时打印,然后打印排序列表。我也可以在排序块中设置断点,这是我所期望的。
使用现有代码是另一回事。 .forEach和.sort不起作用,我无法在java 8块中设置断点。调试器步骤到代码,但它似乎不执行。当我进入for循环时,“item”的大小仍为0.看上去就是为了证明数据可以移动,按预期工作,结果大小为4.
任何帮助都将不胜感激。
在示例中,我已验证reg是否正确传递给测试方法。它的结构正确,regI的大小为4,具有正确结构化的对象。
我认为我的环境或代码有问题,但我自己无法识别它。
使用Holger和Stuart Marks建议的方法进行测试后,很明显这与IndirectList和覆盖相关的问题相同。我的JRE和JDK都是Java 8,我已升级到EclipseLink 2.5.2。我已经证明了问题是100%的时间用比较器,Collections.sort与lambdas和.forEach发生。这似乎是一个非常普遍的问题,我很惊讶另外一个问题并没有吸引更多的关注,而不是除了我的一个上升。
答案 0 :(得分:6)
此问题的根本原因是在EclipseLink JPA的IndirectList
类中使用了有缺陷的实现模式。 (doc,source)此问题发生在2.5版本系列中;它也可能出现在其他版本中。
问题是这个类子类 Vector
和都有一个对Vector
实例的引用。它试图通过覆盖Vector
的所有方法将所有方法调用委托给该实例。只要没有向Vector
添加新方法,这种方法就可以正常工作。
这发生在Java 8中。
Java 8向Collection
,Iterable
和List
接口添加了几个新的默认方法,包括:
forEach
parallelStream
removeIf
replaceAll
sort
spliterator
stream
通常,添加默认方法是安全的,因为它们必须根据其他现有方法实现。但是,出于效率的原因,实现类以覆盖默认方法通常是个好主意。 Java 8 Vector
实现添加了这些默认方法的几个覆盖。如果你有一个实际的Vector
类的实例,这些工作正常。 IndirectList
类不会覆盖这些方法,因此它尝试设置的委派路径对这些方法不起作用。相反,使用普通的Vector
实现。遗憾的是,IndirectList
方法不会使超类状态保持最新,因此方法的Vector
实现的行为就像Vector
为空。
Vector
会覆盖forEach
,removeIf
,replaceAll
,sort
和spliterator
。 parallelStream
和stream
默认方法是根据spliterator
实现的,因此它们也会发生同样的事情。基本上,如果在EclipseLink JPA检索到的IndirectList
实现上使用集合上的新默认方法,则它们都不会起作用。
请注意,Collections.sort(indirectList)
也会出现此问题。此方法只调用indirectList.sort()
方法,因此遇到与上述完全相同的问题。
this answer中列出了此问题的一些可能的解决方法。
有关EclipseLink状态的更多信息,请参阅EclipseLink JPA错误433075和446236。
有关此实施模式的缺陷的更多信息,请参阅Joshua Bloch的书 Effective Java,Second Edition ,第16项:赞成合成而不是继承。
答案 1 :(得分:1)
尝试更改处理列表的方式。如果您想获取列表中的项目,对它们进行排序,并将它们分配给新列表,那么请尝试以下操作:
List<RegistrationItem> sortedItems = reg.getRegistrationItem().stream()
.sorted((a, b)-> a.getId().compareTo(b.getId()))
.peek(System.out::println)
.collect(Collectors.toList());
使用forEach()并添加,然后排序效率非常低。使用流API允许您以更加并行友好的方式完成所有这些操作,代码更少。
此代码中唯一可能出错的是,如果reg.getRegistrationItem()
{List<RegistrationItem>
RegistrationItem
没有返回getId()
或在比较器内部1}}返回null
。
通过将空安全比较器分层到比较器,可以使比较器更安全。例如,以下代码将null
视为比其他任何值更低的值:
List<RegistrationItem> item = regI.stream()
.sorted(Comparator.nullsFirst(
Comparator.comparing(RegistrationItem::getId,
Comparator.nullsFirst(String::compareTo))))
.peek(System.out::println)
.collect(Collectors.toList());