Por que o Stream <T> não implementa o Iterable <T>?


262

No Java 8, temos a classe Stream <T> , que curiosamente tem um método

Iterator<T> iterator()

Então, você esperaria implementar a interface Iterable <T> , que requer exatamente esse método, mas esse não é o caso.

Quando quero iterar em um Stream usando um loop foreach, tenho que fazer algo como

public static Iterable<T> getIterable(Stream<T> s) {
    return new Iterable<T> {
        @Override
        public Iterator<T> iterator() {
            return s.iterator();
        }
    };
}

for (T element : getIterable(s)) { ... }

Estou faltando alguma coisa aqui?


7
para não mencionar que os outros métodos de 2 iteráveis (forEach e spliterator) também são em Stream
njzk2

1
isto é necessário para passar Streampara APIs de legado que esperaIterable
Zhongyu

12
Um bom IDE (por exemplo, IntelliJ) irá pedir-lhe para simplificar o seu código em getIterable()quereturn s::iterator;
Zhongyu

24
Você não precisa de um método. Onde você tem um Stream e deseja um Iterable, basta transmitir stream :: iterator (ou, se preferir, () -> stream.iterator ()), e pronto.
Brian Goetz

7
Infelizmente não posso escrever for (T element : stream::iterator), por isso ainda prefiro se o Stream também implementaria Iterableou um método toIterable().
Thorsten 15/05

Respostas:


197

As pessoas já perguntaram o mesmo na lista de discussão ☺. O principal motivo é que o Iterable também possui uma semântica reiterável, enquanto o Stream não.

Eu acho que a principal razão é que isso Iterableimplica reutilização, enquanto que Streamé algo que só pode ser usado uma vez - mais como um Iterator.

Se Streamestendido Iterable, o código existente pode ser surpreendido ao receber um Iterableque é emitido Exceptionpela segunda vez for (element : iterable).


22
Curiosamente, já havia algumas iteráveis ​​com esse comportamento no Java 7, por exemplo, DirectoryStream: Embora o DirectoryStream estenda o Iterable, ele não é um Iterable de uso geral, pois suporta apenas um único Iterator; invocar o método iterador para obter um segundo ou um iterador subsequente lança IllegalStateException. ( openjdk.java.net/projects/nio/javadoc/java/nio/file/… )
roim

32
Infelizmente, não há nada na documentação Iterablesobre se iteratordeve ou não ser possível chamar várias vezes. Isso é algo que eles deveriam colocar lá. Isso parece ser mais uma prática padrão do que uma especificação formal.
Lii 02/04

7
Se eles vão usar desculpas, você acha que eles poderiam ao menos adicionar um método asIterable () - ou sobrecarregar todos os métodos que usam apenas Iterable.
Trejkaz

26
Talvez a melhor solução fosse fazer com que o foreach de Java aceite um <T> iterável e potencialmente um <T> de fluxo?
devorado elysium

3
@Lii Conforme as práticas padrão, é bastante forte.
Biziclop 28/05

161

Para converter um Streampara um Iterable, você pode fazer

Stream<X> stream = null;
Iterable<X> iterable = stream::iterator

Para passar a Streampara um método que espera Iterable,

void foo(Iterable<X> iterable)

simplesmente

foo(stream::iterator) 

no entanto, provavelmente parece engraçado; pode ser melhor ser um pouco mais explícito

foo( (Iterable<X>)stream::iterator );

67
Você também pode usar isso em um loop for(X x : (Iterable<X>)stream::iterator), embora pareça feio. Realmente, toda a situação é simplesmente absurda.
Aleksandr Dubinsky

24
@HRJIntStream.range(0,N).forEach(System.out::println)
MikeFHay

11
Eu não entendo a sintaxe de dois pontos neste contexto. Qual é a diferença entre stream::iteratore stream.iterator(), que torna o primeiro aceitável para um, Iterablemas não o último?
Daniel C. Sobral

20
Responder a mim mesmo: Iterableé uma interface funcional; basta passar uma função que a implementa.
Daniel C. Sobral

5
Eu tenho que concordar com @AleksandrDubinsky, O verdadeiro problema é que existe uma solução alternativa para (X x: (Iterable <X>) stream :: iterator), que permite a mesma operação, embora com um grande ruído de código. Mas esse é o Java, e nós toleramos a lista <String> list = new ArrayList (Arrays.asList (array)) por um longo tempo :) Embora os poderes existentes possam nos oferecer todos List <>> list = array. toArrayList (). Mas o verdadeiro absurdo é que, incentivando todo mundo a usar o forEach on Stream, as exceções devem necessariamente ser desmarcadas [o discurso retórico].
O Coordenador

10

Gostaria de salientar que StreamEximplementa Iterable(e Stream), bem como uma série de outras funcionalidades imensamente impressionantes ausentes Stream.


8

Você pode usar um fluxo em um forloop da seguinte maneira:

Stream<T> stream = ...;

for (T x : (Iterable<T>) stream::iterator) {
    ...
}

(Execute este trecho aqui )

(Isso usa uma interface funcional Java 8 convertida.)

(Isso é abordado em alguns dos comentários acima (por exemplo, Aleksandr Dubinsky ), mas eu queria extraí-lo em uma resposta para torná-lo mais visível.)


Quase todo mundo precisa olhar duas ou três vezes antes de perceber como é compilado. Isso é um absurdo. (Isso é abordado na resposta aos comentários no mesmo fluxo de comentários, mas eu queria adicionar o comentário aqui para torná-lo mais visível.)
ShioT

7

O kennytm descreveu por que não é seguro tratar um Streamcomo um Iterablee Zhong Yu oferece uma solução alternativa que permite o uso de um Streamcomo Iterable, embora de maneira insegura. É possível obter o melhor dos dois mundos: a reutilizáveis Iterablea partir de um Streamque atenda todas as garantias feitas pela Iterableespecificação.

Nota: SomeTypenão é um parâmetro de tipo aqui - você precisa substituí-lo por um tipo adequado (por exemplo, String) ou recorrer à reflexão

Stream<SomeType> stream = ...;
Iterable<SomeType> iterable = stream.collect(toList()):

Há uma grande desvantagem:

Os benefícios da iteração lenta serão perdidos. Se você planejou iterar imediatamente todos os valores no encadeamento atual, qualquer sobrecarga será insignificante. No entanto, se você planejou iterar apenas parcialmente ou em um encadeamento diferente, essa iteração imediata e completa pode ter consequências indesejadas.

A grande vantagem, é claro, é que você pode reutilizar o Iterable, enquanto (Iterable<SomeType>) stream::iteratorisso permitiria apenas um único uso. Se o código de recebimento estiver repetindo a coleção várias vezes, isso não é apenas necessário, mas provavelmente benéfico para o desempenho.


1
Você já tentou compilar seu código antes de responder? Isso não funciona.
Tagir Valeev

1
@TagirValeev Sim, eu fiz. Você precisa substituir T pelo tipo apropriado. Copiei o exemplo do código de trabalho.
Zenexer 23/01

2
@TagirValeev Testei novamente no IntelliJ. Parece que o IntelliJ às vezes fica confuso com essa sintaxe; Eu realmente não encontrei um padrão para isso. No entanto, o código é compilado corretamente e o IntelliJ remove o aviso de erro após a compilação. Eu acho que é apenas um bug.
Zenexer 23/01

1
Stream.toArray()retorna uma matriz, não uma Iterable, portanto esse código ainda não é compilado. mas pode ser um bug no eclipse, desde IntelliJ parece compilá-lo
Benez

1
@Zenexer Como você pôde atribuir uma matriz a um Iterable?
precisa saber é o seguinte

3

Streamnão implementa Iterable. O entendimento geral de Iterablequalquer coisa que possa ser repetido, muitas e muitas vezes. Streampode não ser reproduzível.

A única solução alternativa em que consigo pensar, em que uma iterável baseada em um fluxo também é reproduzível, é recriar o fluxo. Estou usando um Supplierabaixo para criar uma nova instância do fluxo, sempre que um novo iterador é criado.

    Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(10);
    Iterable<Integer> iterable = () -> streamSupplier.get().iterator();
    for(int i : iterable) {
        System.out.println(i);
    }
    // Can iterate again
    for(int i : iterable) {
        System.out.println(i);
    }

2

Se você não se importa em usar bibliotecas de terceiros, o cyclops- react define um Stream que implementa Stream e Iterable e também é reproduzível (resolvendo o problema descrito pelo kennytm ).

 Stream<String> stream = ReactiveSeq.of("hello","world")
                                    .map(s->"prefix-"+s);

ou: -

 Iterable<String> stream = ReactiveSeq.of("hello","world")
                                      .map(s->"prefix-"+s);

 stream.forEach(System.out::println);
 stream.forEach(System.out::println);

[Divulgação Sou o principal desenvolvedor do cyclops-react]


0

Não é perfeito, mas funcionará:

iterable = stream.collect(Collectors.toList());

Não é perfeito porque ele buscará todos os itens do fluxo e os colocará nele List, o que não é exatamente o que é Iterablee o que Streamé. Eles deveriam ser preguiçosos .


-1

Você pode iterar sobre todos os arquivos em uma pasta usando o Stream<Path>seguinte:

Path path = Paths.get("...");
Stream<Path> files = Files.list(path);

for (Iterator<Path> it = files.iterator(); it.hasNext(); )
{
    Object file = it.next();

    // ...
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.