Sort a List in java that contains null values

2

Good morning!

In my List , when I do the sort order I would like the null values to last.

I'll show you an example to get clearer:

Code output (in this case the word would be the letter after the number):

SEM ORDENAÇÃO:
10c 2018-01-01
11b null
12a 2018-01-02

ORDENADO PELO NÚMERO:  
10c 2018-01-01
11b null
12a 2018-01-02

ORDENADO PELA PALAVRA: 
12a 2018-01-02 
11b null 
10c 2018-01-01

ORDENADO PELA DATA: 
10c 2018-01-01 
11b null 
12a 2018-01-02

You may notice that in ordering by date the null value was before a non-null value, and I would like the null value to last. Follow the source below:

Class main:

public class Main 
{
    public static void main (String[] args)
    {
        Objeto o1 = new Objeto();
        Objeto o2 = new Objeto();
        Objeto o3 = new Objeto();

        o1.numero = 1;
        o1.palavra = "c";
        o1.data = LocalDate.of(2018, 01, 1);

        o2.numero = 2;
        o2.palavra = "b";

        o3.numero = 3;
        o3.palavra = "a";
        o3.data = LocalDate.of(2018, 01, 2);

        List<Objeto> objetos = new ArrayList<>();
        objetos.add(o1);
        objetos.add(o2);
        objetos.add(o3);

        System.out.println("SEM ORDENAÇÃO:\n");
        for(Objeto objeto : objetos)
        {
            System.out.println(objeto.numero + '\t' + objeto.palavra + '\t' + objeto.data);
        }

        System.out.println("\n\nORDENADO PELO NÚMERO:");
        Collections.sort(objetos, new ObjetoComparator(1));
        for(Objeto objeto : objetos)
        {
            System.out.println(objeto.numero + '\t' + objeto.palavra + '\t' + objeto.data);
        }

        System.out.println("\n\nORDENADO PELa PALAVRA:");
        Collections.sort(objetos, new ObjetoComparator(2));
        for(Objeto objeto : objetos)
        {
            System.out.println(objeto.numero + '\t' + objeto.palavra + '\t' + objeto.data);
        }

        System.out.println("\n\nORDENADO PELA DATA::");
        Collections.sort(objetos, new ObjetoComparator(3));
        for(Objeto objeto : objetos)
        {
            System.out.println(objeto.numero + '\t' + objeto.palavra + '\t' + objeto.data);
        }
    }
}

Object Class:

public class Objeto 
{
    public int numero;
    public String palavra;
    public LocalDate data;

}

Comparison class:

public class ObjetoComparator implements Comparator<Objeto>
{
    /*
     * 1 - Compara pelo numero
     * 2 - Compara pela palavra
     * 3 - Compara pela data
     */
    private int ord;

    public ObjetoComparator(int Ord)
    {
        this.ord = Ord;
    }

    public int compare(Objeto o1, Objeto o2)
    {
        switch(this.ord)
        {
            case 1:
                if (o1.numero < o2.numero)
                    return -1;
                if (o1.numero > o2.numero)
                    return 1;

                return 0;

            case 2:
                return o1.palavra.compareTo(o2.palavra);

            case 3:
                try
                {
                    return o1.data.compareTo(o2.data);  
                }
                catch(NullPointerException e)
                {
                    return -1;
                }

            default:
                return 0;
        }
    }
}
    
asked by anonymous 24.01.2018 / 14:20

2 answers

5

You can use the nullsLast method of the Comparator class:

Class Objeto :

public class Objeto {

    private int numero;

    private String palavra;

    private LocalDate data;

    public Objeto(int numero, String palavra, LocalDate data) {
        super();
        this.numero = numero;
        this.palavra = palavra;
        this.data = data;
    }

    public int getNumero() {
        return numero;
    }

    public String getPalavra() {
        return palavra;
    }

    public LocalDate getData() {
        return data;
    }

    @Override
    public String toString() {
        return "Objeto [numero=" + numero + ", palavra=" + palavra + ", data=" + data + "]";
    }
}

Class Performing date comparison:

Objeto o1 = new Objeto(1, "c", LocalDate.of(2018, 01, 1));
Objeto o2 = new Objeto(2, "b", null);
Objeto o3 = new Objeto(3, "a", LocalDate.of(2018, 01, 2));

List<Objeto> objetos = Stream.of(o1, o2, o3).collect(Collectors.toList());

objetos.sort(Comparator.comparing(Objeto::getData, Comparator.nullsLast(LocalDate::compareTo)));
objetos.forEach(System.out::println);
    
24.01.2018 / 14:43
5

First, never use public attributes. This is a bad programming practice that should have been abolished and condemned almost unanimously by experienced programmers.

In addition, it is good practice to make these objects immutable if this is possible (it is not always, but if not, that it is something well thought out in this sense), even more so when it is an object whose only responsibility is to group related data.

Here's your new class Objeto :

public final class Objeto {
    private final int numero;
    private final String palavra;
    private final LocalDate data;

    public Objeto(int numero, String palavra, LocalDate data) {
        this.numero = numero;
        this.palavra = palavra;
        this.data = data;
    }

    public int getNumero() {
        return numero;
    }

    public String getPalavra() {
        return palavra;
    }

    public LocalDate getData() {
        return data;
    }

    @Override
    public String toString() {
         return numero + " " + palavra + " " + data;
    }
}

Your ObjetoComparator has three different behaviors (separated by switch ). In this case, it would be better to create three different classes to not mix them:

public class ObjetoNumeroComparator implements Comparator<Objeto> {
    public ObjetoNumeroComparator() {
    }

    @Override
    public int compare(Objeto o1, Objeto o2) {
        return o1.getNumero() - o2.getNumero();
    }
}
public class ObjetoPalavraComparator implements Comparator<Objeto> {
    public ObjetoPalavraComparator() {
    }

    @Override
    public int compare(Objeto o1, Objeto o2) {
        String a = o1.getPalavra();
        String b = o2.getPalavra();
        return a == null && b == null ? 0
                : a == null ? 1
                : b == null ? -1
                : a.compareTo(b);
    }
}
public class ObjetoDataComparator implements Comparator<Objeto> {
    public ObjetoDataComparator() {
    }

    @Override
    public int compare(Objeto o1, Objeto o2) {
        LocalDate a = o1.getData();
        LocalDate b = o2.getData();
        return a == null && b == null ? 0
                : a == null ? 1
                : b == null ? -1
                : a.compareTo(b);
    }
}

Note the% method with% of the last two cases. They check the compare and put it at the end. This is critical for the program to work.

So, instead of null , you use new ObjetoComparator(1) . Instead of new ObjetoNumeroComparator() , you use new ObjetoComparator(2) . Instead of new ObjetoPalavraComparator() , you use new ObjetoComparator(3) . This eliminates the need to have a crazy and arbitrary code number to define the desired behavior, separates those behaviors from each other and enables new types of behaviors to be created without having to change the code of the existing ones. / p>

Your code new ObjetoDataComparator() looks like this:

public class Main {
    public static void main(String[] args) {
        Objeto o1 = new Objeto(1, "c", LocalDate.of(2018, 01, 1));
        Objeto o2 = new Objeto(2, "b", null);
        Objeto o3 = new Objeto(3, "a", LocalDate.of(2018, 01, 2));

        List<Objeto> objetos = new ArrayList<>();
        objetos.add(o1);
        objetos.add(o2);
        objetos.add(o3);

        System.out.println("SEM ORDENAÇÃO: ");
        for (Objeto objeto : objetos) {
            System.out.println(objeto);
        }

        System.out.println("\n\nORDENADO PELO NÚMERO: ");
        Collections.sort(objetos, new ObjetoNumeroComparator());
        for (Objeto objeto : objetos) {
            System.out.println(objeto);
        }

        System.out.println("\n\nORDENADO PELA PALAVRA: ");
        Collections.sort(objetos, new ObjetoPalavraComparator());
        for (Objeto objeto : objetos) {
            System.out.println(objeto);
        }

        System.out.println("\n\nORDENADO PELA DATA: ");
        Collections.sort(objetos, new ObjetoDataComparator());
        for (Objeto objeto : objetos) {
            System.out.println(objeto);
        }
    }
}

See here working on ideone. . Here's the output:

SEM ORDENAÇÃO: 
1 c 2018-01-01
2 b null
3 a 2018-01-02


ORDENADO PELO NÚMERO: 
1 c 2018-01-01
2 b null
3 a 2018-01-02


ORDENADO PELA PALAVRA: 
3 a 2018-01-02
2 b null
1 c 2018-01-01


ORDENADO PELA DATA: 
1 c 2018-01-01
3 a 2018-01-02
2 b null

Oh, I also fixed a bug, here:

System.out.println(objeto.numero + '\t' + objeto.palavra + '\t' + objeto.data);

The Main is interpreted as 9, so it first adds 9 to \t and finds in the result at numero . This is why your output shows palavra , 10a and 11b instead of 12c , 1 a and 2 b . Using double quotation marks ( 3 c ) instead of single quotes ( " ), this problem is solved.

    
24.01.2018 / 14:39