Cómo usar el Comparador en Java para ordenar

Aprendí cómo usar el comparable, pero estoy teniendo dificultades con el Comparador. Tengo un error en mi código:

Exception in thread "main" java.lang.ClassCastException: New.People cannot be cast to java.lang.Comparable at java.util.Arrays.mergeSort(Unknown Source) at java.util.Arrays.sort(Unknown Source) at java.util.Collections.sort(Unknown Source) at New.TestPeople.main(TestPeople.java:18) 

Aquí está mi código:

 import java.util.Comparator; public class People implements Comparator { private int id; private String info; private double price; public People(int newid, String newinfo, double newprice) { setid(newid); setinfo(newinfo); setprice(newprice); } public int getid() { return id; } public void setid(int id) { this.id = id; } public String getinfo() { return info; } public void setinfo(String info) { this.info = info; } public double getprice() { return price; } public void setprice(double price) { this.price = price; } public int compare(Object obj1, Object obj2) { Integer p1 = ((People) obj1).getid(); Integer p2 = ((People) obj2).getid(); if (p1 > p2) { return 1; } else if (p1 < p2){ return -1; } else { return 0; } } } 
 import java.util.ArrayList; import java.util.Collections; public class TestPeople { public static void main(String[] args) { ArrayList peps = new ArrayList(); peps.add(new People(123, "M", 14.25)); peps.add(new People(234, "M", 6.21)); peps.add(new People(362, "F", 9.23)); peps.add(new People(111, "M", 65.99)); peps.add(new People(535, "F", 9.23)); Collections.sort(peps); for (int i = 0; i < peps.size(); i++){ System.out.println(peps.get(i)); } } } 

Creo que tiene que ver algo con el casting en el método de comparación, pero estaba jugando con eso y todavía no podía encontrar la solución

Hay un par de cosas incómodas con su clase de ejemplo:

  • se llama Gente mientras tiene un price e info (más algo para objetos, no personas);
  • al nombrar una clase como un plural de algo, sugiere que es una abstracción de más de una cosa.

De todos modos, aquí hay una demostración de cómo usar un Comparator :

 public class ComparatorDemo { public static void main(String[] args) { List people = Arrays.asList( new Person("Joe", 24), new Person("Pete", 18), new Person("Chris", 21) ); Collections.sort(people, new LexicographicComparator()); System.out.println(people); Collections.sort(people, new AgeComparator()); System.out.println(people); } } class LexicographicComparator implements Comparator { @Override public int compare(Person a, Person b) { return a.name.compareToIgnoreCase(b.name); } } class AgeComparator implements Comparator { @Override public int compare(Person a, Person b) { return a.age < b.age ? -1 : a.age == b.age ? 0 : 1; } } class Person { String name; int age; Person(String n, int a) { name = n; age = a; } @Override public String toString() { return String.format("{name=%s, age=%d}", name, age); } } 

EDITAR

Y una demostración Java 8 equivalente se vería así:

 public class ComparatorDemo { public static void main(String[] args) { List people = Arrays.asList( new Person("Joe", 24), new Person("Pete", 18), new Person("Chris", 21) ); Collections.sort(people, (a, b) -> a.name.compareToIgnoreCase(b.name)); System.out.println(people); Collections.sort(people, (a, b) -> a.age < b.age ? -1 : a.age == b.age ? 0 : 1); System.out.println(people); } } 

Aquí hay una plantilla súper corta para hacer la clasificación de inmediato:

 Collections.sort(people,new Comparator(){ @Override public int compare(final Person lhs,Person rhs) { //TODO return 1 if rhs should be before lhs // return -1 if lhs should be before rhs // return 0 otherwise } }); 

si es difícil de recordar, trate de recordar que es similar (en términos del signo del número) a:

  lhs-rhs 

Eso es en caso de que desee ordenar en orden ascendente: del número más pequeño al número más grande.

Use People implements Comparable lugar; esto define el orden natural para las People .

También se puede definir un Comparator , pero la People implements Comparator no es la forma correcta de hacer las cosas.

Las dos sobrecargas para Collections.sort son diferentes:

  • > void sort(List list)
    • Ordena Objetos Comparable usando su ordenamiento natural
  • void sort(List list, Comparator< ? super T> c)
    • Ordena lo que sea usando un Comparator compatible

Estás confundiendo a los dos al tratar de ordenar un Comparator (que es otra vez por qué no tiene sentido que la Person implements Comparator ). Nuevamente, para usar Collections.sort , necesita que uno de estos sea verdadero:

  • El tipo debe ser Comparable (use el orden 1-arg)
  • Se debe proporcionar un Comparator para el tipo (use el sort 2-args)

Preguntas relacionadas

  • Cuándo usar Comparable vs Comparator
  • Ordenando una ArrayList de contactos

Además, no use tipos sin procesar en el nuevo código . Los tipos crudos no son seguros, y solo se proporcionan por compatibilidad.

Es decir, en lugar de esto:

 ArrayList peps = new ArrayList(); // BAD!!! No generic safety! 

deberías haber usado la statement genérica typesafe así:

 List peps = new ArrayList(); // GOOD!!! 

¡Entonces descubrirás que tu código ni siquiera se comstack! Eso sería algo bueno, porque SI hay algo mal con el código (la Person no implements Comparable ), pero debido a que usaste el tipo sin procesar, el comstackdor no verificó esto y en su lugar obtienes una ClassCastException en ejecución -¡¡¡hora!!!

Esto debería convencerlo de que siempre use tipos generics seguros en un nuevo código. Siempre.

Ver también

  • ¿Qué es un tipo crudo y por qué no deberíamos usarlo?

En aras de la exhaustividad, aquí hay un método simple de compare una sola línea:

 Collections.sort(people,new Comparator() { @Override public int compare(Person lhs,Person rhs) { return Integer.signum(lhs.getId()-rhs.getId()); } } 

Java 8 agregó una nueva forma de hacer Comparadores que reduce la cantidad de código que tiene que escribir, Comparator.comparing . También echa un vistazo a Comparator.reversed

Aquí hay una muestra

 import org.junit.Test; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import static org.junit.Assert.assertTrue; public class ComparatorTest { @Test public void test() { List peopleList = new ArrayList<>(); peopleList.add(new Person("A", 1000)); peopleList.add(new Person("B", 1)); peopleList.add(new Person("C", 50)); peopleList.add(new Person("Z", 500)); //sort by name, ascending peopleList.sort(Comparator.comparing(Person::getName)); assertTrue(peopleList.get(0).getName().equals("A")); assertTrue(peopleList.get(peopleList.size() - 1).getName().equals("Z")); //sort by name, descending peopleList.sort(Comparator.comparing(Person::getName).reversed()); assertTrue(peopleList.get(0).getName().equals("Z")); assertTrue(peopleList.get(peopleList.size() - 1).getName().equals("A")); //sort by age, ascending peopleList.sort(Comparator.comparing(Person::getAge)); assertTrue(peopleList.get(0).getAge() == 1); assertTrue(peopleList.get(peopleList.size() - 1).getAge() == 1000); //sort by age, descending peopleList.sort(Comparator.comparing(Person::getAge).reversed()); assertTrue(peopleList.get(0).getAge() == 1000); assertTrue(peopleList.get(peopleList.size() - 1).getAge() == 1); } class Person { String name; int age; Person(String n, int a) { name = n; age = a; } public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } } } 

Desea implementar Comparable, no comparador. Necesita implementar el método compareTo. Estás cerca sin embargo. El comparador es una rutina de comparación de “terceros”. Comparable es que este objeto se puede comparar con otro.

 public int compareTo(Object obj1) { People that = (People)obj1; Integer p1 = this.getId(); Integer p2 = that.getid(); if (p1 > p2 ){ return 1; } else if (p1 < p2){ return -1; } else return 0; } 

Tenga en cuenta que es posible que desee comprobar null aquí para getId ... solo en caso.

Aquí hay un ejemplo de un Comparador que funcionará para cualquier método zero arg que arroje un Comparable. ¿Algo como esto existe en un jdk o biblioteca?

 import java.lang.reflect.Method; import java.util.Comparator; public class NamedMethodComparator implements Comparator { // // instance variables // private String methodName; private boolean isAsc; // // constructor // public NamedMethodComparator(String methodName, boolean isAsc) { this.methodName = methodName; this.isAsc = isAsc; } /** * Method to compare two objects using the method named in the constructor. */ @Override public int compare(Object obj1, Object obj2) { Comparable comp1 = getValue(obj1, methodName); Comparable comp2 = getValue(obj2, methodName); if (isAsc) { return comp1.compareTo(comp2); } else { return comp2.compareTo(comp1); } } // // implementation // private Comparable getValue(Object obj, String methodName) { Method method = getMethod(obj, methodName); Comparable comp = getValue(obj, method); return comp; } private Method getMethod(Object obj, String methodName) { try { Class[] signature = {}; Method method = obj.getClass().getMethod(methodName, signature); return method; } catch (Exception exp) { throw new RuntimeException(exp); } } private Comparable getValue(Object obj, Method method) { Object[] args = {}; try { Object rtn = method.invoke(obj, args); Comparable comp = (Comparable) rtn; return comp; } catch (Exception exp) { throw new RuntimeException(exp); } } } 
 public static Comparator JobEndTimeComparator = new Comparator() { public int compare(JobSet j1, JobSet j2) { int cost1 = j1.cost; int cost2 = j2.cost; return cost1-cost2; } }; 

La solución se puede optimizar de la siguiente manera: en primer lugar, use una clase interna privada ya que el scope de los campos será la clase adjunta TestPeople, así como la implementación de la clase Las personas no se verán expuestas al mundo exterior. Esto se puede entender en términos de crear una API que espera una lista ordenada de personas. En segundo lugar, usar la expresión Lamba (java 8) que reduce el código, por lo tanto, el esfuerzo de desarrollo

Por lo tanto, el código sería el siguiente:

 import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class TestPeople { public static void main(String[] args) { ArrayList peps = new ArrayList<>();// Be specific, to avoid // classCast Exception TestPeople test = new TestPeople(); peps.add(test.new People(123, "M", 14.25)); peps.add(test.new People(234, "M", 6.21)); peps.add(test.new People(362, "F", 9.23)); peps.add(test.new People(111, "M", 65.99)); peps.add(test.new People(535, "F", 9.23)); /* * Collections.sort(peps); * * for (int i = 0; i < peps.size(); i++){ * System.out.println(peps.get(i)); } */ // The above code can be replaced by followin: peps.sort((People p1, People p2) -> p1.getid() - p2.getid()); peps.forEach((p) -> System.out.println(" " + p.toString())); } private class People { private int id; @Override public String toString() { return "People [id=" + id + ", info=" + info + ", price=" + price + "]"; } private String info; private double price; public People(int newid, String newinfo, double newprice) { setid(newid); setinfo(newinfo); setprice(newprice); } public int getid() { return id; } public void setid(int id) { this.id = id; } public String getinfo() { return info; } public void setinfo(String info) { this.info = info; } public double getprice() { return price; } public void setprice(double price) { this.price = price; } } } 

Debes usar el método de ordenación (peps, new People ()) sobrecargado

 import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class Test { public static void main(String[] args) { List peps = new ArrayList<>(); peps.add(new People(123, "M", 14.25)); peps.add(new People(234, "M", 6.21)); peps.add(new People(362, "F", 9.23)); peps.add(new People(111, "M", 65.99)); peps.add(new People(535, "F", 9.23)); Collections.sort(peps, new People().new ComparatorId()); for (int i = 0; i < peps.size(); i++) { System.out.println(peps.get(i)); } } } class People { private int id; private String info; private double price; public People() { } public People(int newid, String newinfo, double newprice) { setid(newid); setinfo(newinfo); setprice(newprice); } public int getid() { return id; } public void setid(int id) { this.id = id; } public String getinfo() { return info; } public void setinfo(String info) { this.info = info; } public double getprice() { return price; } public void setprice(double price) { this.price = price; } class ComparatorId implements Comparator { @Override public int compare(People obj1, People obj2) { Integer p1 = obj1.getid(); Integer p2 = obj2.getid(); if (p1 > p2) { return 1; } else if (p1 < p2){ return -1; } else { return 0; } } } }