Comprender la lógica en CaseInsensitiveComparator

¿Alguien puede explicar el siguiente código de String.java , específicamente por qué hay tres sentencias if (que he marcado //1 , //2 y //3 )?

 private static class CaseInsensitiveComparator implements Comparator, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1=s1.length(), n2=s2.length(); for (int i1=0, i2=0; i1<n1 && i2<n2; i1++, i2++) { char c1 = s1.charAt(i1); char c2 = s2.charAt(i2); if (c1 != c2) {/////////////////////////1 c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) {/////////////////////////2 c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) {/////////////////////////3 return c1 - c2; } } } } return n1 - n2; } } 

Normalmente, esperaríamos convertir el caso una vez y compararlo y terminarlo. Sin embargo, el código convierte el caso dos veces, y el motivo se expresa en el comentario sobre un método diferente public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) :

Desafortunadamente, la conversión a mayúsculas no funciona correctamente para el alfabeto georgiano, que tiene reglas extrañas sobre la conversión de mayúsculas y minúsculas. Entonces, debemos hacer una última comprobación antes de salir.


Apéndice

El código de regionMatches tiene algunas diferencias con respecto al código en CaseInsenstiveComparator , pero esencialmente hace lo mismo. El código completo del método se cita a continuación con el propósito de verificación cruzada:

 public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) { char ta[] = value; int to = offset + toffset; char pa[] = other.value; int po = other.offset + ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > (long)count - len) || (ooffset > (long)other.count - len)) { return false; } while (len-- > 0) { char c1 = ta[to++]; char c2 = pa[po++]; if (c1 == c2) { continue; } if (ignoreCase) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. // If the results match, then the comparison scan should // continue. char u1 = Character.toUpperCase(c1); char u2 = Character.toUpperCase(c2); if (u1 == u2) { continue; } // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { continue; } } return false; } return true; } 

Del estándar técnico de Unicode :

Además, debido a los caprichos del lenguaje natural, hay situaciones en las que dos caracteres Unicode diferentes tienen la misma mayúscula o minúscula

Por lo tanto, no es suficiente comparar solo mayúsculas de dos caracteres, ya que pueden tener diferentes mayúsculas y minúsculas

La simple prueba de fuerza bruta da algunos resultados. Compruebe por ejemplo los puntos de código 73 y 304:

 char ch1 = (char) 73; //LATIN CAPITAL LETTER I char ch2 = (char) 304; //LATIN CAPITAL LETTER I WITH DOT ABOVE System.out.println(ch1==ch2); System.out.println(Character.toUpperCase(ch1)==Character.toUpperCase(ch2)); System.out.println(Character.toLowerCase(ch1)==Character.toLowerCase(ch2)); 

Salida:

 false false true 

Entonces “İ” y “I” no son iguales entre sí. Ambos personajes son mayúsculas. Pero comparten la misma letra minúscula: “yo” y eso da una razón para tratarlos como los mismos valores en caso de comparación insensible.

En otra respuesta, la configuración regional predeterminada ya dio un ejemplo de por qué no basta con comparar solo mayúsculas, es decir, la letra ASCII “I” y la mayúscula I con el punto “İ”.

Ahora se podría preguntar, ¿por qué no solo comparan solo minúsculas en lugar de mayúsculas y minúsculas, si captan más casos que mayúsculas? La respuesta es que no atrapa más casos, simplemente encuentra diferentes casos.

Tome la letra “ı” ( (char)305 , pequeño sin puntos i) y el ascii “i”. Son diferentes, sus minúsculas son diferentes, pero comparten la misma letra mayúscula “I”.

Y finalmente, compare el capital I con el punto “İ” con el pequeño sin punto i “ı”. Ni sus mayúsculas (“İ” contra “I”) ni sus minúsculas (“i” contra “ı”) coinciden, pero la minúscula de su mayúscula es la misma (“I”). Encontré otro caso si este fenómeno, en las letras griegas “Θ” y “θ” (caracteres 1012 y 977).

Por lo tanto, una comparación insensible a mayúsculas y minúsculas ni siquiera puede verificar mayúsculas y minúsculas de los caracteres originales , pero debe verificar las minúsculas de las mayúsculas.

Considere los siguientes caracteres: f y F La sentencia if inicial devolvería false porque no coinciden. Sin embargo, si capitalizas ambos caracteres, obtienes F y F Entonces coincidirían. Lo mismo no sería cierto para, digamos, c y G

El código es eficiente No es necesario capitalizar ambos caracteres si ya coinciden (de ahí la primera instrucción if ). Sin embargo, si no coinciden, debemos verificar si difieren solo en el caso (de ahí el segundo enunciado if ).

La sentencia if final se usa para ciertos alfabetos (como Georgian) donde la capitalización es un asunto complicado. Para ser sincero, no sé mucho sobre cómo funciona eso (¡solo confía en que Oracle lo haga!).

En el caso anterior para la comparación insensible a mayúsculas y minúsculas supongamos s1 = “Apple” y s2 = “apple” En este caso ‘A’! = ‘A’ como los valores ascii de ambos caracteres son diferentes, entonces cambia los caracteres a mayúsculas y nuevamente se compara, entonces el bucle continúa obteniendo el valor final de n1-n2 = 0, por lo tanto, las cuerdas se vuelven iguales. Suponga que si los caracteres no son iguales en todo el segundo control

 if (c1 != c2) { return c1 - c2; } 

devuelve la diferencia en el valor ascii de los dos caracteres.