Estou representando uma coleção de tartarugas, cada uma com um momento em que chocaram. Quero escolher aleatoriamente qualquer tartaruga madura.
Finjo que minutos são anos nesta simulação de aquário.
record Turtle(
String name ,
Instant hatched ,
Duration lifespan
)
{
static final Duration MATURITY = Duration.ofMinutes ( 4 );
Duration age ( ) { return Duration.between ( this.hatched , Instant.now ( ) ); }
}
Eu costumo ThreadLocalRandom
gerar um IntStream
de inteiros aleatórios. Eu uso esses inteiros como um índice em meus objetos List
de Turtle
. Eu verifico a idade daquela tartaruga, para ver se ela excede a MATURITY
duração predefinida. Se não estiver madura, deixo o fluxo continuar.
Claro, nenhuma tartaruga pode estar madura ainda. Então eu defini o tipo de retorno como Optional < Turtle >
, para ser vazio se nenhuma tartaruga for encontrada.
O problema é que meu stream parece falhar, sem nenhum resultado retornado. Por quê?
public class Aquarium
{
public static void main ( String[] args )
{
System.out.println ( "INFO Demo start. " + Instant.now ( ) );
// Sample data.
List < Turtle > turtles =
List.of (
new Turtle ( "Alice" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 3 ) ) , Duration.ofMinutes ( 17 ) ) ,
new Turtle ( "Bob" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 2 ) ) , Duration.ofMinutes ( 16 ) ) ,
new Turtle ( "Carol" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 1 ) ) , Duration.ofMinutes ( 18 ) ) ,
new Turtle ( "Davis" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 2 ) ) , Duration.ofMinutes ( 22 ) )
);
System.out.println ( "turtles = " + turtles );
// Logic
Optional < Turtle > anArbitraryMatureTurtle =
ThreadLocalRandom
.current ( )
.ints ( 0 , turtles.size ( ) )
.filter (
( int randomIndex ) -> turtles.get ( randomIndex ).age ( ).compareTo ( Turtle.MATURITY ) > 0
)
.mapToObj ( turtles :: get )
.findAny ( );
System.out.println ( "anArbitraryMatureTurtle = " + anArbitraryMatureTurtle );
// Wrap-up
try { Thread.sleep ( Duration.ofMinutes ( 30 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); } // Let the aquarium run a while.
System.out.println ( "INFO Demo end. " + Instant.now ( ) );
}
}
Infinito
Stream
pode ser loop infinitoVocê inadvertidamente fez um loop infinito do seu infinito
IntStream
.Seu
IntStream
criado porThreadLocalRandom.ints
é infinito, gerando continuamente um número aleatório após o outro, dentro do seu intervalo definido. Duplicatas podem ocorrer, pois o número aleatório continua gerando até que seu fluxo alcance uma operação terminal.Você ligou
findAny
para encerrar seu fluxo. Mas olhe para seus dados de amostra. Todas as suas tartarugas são jovens, onde nenhuma delas tem uma idade que atenda ao seuIntPredicate
teste de exceder aMATURITY
duração. Então nenhuma das suas tartarugas passa no teste de idade. Então ofindAny
nunca encontra nenhumTurtle
, então ele nunca retorna nenhumOptional < Turtle >
.Isso significa que a geração de números aleatórios continua moendo, produzindo um número de índice após o outro. Para cada número aleatório, acessamos os
List
objetosTurtle
. Mas nenhum passa no nosso teste de predicado. Portanto, nosso fluxo nunca termina . Esta rotina de teste de predicado random-generator ->List#get
-> continua infinitamente. O fluxo nunca termina, neste loop infinito, dando a aparência de que nada está acontecendo. Na verdade, muita coisa está acontecendo, com um núcleo de CPU muito ocupado gerando números, acessando a lista e calculando e comparando durações, infinitamente.Podemos demonstrar isso alterando seus dados de amostra. Inicie os peixes
Bob
&Davis
com hatch-dates de 7 minutos atrás em vez de 2 minutos. Execute seu código novamente para ver que qualquer um deles é escolhido aleatoriamente, e escolhido bem rápido.Solução
Não há solução para consertar diretamente seu código atual † . Você não pode usar um fluxo infinito sem uma terminação garantida.
Em vez disso, você deve reescrever seu código.
Uma reescrita poderia ser fazer uma cópia de seus
List
objetosTurtle
. Embaralhe-os, colocando a lista em ordem aleatória. Então transmita essa lista randomizada até que uma seja encontrada atendendo às condições do seu teste de predicado. Se nenhuma atender a essas condições, o fluxo termina após verificar exaustivamente cada elemento, e um vazioOptional
será retornado.Altere sua seção Lógica para isto:
Não há mais índice aleatório.
Quando executado com todas as tartarugas jovens e imaturas:
Essa mesma lógica poderia ser feita com um
for
loop convencional em vez de umStream
. Acho que aStream
sintaxe é mais expressiva neste caso em particular.Embaralhar uma coleção inteira provavelmente não é tão eficiente quanto escolher um índice aleatoriamente. Essa pode ter sido sua motivação original ao escolher a abordagem de índice aleatório. Mas, dados seus possíveis valores de dados, a abordagem de índice aleatório não é viável.
† Errado… há uma solução que corrige essa abordagem de fluxo de números de índice aleatórios. Veja a resposta correta de tquadrat .
Tente isso para a seção Logic
main()
no seu método, adicionando chamadas paraStream#distinct
eStream#limit
. Dessa forma, cobrimos todos os valores de índice possíveis uma vez, em uma ordem aleatória.Se nenhum
Turtle
passar no teste, obtemos um . vazioOptional
.A sequência de código:
… imprime os cinco valores
0
em4
uma ordem aleatória – sempre cinco, sem duplicatas. Claro que você tem que usarturtles.size()
para seu caso de uso em vez da constante5
.Tenho algumas dúvidas de que esta seja a maneira mais eficiente de obter uma tartaruga adulta aleatória da sua lista (considerando que existe uma), mas funciona.