Por qué es String.chars () un flujo de ints en Java 8?


En Java 8, hay un nuevo método String.chars() que devuelve un flujo de int s (IntStream) que representan los códigos de caracteres. Supongo que mucha gente esperaría una corriente de chars aquí en su lugar. ¿Cuál fue la motivación para diseñar la API de esta manera?

Author: Arend v. Reinersdorff, 2014-03-16

2 answers

Como otros ya han mencionado, la decisión de diseño detrás de esto fue para evitar la explosión de métodos y clases.

Aún así, personalmente creo que esta fue una muy mala decisión, y no debería, dado que no quieren hacer CharStream, que es razonable, métodos diferentes en lugar de chars(), yo pensaría en:

  • Stream<Character> chars(), que da un flujo de caracteres de cajas, que tendrán una penalización de rendimiento ligera.
  • IntStream unboxedChars(), que se utilizaría para código de rendimiento.

Sin embargo , en lugar de centrarse en por qué se hace de esta manera actualmente, creo que esta respuesta debe centrarse en mostrar una manera de hacerlo con la API que hemos conseguido con Java 8.

En Java 7 lo habría hecho así:

for (int i = 0; i < hello.length(); i++) {
    System.out.println(hello.charAt(i));
}

Y creo que un método razonable para hacerlo en Java 8 es el siguiente:

hello.chars()
        .mapToObj(i -> (char)i)
        .forEach(System.out::println);

Aquí puedo obtener un IntStream y asociarlo a un objeto a través de la lambda i -> (char)i, esto automáticamente cuadro en un Stream<Character>, y entonces podemos hacer lo que queremos, y todavía utilizar referencias de métodos como un plus.

Tenga en cuenta aunque debe hacer mapToObj, si olvida y usa map, entonces nada se quejará, pero aún así terminará con un IntStream, y podría quedarse preguntándose por qué imprime los valores enteros en lugar de las cadenas que representan los caracteres.

Otras alternativas feas para Java 8:

, permaneciendo en un IntStream y queriendo imprimirlos en última instancia, ya no puede usar referencias de métodos para imprimir:

hello.chars()
        .forEach(i -> System.out.println((char)i));

Por otra parte, el uso de referencias de método a su propio método no funcionan más! Considere lo siguiente:

private void print(char c) {
    System.out.println(c);
}

Y luego

hello.chars()
        .forEach(this::print);

Esto dará un error de compilación, ya que posiblemente hay una conversión con pérdida.

Conclusión:

La API fue diseñada de esta manera debido a no querer agregar CharStream, personalmente creo que el método debe devolver un Stream<Character>, y la solución actualmente es usar mapToObj(i -> (char)i) en un IntStream para poder trabajar correctamente con ellos.

 167
Author: skiwi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-16 12:09:36

La respuesta de skiwi ya cubrió muchos de los puntos principales. Voy a rellenar un poco más de fondo.

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 la versión 1.0. Hacen de Java un lenguaje orientado a objetos "impuro", ya que los primitivos no son objetos. La adición de primitivos fue, creo, una decisión pragmática para mejorar rendimiento a expensas de la pureza orientada a objetos.

Esta es una compensación con la que todavía estamos viviendo hoy, casi 20 años después. La función de autoboxing agregada en Java 5 eliminó en su mayoría la necesidad de desordenar el código fuente con las llamadas al método de boxeo y unboxing, pero la sobrecarga sigue ahí. En muchos casos no se nota. Sin embargo, si realizara boxing o unboxing dentro de un bucle interno, vería que puede imponer una recolección significativa de CPU y basura sobrecarga.

Al diseñar la API de flujos, estaba claro que teníamos que admitir primitivas. La sobrecarga de boxeo/unboxing mataría cualquier beneficio de rendimiento del paralelismo. Sin embargo, no queríamos soportar todas las primitivas, ya que eso habría agregado una gran cantidad de desorden a la API. (¿ Realmente puedes ver un uso para un ShortStream?) "Todos " o" ninguno " son lugares cómodos para que un diseño sea, pero ninguno era aceptable. Así que tuvimos que encontrar un valor razonable de "algunos". Nos terminó con especializaciones primitivas para int, long, y double. (Personalmente me habría dejado fuera int pero eso es solo yo.)

Para CharSequence.chars() consideramos devolver Stream<Character> (un prototipo temprano podría haber implementado esto), pero fue rechazado debido a la sobrecarga de boxeo. Teniendo en cuenta que una cadena tiene valores char como primitivos, parecería ser un error imponer el boxeo incondicionalmente cuando la persona que llama probablemente solo haría un poco de procesamiento en el valor y lo descomprimiría de nuevo en una cuerda.

También consideramos una especialización primitiva CharStream, pero su uso parece ser bastante limitado en comparación con la cantidad de volumen que agregaría a la API. No parecía que valiera la pena agregarlo.

La penalización que esto impone a los llamantes es que tienen que saber que el IntStream contiene char valores representados como ints y que el casting debe hacerse en el lugar adecuado. Esto es doblemente confuso porque hay llamadas de API sobrecargadas como PrintStream.print(char) y PrintStream.print(int) que difieren notablemente en su comportamiento. Un punto adicional de confusión posiblemente surge porque la llamada codePoints() también devuelve un IntStream pero los valores que contiene son bastante diferentes.

Entonces, esto se reduce a elegir pragmáticamente entre varias alternativas: {[19]]}

  1. No podríamos proporcionar especializaciones primitivas, lo que resulta en una API simple, elegante y consistente, pero que impone un alto rendimiento y sobrecarga GC;

  2. 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

  3. Podríamos proporcionar un subconjunto de especializaciones primitivas, dando una API de tamaño moderado y alto rendimiento que impone una carga relativamente pequeña a los llamantes en un rango bastante estrecho de casos de uso (procesamiento de caracteres).

Elegimos el último.

 66
Author: Stuart Marks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:59