Ordina ArrayList di oggetti personalizzati per proprietà


1145

Ho letto dell'ordinamento di ArrayLists usando un comparatore, ma in tutti gli esempi le persone hanno usato compareToche secondo alcune ricerche è un metodo per le stringhe.

Volevo ordinare un ArrayList di oggetti personalizzati in base a una delle loro proprietà: un oggetto Date ( getStartDay()). Normalmente li paragono, item1.getStartDate().before(item2.getStartDate())quindi mi chiedevo se potevo scrivere qualcosa del tipo:

public class CustomComparator {
    public boolean compare(Object object1, Object object2) {
        return object1.getStartDate().before(object2.getStartDate());
    }
}

public class RandomName {
    ...
    Collections.sort(Database.arrayList, new CustomComparator);
    ...
}


2
La risposta di @Yishai in questo post dimostra un uso elegante dell'enum per l'ordinamento personalizzato e l'ordinamento raggruppato (argomenti multipli) utilizzando il concatenamento del comparatore.
gunalmel,

Risposte:


1538

Dal momento che Dateimplementa Comparable, ha un compareTometodo proprio come Stringfa.

Quindi la tua abitudine Comparatorpotrebbe apparire così:

public class CustomComparator implements Comparator<MyObject> {
    @Override
    public int compare(MyObject o1, MyObject o2) {
        return o1.getStartDate().compareTo(o2.getStartDate());
    }
}

Il compare()metodo deve restituire un int, quindi non è possibile restituire direttamente un booleancome previsto comunque.

Il codice di ordinamento sarebbe simile a quello che hai scritto:

Collections.sort(Database.arrayList, new CustomComparator());

Un modo leggermente più breve per scrivere tutto questo, se non hai bisogno di riutilizzare il tuo comparatore, è di scriverlo come una classe anonima in linea:

Collections.sort(Database.arrayList, new Comparator<MyObject>() {
    @Override
    public int compare(MyObject o1, MyObject o2) {
        return o1.getStartDate().compareTo(o2.getStartDate());
    }
});

Da

Ora puoi scrivere l'ultimo esempio in una forma più breve usando un'espressione lambda per Comparator:

Collections.sort(Database.arrayList, 
                        (o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

E Listha un sort(Comparator)metodo, quindi puoi accorciare ulteriormente questo:

Database.arrayList.sort((o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

Questo è un linguaggio così comune che esiste un metodo incorporato per generare un Comparatorper una classe con una Comparablechiave:

Database.arrayList.sort(Comparator.comparing(MyObject::getStartDate));

Tutte queste sono forme equivalenti.


33
+1 per menzionare che dovrebbe tornare inte che è meglio usare Date#compareTo()per questo. Perché questo non sia votato sopra l'altra risposta è al di là di me. Questo collegamento può anche essere utile: Tutorial per l'ordinazione di oggetti su Sun.com .
BalusC,

5
Penso che la risposta migliore dovrebbe includere anche il modo corretto di farlo in Java 8. Collections.sort (elenco, Comparator.comparing (MyObject :: getStartDate)); che legge meglio ed è meno soggetto a errori. È molto semplice scrivere return o1.getStartDate (). CompareTo (o1.getStartDate ());
Kuba,

2
La classe di confronto dovrebbe essere statica :)
Jarmez De La Rocha,

4
@Kuba ancora meglio, usa List.sort().
shmosel,

3
Questa soluzione non funziona su API Android <24. Ragazzi, conoscete una soluzione per questo?
Jim Clermonts,

194

Le classi che hanno un ordinamento naturale (un numero di classe, come esempio) dovrebbero implementare l'interfaccia Comparable, mentre le classi che non hanno un ordinamento naturale (un presidente di classe, come esempio) dovrebbero essere fornite con un comparatore (o un comparatore anonimo classe).

Due esempi:

public class Number implements Comparable<Number> {
    private int value;

    public Number(int value) { this.value = value; }
    public int compareTo(Number anotherInstance) {
        return this.value - anotherInstance.value;
    }
}

public class Chair {
    private int weight;
    private int height;

    public Chair(int weight, int height) {
        this.weight = weight;
        this.height = height;
    }
    /* Omitting getters and setters */
}
class ChairWeightComparator implements Comparator<Chair> {
    public int compare(Chair chair1, Chair chair2) {
        return chair1.getWeight() - chair2.getWeight();
    }
}
class ChairHeightComparator implements Comparator<Chair> {
    public int compare(Chair chair1, Chair chair2) {
        return chair1.getHeight() - chair2.getHeight();
    }
}

Uso:

List<Number> numbers = new ArrayList<Number>();
...
Collections.sort(numbers);

List<Chair> chairs = new ArrayList<Chair>();
// Sort by weight:
Collections.sort(chairs, new ChairWeightComparator());
// Sort by height:
Collections.sort(chairs, new ChairHeightComparator());

// You can also create anonymous comparators;
// Sort by color:
Collections.sort(chairs, new Comparator<Chair>() {
    public int compare(Chair chair1, Chair chair2) {
        ...
    }
});

Ci ho provato, ma quando voglio accedere alla classe di comparazione ChairWeightComparator all'esterno di qualsiasi altra classe non riesco ad accedere a questa classe (ovviamente non perché non è pubblica). Devo creare una nuova classe pubblica ChairWeightComparator in un file separato? - Sono davvero il primo a provarlo dopo 3 anni o mi sono perso sth?
user387184

@ user387184: rendilo pubblico e inseriscilo nel suo file (preferibilmente anche nel suo pacchetto) e sarai in grado di usarlo ovunque nel tuo progetto. Non c'è bisogno di creare una classe aggiuntiva!
Björn,

intendi creare un nuovo file, non una classe e inserire il codice: "class ChairWeightComparator implementa Comparator <Chair> {...."?
user387184

@ user387184, esattamente - ma con la parola chiave publicdi fronte class.
Björn,

157

Per l'ordinamento ArrayListè possibile utilizzare il seguente frammento di codice:

Collections.sort(studList, new Comparator<Student>(){
    public int compare(Student s1, Student s2) {
        return s1.getFirstName().compareToIgnoreCase(s2.getFirstName());
    }
});


Che tipo, ma dà il doppio valore per ogni elemento
Sam

44

Si, puoi. Esistono due opzioni con elementi di confronto, l' interfaccia Comparable e l' interfaccia Comparator .

Entrambe queste interfacce consentono comportamenti diversi. Comparable ti permette di far agire l'oggetto come hai appena descritto Strings (in effetti String implementa Comparable). Il secondo, Comparatore, ti consente di fare ciò che stai chiedendo di fare. Lo faresti così:

Collections.sort(myArrayList, new MyComparator());

Ciò farà sì che il metodo Collections.sort utilizzi il tuo comparatore per il suo meccanismo di ordinamento. Se gli oggetti in ArrayList implementano comparabili, puoi invece fare qualcosa del genere:

Collections.sort(myArrayList);

La classe Collezioni contiene numerosi strumenti utili e comuni.


42

Espressione lambda JAVA 8

Collections.sort(studList, (Student s1, Student s2) ->{
        return s1.getFirstName().compareToIgnoreCase(s2.getFirstName());
});

O

Comparator<Student> c = (s1, s2) -> s1.firstName.compareTo(s2.firstName);
studList.sort(c)

3
OppureCollections.sort(studList, Comparator.comparing(Student::getFirstName));
Holger,

5
.. oppurestudList.sort(Comparator.comparing(Student::getFirstName));
Alexis C.

L'ordinamento sarà sempre crescente nel tuo caso. Mi occupo anche dell'ordinamento nel mio esempio. Grazie signori.
Sorter

33

Con Java 8 puoi utilizzare un riferimento di metodo per il tuo comparatore:

import static java.util.Comparator.comparing;

Collections.sort(list, comparing(MyObject::getStartDate));

@ user387184 sfortunatamente Android non supporta Java 8, anche se potrebbe esserci una soluzione alternativa (non l'ho provato).
Assylias,

14

Poiché le tecnologie appaiono ogni giorno, la risposta cambierà nel tempo. Ho dato un'occhiata a LambdaJ e sembra molto interessante.

Puoi provare a risolvere queste attività con LambdaJ . Puoi trovarlo qui: http://code.google.com/p/lambdaj/

Ecco un esempio:

Ordina Iterativo

List<Person> sortedByAgePersons = new ArrayList<Person>(persons);
Collections.sort(sortedByAgePersons, new Comparator<Person>() {
        public int compare(Person p1, Person p2) {
           return Integer.valueOf(p1.getAge()).compareTo(p2.getAge());
        }
});

Ordina con lambda

List<Person> sortedByAgePersons = sort(persons, on(Person.class).getAge()); 

Certo, avere questo tipo di bellezza influisce sulla performance (in media 2 volte), ma riesci a trovare un codice più leggibile?


Che tipo, ma dà doppio valore per ogni elemento come evitarlo
Sam

@Sam, non dovrebbe ... funziona come previsto. A meno che tu non stia utilizzando una nuova versione con un bug, che ti consiglio di postare nel forum. Comunque, questa risposta è stata inviata prima di java 8, se la usi, allora sarà molto meglio che usare lambdaj
Federico Piazza

Ho dovuto rimuovere anche gli elementi in un ciclo foreach in altro modo mi dà il doppio di ogni contenuto.
Sam,

13
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;

public class test {

public static class Person {
    public String name;
    public int id;
    public Date hireDate;

    public Person(String iname, int iid, Date ihireDate) {
        name = iname;
        id = iid;
        hireDate = ihireDate;
    }

    public String toString() {
        return name + " " + id + " " + hireDate.toString();
    }

    // Comparator
    public static class CompId implements Comparator<Person> {
        @Override
        public int compare(Person arg0, Person arg1) {
            return arg0.id - arg1.id;
        }
    }

    public static class CompDate implements Comparator<Person> {
        private int mod = 1;
        public CompDate(boolean desc) {
            if (desc) mod =-1;
        }
        @Override
        public int compare(Person arg0, Person arg1) {
            return mod*arg0.hireDate.compareTo(arg1.hireDate);
        }
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    SimpleDateFormat df = new SimpleDateFormat("mm-dd-yyyy");
    ArrayList<Person> people;
    people = new ArrayList<Person>();
    try {
        people.add(new Person("Joe", 92422, df.parse("12-12-2010")));
        people.add(new Person("Joef", 24122, df.parse("1-12-2010")));
        people.add(new Person("Joee", 24922, df.parse("12-2-2010")));
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    Collections.sort(people, new Person.CompId());
    System.out.println("BY ID");
    for (Person p : people) {
        System.out.println(p.toString());
    }

    Collections.sort(people, new Person.CompDate(false));
    System.out.println("BY Date asc");
    for (Person p : people) {
        System.out.println(p.toString());
    }
    Collections.sort(people, new Person.CompDate(true));
    System.out.println("BY Date desc");
    for (Person p : people) {
        System.out.println(p.toString());
    }

}

}

7
Benvenuto in StackOverflow. A questa domanda è stata data risposta qualche tempo fa. Prima di resuscitare vecchi thread, assicurati che la tua risposta aggiunga qualcosa di significativo al thread.
Leigh

1
Aggiungi una spiegazione alla tua risposta.
Arashsoft,

Basta compilare ed eseguire. Il codice è il commento e la spiegazione.
CharlesW,

9

Il miglior modo semplice con JAVA 8 è per l'ordinamento alfabetico inglese

Implementazione di classe

public class NewspaperClass implements Comparable<NewspaperClass>{
   public String name;

   @Override
   public int compareTo(NewspaperClass another) {
      return name.compareTo(another.name);
   }
}

Ordinare

  Collections.sort(Your List);

Se vuoi ordinare l'alfabeto che contiene caratteri non inglesi puoi usare Locale ... Sotto il codice usa l'ordinamento dei caratteri turchi ...

Implementazione di classe

public class NewspaperClass implements Comparator<NewspaperClass> {
   public String name;
   public Boolean isUserNewspaper=false;
   private Collator trCollator = Collator.getInstance(new Locale("tr_TR"));



   @Override
   public int compare(NewspaperClass lhs, NewspaperClass rhs) {
      trCollator.setStrength(Collator.PRIMARY);
      return trCollator.compare(lhs.name,rhs.name);
   }
}

Ordinare

Collections.sort(your array list,new NewspaperClass());

9

Riferimenti su funzioni e metodi

Il Collections.sortmetodo può ordinare un Listusando un Comparatorpassaggio. Ciò Comparatorpuò essere implementato usando il Comparator.comparingmetodo in cui è possibile passare un riferimento al metodo come necessario Function. Fortunatamente, il codice attuale è molto più semplice e più breve di questa descrizione.

Per Java 8:

Collections.sort(list, comparing(ClassName::getName));

o

Collections.sort(list, comparing(ClassName::getName).reversed());

Un altro modo è

Collections.sort(list, comparing(ClassName::getName, Comparator.nullsLast(Comparator.naturalOrder())));

1
La chiamata richiede il livello API 24
akshay bhange


6

Java 8 Lambda accorcia il tipo.

Collections.sort(stdList, (o1, o2) -> o1.getName().compareTo(o2.getName()));

2
Collections.sort(stdList, Comparator.comparing(SomeClass::getName));
bcsb1001,


5

Sì, è possibile ad esempio in questa risposta che ordino per la proprietà vdella classeIndexValue

    // Sorting by property v using a custom comparator.
    Arrays.sort( array, new Comparator<IndexValue>(){
        public int compare( IndexValue a, IndexValue b ){
            return a.v - b.v;
        }
    });

Se noti qui sto creando una classe interna anonima (che è Java per le chiusure) e la passo direttamente al sortmetodo della classeArrays

Il tuo oggetto può anche implementare Comparable(è quello che fa String e la maggior parte delle librerie di base in Java) ma questo definirebbe il "naturale ordinamento" della classe stessa e non ti consentirà di inserirne di nuove.


1
... ma con cui puoi semplicemente sostituire Comparator:)
BalusC,

5

Ho trovato la maggior parte se non tutte queste risposte si basano sulla classe sottostante (Object) per implementare comparabili o per avere un'interfaccia helper comparabile.

Non con la mia soluzione! Il codice seguente consente di confrontare il campo dell'oggetto conoscendone il nome della stringa. È possibile modificarlo facilmente per non utilizzare il nome, ma è necessario esporlo o costruire uno degli oggetti con cui si desidera confrontare.

Collections.sort(anArrayListOfSomeObjectPerhapsUsersOrSomething, new ReflectiveComparator(). new ListComparator("name"));

public class ReflectiveComparator {
    public class FieldComparator implements Comparator<Object> {
        private String fieldName;

        public FieldComparator(String fieldName){
            this.fieldName = fieldName;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        public int compare(Object object1, Object object2) {
            try {
                Field field = object1.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);

                Comparable object1FieldValue = (Comparable) field.get(object1);
                Comparable object2FieldValue = (Comparable) field.get(object2);

                return object1FieldValue.compareTo(object2FieldValue);
            }catch (Exception e){}

            return 0;
        }
    }

    public class ListComparator implements Comparator<Object> {
        private String fieldName;

        public ListComparator(String fieldName) {
            this.fieldName = fieldName;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        public int compare(Object object1, Object object2) {
            try {
                Field field = object1.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                Comparable o1FieldValue = (Comparable) field.get(object1);
                Comparable o2FieldValue = (Comparable) field.get(object2);

                if (o1FieldValue == null){ return -1;}
                if (o2FieldValue == null){ return 1;}
                return o1FieldValue.compareTo(o2FieldValue);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException("Field doesn't exist", e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Field inaccessible", e);
            }
        }
    }
}

5

Puoi provare Guava Ordering :

Function<Item, Date> getStartDate = new Function<Item, Date>() {
    public Date apply(Item item) {
        return item.getStartDate();
    }
};

List<Item> orderedItems = Ordering.natural().onResultOf(getStartDate).
                          sortedCopy(items);

5

Puoi ordinare usando java 8

yourList.sort(Comparator.comparing(Classname::getName));

or

yourList.stream().forEach(a -> a.getBObjects().sort(Comparator.comparing(Classname::getValue)));

3

Questi frammenti di codice potrebbero essere utili. Se vuoi ordinare un oggetto nel mio caso, voglio ordinare per VolumeName:

public List<Volume> getSortedVolumes() throws SystemException {
    List<Volume> volumes = VolumeLocalServiceUtil.getAllVolumes();
    Collections.sort(volumes, new Comparator<Volume>() {
        public int compare(Volume o1, Volume o2) {
            Volume p1 = (Volume) o1;
            Volume p2 = (Volume) o2;
            return p1.getVolumeName().compareToIgnoreCase(
                    p2.getVolumeName());
        }
    });
    return volumes;
}

Questo funziona Lo uso nel mio jsp.


3

Con questa libreria qui è possibile ordinare l'elenco di oggetti personalizzati su più colonne. La libreria utilizza le funzionalità della versione 8.0. Il campione è disponibile anche lì. Ecco un esempio da fare

SortKeys sortKeys = new SortKeys();
sortKeys.addField("firstName")
            .addField("age", true); // This (true) will sort the age descending

// Other ways to specify a property to the sorter are
//      .addField("lastName", String.class);
//      .addField("dob", Date.class, true);

// Instantiate a ListSorter
ListSorter listSorter = new ListSorter();

// Pass the data to sort (listToSort) and the "by keys" to sort (sortKeys)
List sortedList = (List<Person>) listSorter.sortList(listToSort, sortKeys);

3

Puoi dare un'occhiata a questa presentazione al Forum Java di Stoccarda, Germania, nel 2016.

Solo poche diapositive usano la lingua tedesca, il 99% del contenuto è un codice sorgente Java "inglese"; piace

someCollection.sort(
  OurCustomComparator
    .comparing(Person::getName)
    .thenComparing(Person::getId)
);

dove OurCustomComparator sta usando i metodi predefiniti (e altre idee interessanti). Come mostrato, porta a un codice molto conciso per scegliere un metodo getter per l'ordinamento; e concatenamento (o inversione) super semplice di criteri di ordinamento.

Se ti piace java8, troverai un sacco di materiale lì per iniziare.


3

Novità poiché 1.8 è un metodo List.sort () invece di utilizzare Collection.sort () in modo da chiamare direttamente mylistcontainer.sort ()

Ecco uno snippet di codice che dimostra la funzionalità List.sort ():

List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Fruit("Kiwi","green",40));
fruits.add(new Fruit("Banana","yellow",100));
fruits.add(new Fruit("Apple","mixed green,red",120));
fruits.add(new Fruit("Cherry","red",10));

// a) using an existing compareto() method
fruits.sort((Fruit f1,Fruit f2) -> f1.getFruitName().compareTo(f2.getFruitName()));
System.out.println("Using String.compareTo(): " + fruits);
//Using String.compareTo(): [Apple is: mixed green,red, Banana is: yellow, Cherry is: red, Kiwi is: green]

// b) Using a comparable class
fruits.sort((Fruit f1,Fruit f2) -> f1.compareTo(f2));  
System.out.println("Using a Comparable Fruit class (sort by color): " + fruits);
// Using a Comparable Fruit class (sort by color): [Kiwi is green, Apple is: mixed green,red, Cherry is: red, Banana is: yellow]

La classe Fruit è:

public class Fruit implements Comparable<Fruit>
{
    private String name;
    private String color;
    private int quantity;

    public Fruit(String name,String color,int quantity)
    { this.name = name; this.color = color; this.quantity = quantity; }

    public String getFruitName() { return name; }        
    public String getColor() { return color; }  
    public int getQuantity() { return quantity; }

    @Override public final int compareTo(Fruit f) // sorting the color
    {
        return this.color.compareTo(f.color);
    }     
    @Override public String toString()
    {   
        return (name + " is: " + color);
    }
} // end of Fruit class   


1

Preferisco questo processo:

public class SortUtil
{    
    public static <T> List<T> sort(List<T> list, String sortByProperty)
    {
            Collections.sort(list, new BeanComparator(sortByProperty));
            return list;
    }
}

List<T> sortedList = SortUtil<T>.sort(unsortedList, "startDate");

Se il tuo elenco di oggetti ha una proprietà chiamata startDate, chiamala ripetutamente. Puoi persino incatenarli startDate.time.

Ciò richiede l'oggetto ad essere Comparableche significa che è necessario un compareTo, equalse hashCodeimplementazione.

Sì, potrebbe essere più veloce ... Ma ora non è necessario creare un nuovo comparatore per ogni tipo di ordinamento. Se puoi risparmiare tempo di sviluppo e rinunciare al tempo di esecuzione, potresti andare con questo.


3
1, questa risposta è stata data 2 ore prima anche con il codice di lavoro fornito. Non è necessario ripubblicare la stessa soluzione e ingombrare il forum soprattutto perché BeanComparator non è una classe standard, quindi non è davvero una soluzione se il poster non sa di cosa stai parlando. Se ti piace il suggerimento originale puoi votarlo e aggiungere un commento se lo desideri.
Camickr,

0

Utilizzando Java 8 l'uso può definire Comparatorin una riga usandoComparator.comparing()

Utilizzare uno dei seguenti modi:

Opzione 1:

listToBeSorted.sort(Comparator.comparing(CustomObject::getStartDate));

Opzione 2:

Collections.sort(listToBeSorted, Comparator.comparing(CustomObject::getStartDate));

1
Sembra essere un duplicato di questa risposta di 3 anni prima.
Basil Bourque,

1
Non esattamente, solo l'opzione 2 qui è simile all'opzione 1 nella risposta citata. Sento che due risposte diverse possono avere almeno alcune somiglianze se rispondono alla stessa domanda.
Sahil Chhabra,

0

La tua classe personalizzata può implementare l'interfaccia "Comparable", che richiede un'implementazione del metodo CompareTo. Nel metodo CompareTo, puoi quindi definire cosa significa che un oggetto è minore o maggiore dell'altro oggetto. Quindi nel tuo esempio, può assomigliare a questo:

public class MyCustomClass implements Comparable<MyCustomClass>{

..........

 @Override
public int compareTo(MyCustomClass a) {
    if(this.getStartDate().before(a.getStartDate())){
        return -1;
    }else if(a.getStartDate().before(this.getStartDate())){
        return 1;
    }else {
        return 0;
    }
}

Un numero negativo indica che questo è più piccolo dell'oggetto confrontato. Un numero positivo indica che questo è maggiore di quello rispetto all'oggetto e uno Zero indica che gli oggetti sono uguali.

È quindi possibile utilizzare collections.sort (myList) per ordinare l'elenco senza dover inserire un comparatore. Questo metodo ha anche il vantaggio di disporre le cose automaticamente se si utilizzano strutture di dati di una raccolta ordinata come TreeSet o TreeMap.

Puoi leggere questo articolo se desideri saperne di più sull'interfaccia comparabile (divulgazione: sono l'autore;)) https://nullbeans.com/the-java-comparable-interface-automatic-sort-of-collections/


0

È inoltre possibile utilizzare Springs PropertyComparator se si dispone solo di un percorso della proprietà String per la proprietà (nidificata) che si desidera ordinare:

List<SomeObject> list = ...;
PropertyComparator<HitWithInfo> propertyComparator = new PropertyComparator<>(
    "property.nested.myProperty", false, true);
list.sort(propertyComparator);

Lo svantaggio è che questo comparatore ignora silenziosamente le proprietà che non esistono o non sono accessibili e le gestisce come valore null per il confronto. Ciò significa che è necessario testare attentamente un simile comparatore o convalidare in qualche modo l'esistenza del percorso della proprietà.


0

usando l'API java-8 stream puoi ordinare un ArrayList:

 Comparator<Person> birthdayComparator = Comparator.comparing(Person::getBirthday);
 List<Person> sortedList = list.stream().sorted(birthdayComparator).collect(toList());

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.