En Java 8, hay un nuevo método String.chars()
que devuelve una secuencia de int
s ( IntStream
) que representa los códigos de caracteres. Supongo que mucha gente esperaría una corriente de char
s aquí en su lugar. ¿Cuál fue la motivación para diseñar la API de esta manera?
198
CharStream
no existe, ¿cuál sería el problema para agregarlo?Respuestas:
Como otros ya han mencionado, la decisión de diseño detrás de esto fue evitar la explosión de métodos y clases.
Aún así, personalmente creo que esta fue una decisión muy mala, y debería, dado que no quieren tomar
CharStream
, lo cual es razonable, métodos diferentes en lugar dechars()
, pensaría en:Stream<Character> chars()
, que proporciona una secuencia de caracteres de cuadros, que tendrá una ligera penalización de rendimiento.IntStream unboxedChars()
, que se utilizaría para el código de rendimiento.Sin embargo , en lugar de centrarnos en por qué se hace de esta manera actualmente, creo que esta respuesta debería centrarse en mostrar una manera de hacerlo con la API que obtuvimos con Java 8.
En Java 7 lo habría hecho así:
Y creo que un método razonable para hacerlo en Java 8 es el siguiente:
Aquí obtengo un
IntStream
y lo mapeo a un objeto a través de la lambdai -> (char)i
, esto automáticamente lo encuadrará en unStream<Character>
, y luego podemos hacer lo que queramos, y aún usar referencias de métodos como un plus.Sin embargo, tenga en cuenta que debe hacerlo
mapToObj
, si olvida y usamap
, entonces nada se quejará, pero aún terminará con unIntStream
, y es posible que se pregunte por qué imprime los valores enteros en lugar de las cadenas que representan los caracteres.Otras alternativas feas para Java 8:
Al permanecer en un
IntStream
y querer imprimirlos en última instancia, ya no puede usar referencias de métodos para imprimir:Además, ¡usar referencias de método a su propio método ya no funciona! Considera lo siguiente:
y entonces
Esto generará un error de compilación, ya que posiblemente haya una conversión con pérdida.
Conclusión:
La API se diseñó de esta manera por no querer agregar
CharStream
, personalmente creo que el método debería devolver unStream<Character>
, y la solución actual es usarmapToObj(i -> (char)i)
en unIntStream
para poder trabajar correctamente con ellos.fuente
codePoints()
lugar dechars()
y encontrará muchas funciones de biblioteca que ya aceptan unint
punto de código adicionalchar
, por ejemplo, todos los métodosjava.lang.Character
, así comoStringBuilder.appendCodePoint
, etc. Este soporte existe desde entoncesjdk1.5
.String
ochar[]
. Apuesto a que la mayoría delchar
código de procesamiento maneja mal los pares sustitutos.void print(int ch) { System.out.println((char)ch); }
y luego puedes usar referencias de métodos.Stream<Character>
fue rechazado.La respuesta de skiwi cubrió muchos de los puntos más importantes. Completaré un poco más de antecedentes.
El diseño de cualquier API es una serie de compensaciones. En Java, uno de los problemas difíciles es lidiar con las decisiones de diseño que se tomaron hace mucho tiempo.
Las primitivas han estado en Java desde 1.0. Hacen de Java un lenguaje orientado a objetos "impuro", ya que las primitivas no son objetos. La adición de primitivas fue, creo, una decisión pragmática para mejorar el rendimiento a expensas de la pureza orientada a objetos.
Esta es una compensación con la que todavía vivimos hoy, casi 20 años después. La característica de autoboxing agregada en Java 5 eliminó principalmente la necesidad de desordenar el código fuente con llamadas al método de boxing y unboxing, pero la sobrecarga sigue ahí. En muchos casos no se nota. Sin embargo, si realizara un boxeo o unboxing dentro de un bucle interno, vería que puede imponer una sobrecarga considerable de CPU y recolección de basura.
Al diseñar la API de Streams, estaba claro que teníamos que soportar primitivas. La sobrecarga de boxing / unboxing mataría cualquier beneficio de rendimiento del paralelismo. Sin embargo, no queríamos admitir todas las primitivas, ya que eso habría agregado una gran cantidad de desorden a la API. (¿Realmente puede ver el uso de a
ShortStream
?) "Todos" o "ninguno" son lugares cómodos para un diseño, pero ninguno era aceptable. Así que tuvimos que encontrar un valor razonable de "algunos". Terminamos con especializaciones primitivas paraint
,long
ydouble
. (Personalmente, me hubiera dejado fuera,int
pero solo soy yo).Porque
CharSequence.chars()
consideramos regresarStream<Character>
(un prototipo temprano podría haber implementado esto) pero fue rechazado debido a la sobrecarga del boxeo. Teniendo en cuenta que una Cadena tienechar
valores como primitivos, parecería un error imponer el boxeo incondicionalmente cuando la persona que llama probablemente solo procesará un poco el valor y lo desempaquetará nuevamente en una cadena.También consideramos una
CharStream
especialización primitiva, pero su uso parece ser bastante limitado en comparación con la cantidad de volumen que agregaría a la API. No valía la pena agregarlo.La penalidad que esto impone a las personas que llaman es que deben saber que
IntStream
contienechar
valores representados comoints
y que el lanzamiento debe realizarse en el lugar adecuado. Esto es doblemente confuso porque hay llamadas API sobrecargadas comoPrintStream.print(char)
yPrintStream.print(int)
que difieren notablemente en su comportamiento. Posiblemente surja un punto de confusión adicional porque lacodePoints()
llamada también devuelve un,IntStream
pero los valores que contiene son bastante diferentes.Entonces, esto se reduce a elegir pragmáticamente entre varias alternativas:
No podríamos proporcionar especializaciones primitivas, lo que da como resultado una API simple, elegante y consistente, pero que impone un alto rendimiento y una sobrecarga de GC;
podríamos proporcionar un conjunto completo de especializaciones primitivas, a costa de saturar la API e imponer una carga de mantenimiento a los desarrolladores de JDK; o
podríamos proporcionar un subconjunto de especializaciones primitivas, proporcionando una API de tamaño moderado y alto rendimiento que impone una carga relativamente pequeña a las personas que llaman en un rango bastante limitado de casos de uso (procesamiento de caracteres).
Elegimos el último.
fuente
chars()
, uno que devuelve unStream<Character>
(con una pequeña penalización de rendimiento) y otroIntStream
, ¿también se consideró? Es muy probable que las personas terminen mapeándolo deStream<Character>
todos modos si creen que la conveniencia vale la pena por la penalización de rendimiento.chars()
método que devuelve los valores de caracteres en unIntStream
, no agrega mucho tener otra llamada API que obtenga los mismos valores pero en forma de recuadro. La persona que llama puede encuadrar los valores sin muchos problemas. Claro, sería más conveniente no tener que hacer esto en este caso (probablemente raro), pero a costa de agregar desorden a la API.chars()
regresarIntStream
no es un gran problema, especialmente dado el hecho de que este método rara vez se usa en absoluto. Sin embargo, sería bueno tener una forma integrada de convertir de nuevoIntStream
aString
. Se puede hacer.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()
, pero es realmente largo.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()
. Supongo que no es realmente más corto, pero el uso de puntos de código evita los(char)
lanzamientos y permite el uso de referencias de métodos. Además, maneja sustitutos correctamente.IntStream
no tienen uncollect()
método que tome unCollector
. Solo tienen uncollect()
método de tres argumentos como se mencionó en comentarios anteriores.