No Java 8, existe um novo método String.chars()
que retorna um fluxo de int
s ( IntStream
) que representa os códigos de caracteres. Eu acho que muitas pessoas esperariam um fluxo de char
s aqui. Qual foi a motivação para projetar a API dessa maneira?
195
CharStream
não existe, qual seria o problema para adicioná-lo?Respostas:
Como outros já mencionaram, a decisão de design por trás disso foi impedir a explosão de métodos e classes.
Ainda assim, pessoalmente, acho que foi uma péssima decisão, e deveria, dado que eles não querem tomar
CharStream
, o que é razoável, métodos diferentes em vez dechars()
, eu pensaria em:Stream<Character> chars()
, que fornece um fluxo de caracteres de caixas, que terão alguma penalidade no desempenho de luz.IntStream unboxedChars()
, que seria usado para o código de desempenho.No entanto , em vez de focar no motivo de isso ser feito atualmente, acho que essa resposta deve se concentrar em mostrar uma maneira de fazer isso com a API que obtivemos com o Java 8.
No Java 7, eu teria feito assim:
E acho que um método razoável para fazer isso no Java 8 é o seguinte:
Aqui eu obtenho um
IntStream
e mapeio-o para um objeto via lambdai -> (char)i
, isso o colocará automaticamente em umStream<Character>
, e então podemos fazer o que queremos, e ainda usar as referências de método como um plus.Esteja ciente de que você deve fazer
mapToObj
, se você esquecer e usarmap
, nada reclamará, mas você ainda terá umIntStream
e poderá ficar se perguntando por que ele imprime os valores inteiros em vez das cadeias que representam os caracteres.Outras alternativas feias para Java 8:
Ao permanecer em um
IntStream
e desejando imprimi-los, você não poderá mais usar referências de métodos para imprimir:Além disso, o uso de referências de métodos ao seu próprio método não funciona mais! Considere o seguinte:
e depois
Isso gerará um erro de compilação, pois possivelmente há uma conversão com perdas.
Conclusão:
A API foi projetada dessa maneira, por não querer adicionar
CharStream
, eu pessoalmente acho que o método deve retornar aStream<Character>
, e a solução atualmente é usarmapToObj(i -> (char)i)
umaIntStream
para poder trabalhar corretamente com eles.fonte
codePoints()
vez dechars()
e você encontrará várias funções da biblioteca que já aceitam umint
ponto de código for adicionalmente achar
, por exemplo, todos os métodosjava.lang.Character
e tambémStringBuilder.appendCodePoint
, etc. Esse suporte existe desde entãojdk1.5
.String
ouchar[]
. Aposto que a maioria doschar
códigos de processamento manipula mal os pares substitutos.void print(int ch) { System.out.println((char)ch); }
e então você pode usar referências de método.Stream<Character>
foi rejeitado.A resposta do skiwi já cobriu muitos dos principais pontos. Vou preencher um pouco mais de fundo.
O design de qualquer API é uma série de compensações. Em Java, uma das questões difíceis é lidar com decisões de design que foram tomadas há muito tempo.
As primitivas estão em Java desde a 1.0. Eles tornam o Java uma linguagem orientada a objetos "impura", uma vez que as primitivas não são objetos. A adição de primitivos foi, acredito, uma decisão pragmática para melhorar o desempenho à custa da pureza orientada a objetos.
Essa é uma troca que ainda estamos vivendo hoje, quase 20 anos depois. O recurso de caixa automática adicionado no Java 5 eliminou principalmente a necessidade de desorganizar o código-fonte com chamadas de métodos de boxe e unboxing, mas a sobrecarga ainda está lá. Em muitos casos, isso não é perceptível. No entanto, se você realizasse boxe ou unboxing dentro de um loop interno, veria que isso pode impor uma sobrecarga significativa na CPU e na coleta de lixo.
Ao projetar a API do Streams, ficou claro que precisávamos oferecer suporte a primitivos. A sobrecarga de boxe / unboxing mataria qualquer benefício de desempenho do paralelismo. No entanto, não queríamos oferecer suporte a todas as primitivas, pois isso acrescentaria uma enorme quantidade de confusão à API. (Você realmente pode ver o uso de um
ShortStream
?) "Todos" ou "nenhum" são lugares confortáveis para um design, mas nenhum deles era aceitável. Então tivemos que encontrar um valor razoável de "alguns". Nós acabamos com especializações primitivos paraint
,long
, edouble
. (Pessoalmente eu teria deixado de foraint
mas sou apenas eu.)Por
CharSequence.chars()
considerarmos o retornoStream<Character>
(um protótipo inicial pode ter implementado isso), mas foi rejeitado por causa da sobrecarga do boxe. Considerando que uma String temchar
valores como primitivos, parece um erro impor boxe incondicionalmente quando o chamador provavelmente apenas processa um pouco o valor e o desmarca de volta em uma string.Também consideramos uma
CharStream
especialização primitiva, mas seu uso pareceria bastante restrito em comparação com a quantidade de volume que acrescentaria à API. Não parecia valer a pena adicioná-lo.A penalidade que isso impõe aos chamadores é que eles precisam saber que os valores
IntStream
contidoschar
são representados comoints
e que a transmissão deve ser feita no local apropriado. Isso é duplamente confuso, pois há chamadas de API sobrecarregadas comoPrintStream.print(char)
ePrintStream.print(int)
que diferem acentuadamente em seu comportamento. Um ponto adicional de confusão possivelmente surge porque acodePoints()
chamada também retorna um,IntStream
mas os valores que ela contém são bem diferentes.Portanto, isso se resume a escolher pragmaticamente entre várias alternativas:
Não poderíamos fornecer especializações primitivas, resultando em uma API simples, elegante e consistente, mas que impõe um alto desempenho e sobrecarga de GC;
poderíamos fornecer um conjunto completo de especializações primitivas, com o custo de sobrecarregar a API e impor uma carga de manutenção aos desenvolvedores do JDK; ou
poderíamos fornecer um subconjunto de especializações primitivas, fornecendo uma API de tamanho médio e alto desempenho que impõe uma carga relativamente pequena aos chamadores em uma variedade bastante estreita de casos de uso (processamento de caracteres).
Nós escolhemos o último.
fonte
chars()
, um que retorna umStream<Character>
(com pequena penalidade de desempenho) e outroIntStream
, isso também foi considerado? É bem provável que as pessoas acabem mapeando-o deStream<Character>
qualquer maneira, se acharem que a conveniência vale a pena sobre a penalidade de desempenho.chars()
método que retorna os valores de char em umIntStream
, não é necessário adicionar outra chamada à API que obtenha os mesmos valores, mas em forma de caixa. O chamador pode colocar os valores em caixa sem muitos problemas. Certamente, seria mais conveniente não fazer isso nesse caso (provavelmente raro), mas com o custo de adicionar desorganização à API.chars()
retornoIntStream
não é um grande problema, especialmente devido ao fato de esse método raramente ser usado. No entanto, seria bom ter uma maneira integrada de converter de voltaIntStream
para o arquivoString
. Isso pode ser feito.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()
, mas é muito longo.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()
. Eu acho que não é realmente mais curto, mas o uso de pontos de código evita as(char)
conversões e permite o uso de referências de método. Além disso, ele lida com substitutos corretamente.IntStream
não têm umcollect()
método que leva aCollector
. Eles têm apenas umcollect()
método de três argumentos , como mencionado nos comentários anteriores.