metodo - Java, ¿por qué está implícito que los objetos son iguales si compareTo() devuelve 0?
implementar compare to java (6)
Tengamos una Person
clase. La persona tiene nombre y altura.
Equals y hashCode () solo tienen en cuenta el nombre. La persona es comparable (o implementamos un comparador para ella, no importa cuál). Las personas se comparan por altura.
Parece razonable esperar una situación en la que dos personas diferentes pueden tener la misma altura, pero por ejemplo. TreeSet se comporta como comapareTo () == 0 significa iguales, no simplemente del mismo tamaño.
Para evitar esto, la comparación puede mirar de otra manera si el tamaño es el mismo, pero luego no se puede usar para detectar objetos del mismo tamaño.
Ejemplo:
import java.util.Comparator;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
public class Person implements Comparable<Person> {
private final String name;
private int height;
public Person(String name,
int height) {
this.name = name;
this.height = height;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
@Override
public int compareTo(Person o) {
return Integer.compare(height, o.height);
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Person other = (Person) obj;
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
public int hashCode() {
int hash = 5;
hash = 13 * hash + Objects.hashCode(this.name);
return hash;
}
public String toString() {
return "Person{" + name + ", height = " + height + ''}'';
}
public static class PComparator1 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
return o1.compareTo(o2);
}
}
public static class PComparator2 implements Comparator<Person> {
@Override
public int compare(Person o1,
Person o2) {
int r = Integer.compare(o1.height, o2.height);
return r == 0 ? o1.name.compareTo(o2.name) : r;
}
}
public static void test(Set<Person> ps) {
ps.add(new Person("Ann", 150));
ps.add(new Person("Jane", 150));
ps.add(new Person("John", 180));
System.out.println(ps.getClass().getName());
for (Person p : ps) {
System.out.println(" " + p);
}
}
public static void main(String[] args) {
test(new HashSet<Person>());
test(new TreeSet<Person>());
test(new TreeSet<>(new PComparator1()));
test(new TreeSet<>(new PComparator2()));
}
}
resultado:
java.util.HashSet
Person{Ann, height = 150}
Person{John, height = 180}
Person{Jane, height = 150}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{John, height = 180}
java.util.TreeSet
Person{Ann, height = 150}
Person{Jane, height = 150}
Person{John, height = 180}
¿Tienes idea de por qué es así?
Cuando le da a la Persona un Comparador que compara instancias en el atributo de altura de la Persona, realmente significa que dos instancias de la Persona son iguales si tienen la misma altura. Tendrá que hacer un Comparador que sea específico para la clase Persona.
Extracto del javadoc java.util.SortedSet
:
Tenga en cuenta que la ordenación mantenida por un conjunto ordenado (ya sea que se proporcione o no un comparador explícito) debe ser coherente con iguales si el conjunto ordenado debe implementar correctamente la interfaz de conjunto. (Consulte la Interfaz comparable o la Interfaz de comparador para obtener una definición precisa de consistente con iguales.) Esto se debe a que la interfaz de conjunto se define en términos de la operación de igual a igual, pero un conjunto clasificado realiza todas las comparaciones de elementos utilizando su método compareTo (o compare) , por lo que dos elementos que se consideran iguales por este método son, desde el punto de vista del conjunto ordenado, iguales. El comportamiento de un conjunto ordenado está bien definido incluso si su orden es inconsistente con los iguales; simplemente no cumple con el contrato general de la interfaz Set.
Por lo tanto, en otras palabras, SortedSet
rompe (o "extiende") los contratos generales para Object.equals()
y Comparable.compareTo
. Ver el contrato de compareTo
:
Se recomienda encarecidamente, pero no es estrictamente obligatorio que (x.compareTo (y) == 0) == (x.equals (y)). En términos generales, cualquier clase que implemente la interfaz Comparable y viole esta condición debe indicar claramente este hecho. El lenguaje recomendado es "Nota: esta clase tiene un orden natural que es inconsistente con los iguales".
Se recomienda que compareTo
solo devuelva 0
, si una llamada a equals
en los mismos objetos devolvería true
:
El orden natural para una clase C se dice que es consistente con igual si y solo si e1.compareTo (e2) == 0 tiene el mismo valor booleano que e1.equals (e2) para cada e1 y e2 de la clase C. Tenga en cuenta que null no es una instancia de ninguna clase, y e.compareTo (null) debería lanzar una NullPointerException aunque e.equals (null) devuelva false.
(Desde el JDK 1.6 Javadocs )
puedes arreglarlo usando el nombre para otra comparación cuando las alturas son iguales
@Override
public int compareTo(Person o) {
if(height == o.height)return name.compareTo(o.name);
return Integer.compare(height, o.height);
}
ya que los nombres son únicos, esto solo devolverá 0 si this.equals(o)
es this.equals(o)
TreeSet
no funciona con códigos hash e igualdad, solo funciona sobre la base del comparador que le das. Tenga en cuenta que el Javadoc dice:
Tenga en cuenta que la ordenación mantenida por un conjunto (ya sea que se proporcione o no un comparador explícito) debe ser coherente con iguales si es para implementar correctamente la interfaz del conjunto. (Consulte Comparable o Comparator para una definición precisa de consistente con iguales). Esto se debe a que la interfaz de conjunto se define en términos de la operación de iguales, pero una instancia de TreeSet realiza todas las comparaciones de elementos utilizando su método compareTo (o compare), por lo que dos Los elementos que se consideran iguales por este método son, desde el punto de vista del conjunto, iguales. El comportamiento de un conjunto está bien definido incluso si su orden es inconsistente con los iguales; simplemente no cumple con el contrato general de la interfaz Set.
En su caso, su comparación * no es consistente con equals
, por lo que su conjunto no obedece el contrato general de Set
.
¿Por qué no simplemente agregar más aspectos a la comparación para que solo los elementos iguales se comparen con un resultado de 0?
Se recomienda encarecidamente, pero no es estrictamente obligatorio que (x.compareTo(y)==0) == (x.equals(y))
[ 1 ]
Por lo tanto, está bien que se compare con un criterio diferente al utilizado en los equals
dado que lo documenta.
Sin embargo, sería mejor si comparara con los mismos criterios y, si fuera necesario, proporcione un comparador personalizado que funcione con los nuevos criterios (altura en el caso de la persona)