Java Stream有状态findFirst

时间:2018-10-11 19:03:29

标签: java java-stream

以下方法是用于挑选歌曲的加权随机选择算法的一部分。

我想将以下方法转换为使用流,以确定是否更清楚/更可取。我不确定这是不可能的,因为计算是有状态的操作,取决于列表中的位置。

public Song songForTicketNumber(long ticket)
{
    if(ticket<0) return null;
    long remaining = ticket;
    for(Song s : allSongs)  // allSongs is ordered list
    {
        rem-=s.numTickets; // numTickets is a long and never negative
        if(remaining<0)
            return s;
    }
    return null;
}

更正式的说法:如果nSong::numTickets中每个Song对象的所有allSongs的总和,那么对于{{1} }},上述方法应返回列表中的歌曲。返回特定的0对象n-1的整数数量将由Song确定。特定歌曲的选择条件是一系列连续整数,该整数由两者x属性和左侧列表中每个项目的x.numTickets属性确定。按照目前的说法,超出范围的任何内容都将返回null。

注意:超出范围的行为可以修改以适应Streams(返回null除外)

2 个答案:

答案 0 :(得分:1)

Stream与基本for或for-each循环相比的效率是一个问题。在您的代码中,Stream的效率很可能会比您当前的代码更低,原因如下:

  1. 您提到的功能是有状态的。使用这种方法维护状态可能意味着要对BinaryOperator进行某种匿名实现以与Stream.reduce一起使用,这将导致比当前代码更笨重且更易读取。
  2. 您在当前回路中短路,并且没有Stream操作会反映出这种效率,尤其是与#1结合使用时。
  3. 您的集合是有序的,这意味着流将以非常类似于您现有循环的方式遍历元素。根据集合的大小,您可能 parallelStream中获得一些效率,但是在这种情况下必须保持顺序将意味着效率较低。

切换到Stream可获得的唯一真正好处是内存消耗的差异(您可以将allSongs保留在内存之外,而让Stream处理更多的内存)效率的方法),在这里似乎并不适用。

最后,由于Stream操作的编写会更加复杂,并且可能对您的效率造成危害(如果有的话),因此建议您不要进行此更改。

话虽这么说,但我个人无法提出一个基于Stream的解决方案来实际回答您有关如何将此工作转换为Stream的问题。再次,这将涉及到reducer或类似的东西,这将是复杂而奇怪的事情……(如果这还不够,我将删除此答案。)

答案 1 :(得分:0)

Java 流确实具有短路评估的功能,例如参见 findFirst() 的文档。话虽如此,递减和检查 remaining 需要状态突变,这不是很好。不是很好,但可行:

    public Optional<Song> songForTicketNumber(long ticket, Stream<Song> songs) {
        if (ticket < 0) return Optional.empty();

        AtomicLong remaining = new AtomicLong(ticket);
        return songs.filter(song -> decrementAndCheck(song, remaining)).findFirst();
    }

    private boolean decrementAndCheck(Song song, AtomicLong total) {
        total.addAndGet(-song.numTickets);
        return total.get() < 0;
    }

据我所知,这种方法的唯一优点是您可以根据需要切换到并行流。