Sort a java List containing null values

Asked

Viewed 458 times

2

Good morning!

In my List, when I do the ordering I would like the null values to be last.

I’ll show you an example to make it clear:

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 can see that in ordering by date the null value was before a nonnull value, and I would like the null value to be last. Follow the source below:

Main class:

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;
        }
    }
}

2 answers

4

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

Moreover, it is good practice to make these objects immutable if this is possible (it is not always, but if it is not, that it is something well thought out in this sense), especially when it is an object whose sole 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 not to 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);
    }
}

Observe the method compare of the last two cases. They verify the null and put it at the end. This is fundamental for the program to work.

With this, instead of new ObjetoComparator(1), you use new ObjetoNumeroComparator(). Instead of new ObjetoComparator(2), you use new ObjetoPalavraComparator(). Instead of new ObjetoComparator(3), you use new ObjetoDataComparator(). This eliminates the need to have a crazy and arbitrary numeric code to define the desired behavior, separates these behaviors from each other and enables new types of behaviors to be created without the need to change the code of existing ones.

Your code Main gets 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 way out:

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

Ah, I also got a bug, look here:

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

The \t is interpreted as 9, so he first adds 9 to the numero and Match the result to palavra. That’s why on your way out appears 10a, 11b and 12c instead of 1 a, 2 b and 3 c. Using double quotes (") instead of single quotes ('), this problem is solved.

  • Yes, as the good practices I always use them. I only made these classes for example, so much so that it has no use at all. The important thing was the issue of ordination itself.

  • The question of the nine I did not know, I found it strange not to leave the tab in the result. Thank you for this kkkk information and finally, thank you very much for the reply, helped a lot : -)

  • I liked the two answers. But only for @Carlosrafaeldeoliveiracarn :)

  • One more question. Uppercase and lowercase interfere with String ordering?

  • @Carlosrafaeldeoliveiracarn Yes, interfere. You can use the method compareToIgnoreCase(String) to solve this.

4


You can use the method nullsLast class Comparator:

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);

Browser other questions tagged

You are not signed in. Login or sign up in order to post.