¿Puede una cadena Unicode válida contener FFFF? ¿Está roto Java / CharacterIterator?

Aquí hay un extracto de la documentación de java.text.CharacterIterator :

  • Esta interface define un protocolo para la iteración bidireccional sobre el texto. El iterador itera sobre una secuencia limitada de caracteres. […] Los métodos previous() y next() se usan para la iteración. DONE si […], lo que indica que el iterador ha llegado al final de la secuencia.

  • static final char DONE : Constante que se devuelve cuando el iterador ha llegado al final o al comienzo del texto. El valor es \uFFFF , el valor “no un carácter” que no debería aparecer en ninguna cadena Unicode válida .

La parte en cursiva es lo que tengo problemas para entender, porque a partir de mis pruebas, parece que Java String ciertamente puede contener \uFFFF , y no parece haber ningún problema con él, excepto obviamente con el recorrido CharacterIterator prescrito idioma que se rompe debido a un falso positivo (por ejemplo, next() devuelve '\uFFFF' == DONE cuando en realidad no está “hecho”).

Aquí hay un fragmento para ilustrar el “problema” ( ver también en ideone.com ):

 import java.text.*; public class CharacterIteratorTest { // this is the prescribed traversal idiom from the documentation public static void traverseForward(CharacterIterator iter) { for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { System.out.print(c); } } public static void main(String[] args) { String s = "abc\uFFFFdef"; System.out.println(s); // abc?def System.out.println(s.indexOf('\uFFFF')); // 3 traverseForward(new StringCharacterIterator(s)); // abc } } 

Entonces, ¿qué está pasando aquí?

  • ¿El modismo transversal prescrito está “roto” porque hace una suposición errónea sobre \uFFFF ?
  • ¿ StringCharacterIterator implementación de StringCharacterIterator “rota” porque, por ejemplo, no throw una IllegalArgumentException si, de hecho, \uFFFF está prohibido en cadenas Unicode válidas?
  • ¿Es realmente cierto que las cadenas Unicode válidas no deberían contener \uFFFF ?
  • Si eso es cierto, ¿Java está “roto” por violar la especificación Unicode por (en su mayor parte) permitiendo que String contenga \uFFFF todos modos?

EDITAR (2013-12-17): Peter O. presenta un excelente punto a continuación, que hace que esta respuesta sea incorrecta. Respuesta anterior a continuación, para precisión histórica.


Contestando tus preguntas:

¿El modismo transversal prescrito está “roto” porque hace una suposición errónea sobre \ uFFFF?

No. U + FFFF es un llamado no personaje. De la Sección 16.7 del Estándar Unicode :

Los noncharacters son puntos de código que están reservados permanentemente en el estándar Unicode para uso interno. Están prohibidos para su uso en el intercambio abierto de datos de texto Unicode.

El estándar Unicode reserva 66 puntos de código que no son caracteres. Los dos últimos puntos de código de cada plano son noncharacters: U + FFFE y U + FFFF en el BMP, U + 1FFFE y U + 1FFFF en el plano 1, y así sucesivamente, hasta U + 10FFFE y U + 10FFFF en el plano 16, para un total de 34 puntos de código. Además, hay un rango contiguo de otros 32 puntos de código no característicos en el BMP: U + FDD0..U + FDEF.

¿La implementación de StringCharacterIterator está “rota” porque, por ejemplo, no arroja una IllegalArgumentException si, de hecho, \ uFFFF está prohibido en cadenas Unicode válidas?

No exactamente. Las aplicaciones pueden usar esos puntos de código internamente de la forma que deseen. Citando el estándar otra vez:

Las aplicaciones son libres de usar cualquiera de estos puntos de código que no sean de carácter interno, pero nunca deben intentar intercambiarlos. Si se recibe un carácter que no es carácter en el intercambio abierto, no se requiere una aplicación para interpretarlo de ninguna manera. Sin embargo, es una buena práctica reconocerlo como un carácter distinto y tomar las medidas apropiadas, como reemplazarlo con el CARACTER DE REEMPLAZO DE U + FFFD, para indicar el problema en el texto. No se recomienda simplemente eliminar puntos de código que no sean caracteres de dicho texto, debido a los posibles problemas de seguridad causados ​​por la eliminación de caracteres no interpretados.

Entonces, si bien nunca deberías encontrar una cadena de ese tipo del usuario, otra aplicación o un archivo, puedes ponerlo en una cadena Java si sabes lo que estás haciendo (esto básicamente significa que no puedes usar el CharacterIterator en esa cadena, aunque.

¿Es realmente cierto que las cadenas Unicode válidas no deberían contener \ uFFFF?

Como se citó anteriormente, cualquier cadena utilizada para el intercambio no debe contenerlos. Dentro de su aplicación puede usarlos de la forma que desee.

Por supuesto, un char Java, que es solo un entero sin signo de 16 bits, tampoco se preocupa realmente por el valor que tiene.

Si eso es cierto, ¿Java está “roto” por violar la especificación Unicode por (en su mayor parte) permitiendo que String contenga \ uFFFF de todos modos?

No. De hecho, la sección sobre noncharacters incluso sugiere el uso de U + FFFF como valor centinela:

En efecto, los noncaracters pueden considerarse puntos de código de uso privado interno de la aplicación. A diferencia de los caracteres de uso privado discutidos en la Sección 16.5, Caracteres de uso privado , que son caracteres asignados y que están destinados a uso en intercambio abierto, sujetos a interpretación por acuerdo privado, los caracteres no reservados están permanentemente reservados (sin asignar) y no tienen interpretación alguna fuera de su posible aplicación-usos privados internos.

U + FFFF y U + 10FFFF. Estos dos puntos de código no característicos tienen el atributo de estar asociados con los valores de unidad de código más grandes para formas de encoding Unicode particulares. En UTF-16, U + FFFF está asociado con el mayor valor de unidad de código de 16 bits, FFFF 16 . U + 10FFFF está asociado con el valor de unidad de código UTF-32 legal más grande de 32 bits, 10FFFF 16 . Este atributo hace que estos dos puntos de código no característicos sean útiles para fines internos como centinelas. Por ejemplo, pueden usarse para indicar el final de una lista, para representar un valor en un índice que garantice ser más alto que cualquier valor de carácter válido, y así sucesivamente.

CharacterIterator sigue esto en que devuelve U + FFFF cuando no hay más caracteres disponibles. Por supuesto, esto significa que si tiene otro uso para ese punto de código en su aplicación, puede considerar el uso de un carácter distinto para ese propósito, ya que U + FFFF ya está tomado, al menos si está usando CharacterIterator.

Algunas de estas respuestas han cambiado mientras tanto.

El Consorcio Unicode emitió recientemente el Corrigendum 9 que aclara el rol de los noncaracters, incluyendo U + FFFF, en cadenas Unicode. Establece que, si bien los caracteres que no son caracteres están destinados para uso interno, pueden ocurrir legalmente en cadenas Unicode.

Eso significa que la statement “El valor es \ uFFFF, el valor ‘no un carácter’ que no debería aparecer en ninguna cadena Unicode válida.” ahora es incorrecto, ya que U + FFFF puede ocurrir en cadenas Unicode válidas.

En consecuencia:

  • ¿Se ha roto la “expresión idiomática”? Sí, porque supone erróneamente la validez de U + FFFF en cadenas Unicode.
  • ¿La implementación de StringCharacterIterator está “rota” porque no arroja una excepción si \ uFFFF está prohibido en cadenas Unicode válidas? Como U + FFFF es válido, esto no se aplica aquí. Pero una implementación tiene una amplia flexibilidad para señalar un error cuando encuentra texto que es ilegal por otros motivos, como puntos de código sustituto sin emparejar, que siguen siendo ilegales (consulte la cláusula de conformidad C10 en el capítulo 3 del Estándar Unicode).
  • ¿Es cierto que las cadenas Unicode válidas no deberían contener \ uFFFF? U + FFFF no es ilegal en una cadena Unicode válida. Sin embargo, U + FFFF está reservado como un carácter distinto y, por lo tanto, generalmente no aparecerá en un texto significativo. El corrigendum borró el texto de que los noncaracters “nunca deberían intercambiarse”, lo que el corrigendum dice que sucede “cada vez que una cadena Unicode cruza un límite API”, incluida la API StringCharacterIterator en cuestión aquí.
  • Si eso es cierto, ¿Java está “roto” por violar la especificación Unicode al permitir que String contenga \ uFFFF de todos modos? La especificación para java.lang.String dice “Una cadena representa una cadena en el formato UTF-16”. U + FFFF es legal en una cadena Unicode, por lo que Java no infringe Unicode por permitir U + FFFF en una cadena que lo contiene.

¿La implementación de StringCharacterIterator está “rota” porque, por ejemplo, no arroja una IllegalArgumentException si, de hecho, \ uFFFF está prohibido en cadenas Unicode válidas?

No estrictamente de acuerdo con Unicode, pero es inconsistente con el rest de las interfaces de manejo de cadenas de Java, y esa incoherencia podría tener efectos muy desagradables. Piense en todos los agujeros de seguridad que hemos tenido a partir del procesamiento de cadenas que hace vs. no trata a \0 como un terminador.

Evitaría fuertemente la interfaz CharacterIterator .

Sí, el uso de CharacterIterator de 0xFFFF como el valor DONE es un poco anómalo. Pero todo tiene sentido desde la perspectiva del procesamiento de texto eficiente.

La clase String no prohíbe el 0xFFFF “no carácter” y otros puntos de código Unicode reservados o no asignados. Para hacerlo, se requeriría que los constructores de String verifiquen cada valor de char proporcionado. También presentaría problemas con el manejo de texto que contiene puntos de código Unicode definidos en una versión futura (con respecto a la JVM) de Unicode.

Por otro lado, la interfaz CharacterIterator está diseñada para permitir la iteración llamando a un solo método; es decir, next() . Han decidido usar un valor distinguido de char para indicar “no más”, porque las otras alternativas son:

  • lanzando una excepción (que es muy costosa), o
  • usando int como el tipo de retorno, cuya vida es más complicada para quien llama.

Si el CharacterIterator se usa para texto Unicode “real”, entonces el hecho de que no pueda incluir 0xFFFF no es un problema. El texto Unicode válido no contiene este punto de código. (De hecho, la razón por la que 0xFFFF está reservado como un carácter no es para admitir aplicaciones donde el texto Unicode se representa como cadenas terminadas por un valor que no sea de carácter. El uso de 0xFFFF como carácter lo rompería por completo).

La conclusión es:

  • si quieres strings Unicode estrictos, entonces no uses String , y
  • si desea iterar sobre cadenas de Java que contienen valores 0xFFFF, entonces no use un CharacterIterator.