Ambas as interfaces definem apenas um método
public operator fun iterator(): Iterator<T>
A documentação diz que Sequence
é para ser preguiçoso. Mas também não é Iterable
preguiçoso (a menos que seja apoiado por a Collection
)?
Ambas as interfaces definem apenas um método
public operator fun iterator(): Iterator<T>
A documentação diz que Sequence
é para ser preguiçoso. Mas também não é Iterable
preguiçoso (a menos que seja apoiado por a Collection
)?
Respostas:
A principal diferença reside na semântica e na implementação das funções de extensão stdlib para Iterable<T>
e Sequence<T>
.
Pois Sequence<T>
, as funções de extensão executam lentamente onde possível, de forma semelhante às operações intermediárias do Java Streams . Por exemplo, Sequence<T>.map { ... }
retorna outro Sequence<R>
e não processa realmente os itens até que uma operação de terminal como toList
ou fold
seja chamada.
Considere este código:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Ele imprime:
before sum 1 2
Sequence<T>
destina-se a uso lento e pipelining eficiente quando você deseja reduzir o trabalho feito em operações de terminal tanto quanto possível, o mesmo para Java Streams. No entanto, a preguiça introduz alguma sobrecarga, o que é indesejável para transformações simples comuns de coleções menores e as torna menos eficientes.
Em geral, não há uma boa maneira de determinar quando ele é necessário, portanto, no Kotlin stdlib, a preguiça é explicitada e extraída para a Sequence<T>
interface para evitar o uso em todos os programas Iterable
por padrão.
Pois Iterable<T>
, ao contrário, as funções de extensão com semântica de operação intermediária funcionam avidamente, processam os itens imediatamente e retornam outro Iterable
. Por exemplo, Iterable<T>.map { ... }
retorna um List<R>
com os resultados do mapeamento nele.
O código equivalente para Iterable:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
Isso imprime:
1 2 before sum
Como dito acima, Iterable<T>
não é preguiçoso por padrão, e esta solução mostra-se bem: na maioria dos casos tem boa localidade de referência , tirando proveito do cache da CPU, predição, pré-busca etc. para que mesmo a cópia múltipla de uma coleção ainda funcione o suficiente e tem melhor desempenho em casos simples com pequenas coleções.
Se você precisar de mais controle sobre o pipeline de avaliação, há uma conversão explícita para uma sequência preguiçosa com Iterable<T>.asSequence()
função.
map
, filter
e outros não carregam informações suficientes para decidir além do tipo de coleção de origem, e uma vez que a maioria das coleções também são Iteráveis, isso não é um bom marcador para "ser preguiçoso" porque é comumente EM TODOS OS LUGARES. preguiçoso deve ser explícito para ser seguro.
Concluindo a resposta da tecla de atalho:
É importante observar como Sequence e Iterable itera em todos os seus elementos:
Exemplo de sequência:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Resultado do log:
filtro - Mapa - Cada; filtro - Mapa - Cada
Exemplo iterável:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filtro - filtro - Mapa - Mapa - Cada - Cada
Iterable
é mapeado para ajava.lang.Iterable
interface noJVM
e é implementado por coleções comumente usadas, como Lista ou Conjunto. As funções de extensão da coleção são avaliadas avidamente, o que significa que todas processam imediatamente todos os elementos em sua entrada e retornam uma nova coleção contendo o resultado.Aqui está um exemplo simples de como usar as funções de coleção para obter os nomes das primeiras cinco pessoas em uma lista com pelo menos 21 anos:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Plataforma de destino: JVMRunning no kotlin v. 1.3.61 Primeiro, a verificação de idade é feita para cada pessoa na lista, com o resultado colocado em uma lista totalmente nova. Em seguida, o mapeamento de seus nomes é feito para cada Pessoa que permaneceu após o operador de filtro, terminando em mais uma nova lista (agora é a
List<String>
). Finalmente, há uma última nova lista criada para conter os primeiros cinco elementos da lista anterior.Em contraste, Sequence é um novo conceito em Kotlin para representar uma coleção de valores avaliada preguiçosamente. As mesmas extensões de coleção estão disponíveis para a
Sequence
interface, mas retornam imediatamente instâncias de Sequence que representam um estado processado da data, mas sem realmente processar nenhum elemento. Para iniciar o processamento, oSequence
deve ser encerrado com um operador de terminal, trata-se basicamente de um pedido à Sequência para materializar os dados que representa de alguma forma concreta. Os exemplos incluemtoList
,toSet
esum
, para mencionar apenas alguns. Quando eles são chamados, apenas o número mínimo necessário de elementos será processado para produzir o resultado exigido.Transformar uma coleção existente em uma sequência é bastante simples, você só precisa usar a
asSequence
extensão. Como mencionado acima, você também precisa adicionar um operador de terminal, caso contrário, a Sequência nunca fará nenhum processamento (de novo, preguiçoso!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Plataforma de destino: JVMRunning no kotlin v. 1.3.61 Nesse caso, as instâncias de Person na Sequência são verificadas quanto à idade, se passarem, seus nomes serão extraídos e, em seguida, adicionados à lista de resultados. Isso é repetido para cada pessoa na lista original até que cinco pessoas sejam encontradas. Nesse ponto, a função toList retorna uma lista e o restante das pessoas no
Sequence
não são processadas.Há também algo extra de que uma Sequência é capaz: ela pode conter um número infinito de itens. Com isso em perspectiva, faz sentido que os operadores trabalhem da maneira que o fazem - um operador em uma sequência infinita nunca poderia retornar se fizesse seu trabalho avidamente.
Como exemplo, aqui está uma sequência que irá gerar tantas potências de 2 quantas forem exigidas por seu operador de terminal (ignorando o fato de que isso transbordaria rapidamente):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Você pode encontrar mais aqui .
Java
(principalmenteGuava
)