java - Entendiendo la lógica en CaseInsensitiveComparator
string jdk1.6 (5)
Considera los siguientes caracteres: f
y F
. La declaración inicial if
devolvería false
porque no coinciden. Sin embargo, si capitalizas ambos caracteres, obtienes F
y F
Entonces ellos coincidirían. Lo mismo no sería verdad de, digamos, c
y G
El código es eficiente. No es necesario usar mayúsculas en 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í la segunda declaración if
).
La sentencia if
final se usa para ciertos alfabetos (como el georgiano) donde la capitalización es un asunto complicado. Para ser honesto, no sé mucho sobre cómo funciona eso (¡solo confíe en que Oracle sí lo hace!).
¿Alguien puede explicar el siguiente código de String.java
, específicamente por qué hay tres afirmaciones if
(que he marcado //1
, //2
y //3
)?
private static class CaseInsensitiveComparator
implements Comparator<String>, 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;
}
}
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 mayúsculas y minúsculas diferentes.
La simple comprobación de fuerza bruta da algunos resultados. Verifique 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
Así que "İ" y "I" no son iguales entre sí. Ambos caracteres son mayúsculas. Pero comparten la misma letra minúscula: "i" y eso da una razón para tratarlos como los mismos valores en comparación insensible.
En el caso anterior, para la comparación no sensible a mayúsculas y minúsculas, suponga que s1 = "Apple" y s2 = "apple" En este caso ''A''! = ''A'' ya que los valores ASCII de ambos caracteres son diferentes, entonces los caracteres cambian a mayúsculas. y nuevamente se compara, entonces el bucle continúa obteniendo el valor final de n1-n2 = 0, por lo que las cadenas se vuelven iguales. Supongamos que los caracteres no son iguales en toda la segunda verificación
if (c1 != c2) {
return c1 - c2;
}
devuelve la diferencia en el valor ascii de los dos caracteres.
En otra respuesta, la configuración regional predeterminada ya dio un ejemplo de por qué no basta con comparar solo mayúsculas, a saber, la letra ASCII "I" y la capital I con el punto "İ".
Ahora puede que se pregunte, ¿por qué no solo comparan solo con minúsculas en lugar de mayúsculas y minúsculas, si se detectan más casos que en mayúsculas? La respuesta es que no captura más casos, simplemente encuentra diferentes casos.
Tome la letra "ı" ( (char)305
, i sin puntos pequeños) y el ascii "i". Son diferentes, su minúscula es diferente, 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 ("İ" vs. "I") ni sus minúsculas ("i" vs. "ı") coinciden, pero la minúscula de sus mayúsculas es la misma ("I"). Encontré otro caso si este fenómeno, en las letras griegas "ϴ" y "ϑ" (char 1012 y 977).
Por lo tanto, una verdadera comparación que no distingue entre mayúsculas y minúsculas ni siquiera puede comprobar las mayúsculas y las minúsculas de los caracteres originales , sino que debe comprobar las minúsculas de las mayúsculas.
Normalmente, esperaríamos convertir el caso una vez y comparar y terminar con él. Sin embargo, el código convierte el caso dos veces, y la razón se establece 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 casos. Así que tenemos que hacer una última comprobación antes de salir.
Apéndice
El código de regionMatches
tiene algunas diferencias con el código de CaseInsenstiveComparator
, pero esencialmente hace lo mismo. El código completo del método se cita a continuación con el propósito de realizar una 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;
}