Java 8流:确定列表成员是否“相等”

时间:2016-06-14 20:44:25

标签: java java-8 java-stream

我正在尝试以非常简洁的方式(当然使用流)来确定集合中的两个成员(列表)是否相等。但在方法equals()的意义上并不相同,而是使用不同的方法进行比较。

基本上,我有一个时间列表,这些定义了一个订单。您可以使用Timeslot#compareTo(Timeslot)方法说明时间段是先于,超过或等于另一个时间段。我需要这些时间段按顺序排列,也就是说,它们中的每一个都在列表中的后面,但我还需要它们中的每一个都严格地大于下一个。它们不能相等(您不能拥有timeslot1=10am然后timeslot2=10am)。

我完成了订购,这很简单:

Collections.sort(timeslots, Timeslot::compareTo);

但是,在确定每个时隙是否严格优先于以下时,我想非常简洁。当然,我可以按照以下传统方式做到这一点:

for (int i = 0; i < timeslots.size() - 1; i++)
    if (timeslots.get(i).compareTo(timeslots.get(i + 1)) == 0)
        throw new IllegalArgumentException("Every timeslot must strictly precede the following");

也许我要求过多的流,并没有更好,更有效的方法来实现这一点,但我很乐意听到你的想法。

4 个答案:

答案 0 :(得分:4)

通过尝试查找Stream API解决方案,您可能认为太复杂了。使用基本的Collection API操作可以轻松解决该任务:

List<Timeslot> timeslots;// the source

TreeSet<Timeslot> sorted=new TreeSet<>(timeslots);
if(sorted.size() != timeslots.size())
  throw new IllegalArgumentException("Every timeslot must strictly precede the following");
// get a list where every timeslot strictly precedes the following:
timeslots=new ArrayList<>(sorted);

TreeSet元素的相等性仅由顺序决定。因此,如果根据顺序存在重复项,并且这就是您的问题所在,则在将列表转换为集合时会删除它们。因此,如果大小不同,就会出现这样的重复。如果不需要,您可以将已排序的集合转换回列表,如果需要列表。否则,您可以继续使用该集而不是列表。

应该提到的是,由于您声明订单与equals不一致,因此TreeSet也不会完全符合its documentation合同中所述的Set合同}:

  

请注意,如果要正确实现Set接口,则由集合维护的排序(无论是否提供显式比较器)必须与equals 一致。 [...]这是因为Set接口是根据equals操作定义的,但TreeSet实例使用其compareTo(或{{compare执行所有元素比较1}})方法,因此,从集合的角度来看,这种方法认为相等的两个元素是相等的。集合的行为定义良好,即使它的排序与equals不一致;它只是没有遵守Set接口的一般合同。

由于您的操作不依赖于Set接口的常规合同,因为之前没有Set,因此对于此特定用例而言,这不是问题。转换为如上所示的List是有效的,因此会迭代元素,以防您在没有转换的情况下使用它,即不需要特定的列表操作。

答案 1 :(得分:2)

我认为对列表进行排序并比较相邻元素是一种合理的方法。以下是基于流的代码:

    timeslots.sort(null);  // sort the list using natural order
    if (IntStream.range(1, timeslots.size())
                 .map(i -> timeslots.get(i-1).compareTo(timeslots.get(i)))
                 .anyMatch(i -> i == 0)) {
        throw new IllegalArgumentException("...");
    }

它并不比你拥有的短,但基于流的方法可能提供更大的灵活性:它可以并行运行,你可以计算重复时间戳的数量等。

您还可以将compareTo()电话内联到anyMatch()来电,如下所示:

    if (IntStream.range(1, timeslots.size())
                 .anyMatch(i -> timeslots.get(i-1).compareTo(timeslots.get(i)) == 0) {

我认为这是否更可取是一种风格问题。

答案 2 :(得分:2)

简单 - 只需使用Stream'sorted'并制作自定义比较器。

请参阅https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#sorted-java.util.Comparator-

这样的事情:

Timeslots timeslots = ... 

Stream<Timeslots> tstream  = timeslots
     .stream()
     .sorted( (a,b) -> {
          int compare = a.compareTo(b);
          if(compare==0){
               throw new IllegalArgumentException("Every timeslot must strictly precede the following");
          }
          return compare;
     });

是的......你需要一个终端操作来实际运行Stream。 我假设有下一个操作或者你给Timeslots提供的东西。

使用此方法可以获得Stream结果,并且无需事先对Timeslots进行预排序。

试一试!

答案 3 :(得分:0)

我不确定这比你的版本更简洁,但是你可以用流来做到这一点:

timeslots.stream().reduce((a, b) -> {
    if (a.compareTo(b) == 0) {
        throw new IllegalArgumentException("Every timeslot must strictly precede the following");
    }
    return b;
});

另一种解决方案,受@ VGR评论的启发:

timeslots.stream().collect(Collectors.toMap(Function.identity(), Function.identity(), (a, b) -> {
    throw new IllegalArgumentException("Every timeslot must strictly precede the following");
}, () -> new TreeMap<>(Timeslot::compareTo)));