Pergunta

Estou implementando método compareTo() para uma classe simples como este (para ser capaz de usar Collections.sort() e outras guloseimas oferecidas pela plataforma Java):

public class Metadata implements Comparable<Metadata> {
    private String name;
    private String value;

// Imagine basic constructor and accessors here
// Irrelevant parts omitted
}

Eu quero o ordenação natural para esses objetos para ser: 1) classificados por nome e 2), ordenadas por valor se o nome é o mesmo; ambas as comparações devem ser maiúsculas e minúsculas. Para ambos os campos valores nulos são perfeitamente aceitáveis, por isso compareTo não deve quebrar nestes casos.

A solução que vem à mente é ao longo das linhas dos seguintes (que estou usando "cláusulas de guarda" aqui, enquanto outros podem preferir um único ponto de retorno, mas isso não vem ao caso):

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(Metadata other) {
    if (this.name == null && other.name != null){
        return -1;
    }
    else if (this.name != null && other.name == null){
        return 1;
    }
    else if (this.name != null && other.name != null) {
        int result = this.name.compareToIgnoreCase(other.name);
        if (result != 0){
            return result;
        }
    }

    if (this.value == null) {
        return other.value == null ? 0 : -1;
    }
    if (other.value == null){
        return 1;
    }

    return this.value.compareToIgnoreCase(other.value);
}

Este faz o trabalho, mas eu não estou perfeitamente feliz com este código. É certo que não é muito complexa, mas é bastante detalhado e tedioso.

A questão é, como você fazer isso menos detalhado (mantendo a funcionalidade)? Sinta-se livre para se referir a bibliotecas padrão Java ou Apache Commons se eles ajudam. Seria a única opção para fazer isso (um pouco) mais simples ser implementar a minha própria "NullSafeStringComparator", e aplicá-lo para comparar os dois campos?

Edições 1-3 : Eddie de direito; fixou o "ambos os nomes são nulos" caso acima

Sobre a resposta aceita

Eu fiz esta pergunta em 2009, em Java 1.6 é claro, e no momento em que a solução JDK puro por Eddie foi minha resposta aceita preferido. Eu nunca got para mudar isso até agora (2017).

Há também soluções de biblioteca 3o partido -a 2009 Apache Commons Collections um ano e 2.013 Goiaba um, tanto postado por me-que eu tinha preferência em algum ponto no tempo.

Agora eu fiz a solução Java 8 limpo por Lukasz Wiktor a resposta aceita. Que deve definitivamente ser preferido se em Java 8, e estes dias Java 8 deve estar disponível para quase todos os projetos.

Foi útil?

Solução

Usando Java 8 :

private static Comparator<String> nullSafeStringComparator = Comparator
        .nullsFirst(String::compareToIgnoreCase); 

private static Comparator<Metadata> metadataComparator = Comparator
        .comparing(Metadata::getName, nullSafeStringComparator)
        .thenComparing(Metadata::getValue, nullSafeStringComparator);

public int compareTo(Metadata that) {
    return metadataComparator.compare(this, that);
}

Outras dicas

Você pode simplesmente usar Apache Commons Lang :

result = ObjectUtils.compare(firstComparable, secondComparable)

Gostaria de implementar um comparador seguro nulo. Pode haver uma implementação lá fora, mas isso é tão simples de implementar que eu sempre rolou meu próprio.

Nota: O comparador acima, se ambos nomes são nulos, não vai mesmo comparar os campos de valor. Eu não acho que isso é o que você quer.

Gostaria de implementar isso com algo como o seguinte:

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(final Metadata other) {

    if (other == null) {
        throw new NullPointerException();
    }

    int result = nullSafeStringComparator(this.name, other.name);
    if (result != 0) {
        return result;
    }

    return nullSafeStringComparator(this.value, other.value);
}

public static int nullSafeStringComparator(final String one, final String two) {
    if (one == null ^ two == null) {
        return (one == null) ? -1 : 1;
    }

    if (one == null && two == null) {
        return 0;
    }

    return one.compareToIgnoreCase(two);
}

EDIT: Corrigidos problemas na amostra de código. Isso é o que eu recebo por não testá-lo primeiro!

EDIT:. Promovido nullSafeStringComparator para estático

Veja a parte inferior desta resposta para solução atualizada (2013) usando goiaba.


Isto é o que em última análise, foi com. Descobriu-se que já tinha um método de utilitário para comparação de Cordas nulo-safe, então a solução mais simples era fazer uso disso. (É uma grande base de código; fácil perder esse tipo de coisa:)

public int compareTo(Metadata other) {
    int result = StringUtils.compare(this.getName(), other.getName(), true);
    if (result != 0) {
        return result;
    }
    return StringUtils.compare(this.getValue(), other.getValue(), true);
}

Esta é a forma como o auxiliar é definido (é sobrecarregado para que você também pode definir se nulos vir primeiro ou o último, se você quiser):

public static int compare(String s1, String s2, boolean ignoreCase) { ... }

Então, isso é essencialmente o mesmo que resposta de Eddie (embora eu não chamaria de um método auxiliar estática um comparador ) e o de uzhin também.

De qualquer forma, em geral, eu teria fortemente favorecido Patrick solução , como eu acho que é uma boa prática para usar bibliotecas estabelecidas sempre que possível. ( Conhecer e utilizar as bibliotecas como diz Josh Bloch). Mas, neste caso, que não teria rendido o código mais limpo, mais simples.

Edit (2009): Versão Apache Commons Collections

Na verdade, aqui está uma maneira de fazer a solução baseada no Apache Commons NullComparator mais simples. Combine-o com o case-insensitive Comparator fornecidos na classe String:

public static final Comparator<String> NULL_SAFE_COMPARATOR 
    = new NullComparator(String.CASE_INSENSITIVE_ORDER);

@Override
public int compareTo(Metadata other) {
    int result = NULL_SAFE_COMPARATOR.compare(this.name, other.name);
    if (result != 0) {
        return result;
    }
    return NULL_SAFE_COMPARATOR.compare(this.value, other.value);
}

Agora, isso é muito elegante, eu acho. (Apenas um pequeno restos de emissão:. A NullComparator Commons não suporta genéricos, por isso há uma atribuição desmarcada)

Update (2013): Goiaba versão

Quase 5 anos depois, aqui está como eu resolver a minha pergunta original. Se a codificação em Java, eu (é claro) estar usando Goiaba . (E muito certamente não Apache Commons.)

Coloque esta constante em algum lugar, por exemplo, em "StringUtils" classe:

public static final Ordering<String> CASE_INSENSITIVE_NULL_SAFE_ORDER =
    Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast(); // or nullsFirst()

Então, em public class Metadata implements Comparable<Metadata>:

@Override
public int compareTo(Metadata other) {
    int result = CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.name, other.name);
    if (result != 0) {
        return result;
    }
    return CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.value, other.value);
}    

Claro, isso é quase idêntica à versão do Apache Commons (tanto para uso JDK é CASE_INSENSITIVE_ORDER ), o uso de nullsLast() sendo a única coisa específica de goiaba. Esta versão é preferível simplesmente porque goiaba é preferível, como uma dependência, a Commons Collections. (Como concorda .)

Se você estava pensando sobre Ordering, nota que implementa Comparator. É muito útil especialmente para mais necessidades de classificação complexas, permitindo-lhe, por exemplo, a cadeia de vários ordenamentos usando compound(). Leia Como encomendar explicou para mais!

Eu sempre recomendo o uso de bens comuns do Apache, uma vez que provavelmente será melhor do que aquele que você pode escrever em seu próprio país. Além disso, você pode então fazer o trabalho 'real' em vez de reinventar.

A classe que você está interessado é o Null Comparador . Ele permite que você faça nulos alta ou baixa. Você também dar-lhe o seu próprio comparador para usar quando os dois valores não são nulos.

No seu caso, você pode ter uma variável de membro estático que faz a comparação e, em seguida, o seu método compareTo apenas referências que.

Somthing como

class Metadata implements Comparable<Metadata> {
private String name;
private String value;

static NullComparator nullAndCaseInsensitveComparator = new NullComparator(
        new Comparator<String>() {

            @Override
            public int compare(String o1, String o2) {
                // inputs can't be null
                return o1.compareToIgnoreCase(o2);
            }

        });

@Override
public int compareTo(Metadata other) {
    if (other == null) {
        return 1;
    }
    int res = nullAndCaseInsensitveComparator.compare(name, other.name);
    if (res != 0)
        return res;

    return nullAndCaseInsensitveComparator.compare(value, other.value);
}

}

Mesmo se você decidir lançar seu próprio, manter esta classe em mente, uma vez que é muito útil quando encomendar listas thatcontain elementos nulos.

Eu sei que isso pode não ser responder directamente à sua pergunta, porque você disse que os valores nulos têm de ser suportados.

Mas eu só quero nota que o apoio nulos em compareTo não está em consonância com contrato de compareTo descrito no oficial javadocs para comparável:

Note que nulo não é uma instância de qualquer classe, e e.compareTo (null) deve lançar um NullPointerException embora e.equals (null) retorna falsa.

Assim, gostaria quer lançar NullPointerException explícita ou apenas deixá-lo ser jogado primeira vez quando argumento nulo está sendo dereferenced.

Você pode extrair método:

public int cmp(String txt, String otherTxt)
{
    if ( txt == null )
        return otjerTxt == null ? 0 : 1;

    if ( otherTxt == null )
          return 1;

    return txt.compareToIgnoreCase(otherTxt);
}

public int compareTo(Metadata other) {
   int result = cmp( name, other.name); 
   if ( result != 0 )  return result;
   return cmp( value, other.value); 

}

Você poderia projetar sua classe para ser imutável (. Effective Java 2ª Ed tem uma grande seção sobre isso, ponto 15: Minimize mutabilidade) e certifique-se sobre a construção que não nulos são possíveis (e usar o nulo objeto padrão se necessário). Então você pode pular todos esses cheques e seguramente assumir os valores não são nulos.

Eu estava procurando por algo semelhante e este parecia um pouco complicado, então eu fiz isso. Eu acho que é um pouco mais fácil de entender. Você pode usá-lo como um comparador ou como um forro. Para esta pergunta que você mudaria para compareToIgnoreCase (). Como é, nulos flutuar para cima. Você pode virar a 1, -1, se você quer que eles pia.

StringUtil.NULL_SAFE_COMPARATOR.compare(getName(), o.getName());

.

public class StringUtil {
    public static final Comparator<String> NULL_SAFE_COMPARATOR = new Comparator<String>() {

        @Override
        public int compare(final String s1, final String s2) {
            if (s1 == s2) {
                //Nulls or exact equality
                return 0;
            } else if (s1 == null) {
                //s1 null and s2 not null, so s1 less
                return -1;
            } else if (s2 == null) {
                //s2 null and s1 not null, so s1 greater
                return 1;
            } else {
                return s1.compareTo(s2);
            }
        }
    }; 

    public static void main(String args[]) {
        final ArrayList<String> list = new ArrayList<String>(Arrays.asList(new String[]{"qad", "bad", "sad", null, "had"}));
        Collections.sort(list, NULL_SAFE_COMPARATOR);

        System.out.println(list);
    }
}

podemos usar java 8 para fazer um comparasion amigável nulo entre o objeto. suposto i hava uma classe Menino com 2 campos:. String name e idade Integer e eu quero primeiro comparar nomes e, em seguida, idades, se ambos são iguais

static void test2() {
    List<Boy> list = new ArrayList<>();
    list.add(new Boy("Peter", null));
    list.add(new Boy("Tom", 24));
    list.add(new Boy("Peter", 20));
    list.add(new Boy("Peter", 23));
    list.add(new Boy("Peter", 18));
    list.add(new Boy(null, 19));
    list.add(new Boy(null, 12));
    list.add(new Boy(null, 24));
    list.add(new Boy("Peter", null));
    list.add(new Boy(null, 21));
    list.add(new Boy("John", 30));

    List<Boy> list2 = list.stream()
            .sorted(comparing(Boy::getName, 
                        nullsLast(naturalOrder()))
                   .thenComparing(Boy::getAge, 
                        nullsLast(naturalOrder())))
            .collect(toList());
    list2.stream().forEach(System.out::println);

}

private static class Boy {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Boy(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "name: " + name + " age: " + age;
    }
}

e o resultado:

    name: John age: 30
    name: Peter age: 18
    name: Peter age: 20
    name: Peter age: 23
    name: Peter age: null
    name: Peter age: null
    name: Tom age: 24
    name: null age: 12
    name: null age: 19
    name: null age: 21
    name: null age: 24

No caso de alguém usando Spring, há uma org.springframework.util.comparator.NullSafeComparator classe que faz isso para você também. Apenas decorar seu próprio comparável com ele como este

new NullSafeComparator<YourObject>(new YourComparable(), true)

https: // docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Comparator;

public class TestClass {

    public static void main(String[] args) {

        Student s1 = new Student("1","Nikhil");
        Student s2 = new Student("1","*");
        Student s3 = new Student("1",null);
        Student s11 = new Student("2","Nikhil");
        Student s12 = new Student("2","*");
        Student s13 = new Student("2",null);
        List<Student> list = new ArrayList<Student>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        list.add(s11);
        list.add(s12);
        list.add(s13);

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

        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
            Student student = (Student) iterator.next();
            System.out.println(student);
        }


    }

}

saída é

Student [name=*, id=1]
Student [name=*, id=2]
Student [name=Nikhil, id=1]
Student [name=Nikhil, id=2]
Student [name=null, id=1]
Student [name=null, id=2]

Uma das maneira simples de usando NullSafe Comparador é usar a implementação da Primavera dele, abaixo é um dos exemplo simples para se referir:

public int compare(Object o1, Object o2) {
        ValidationMessage m1 = (ValidationMessage) o1;
        ValidationMessage m2 = (ValidationMessage) o2;
        int c;
        if (m1.getTimestamp() == m2.getTimestamp()) {
            c = NullSafeComparator.NULLS_HIGH.compare(m1.getProperty(), m2.getProperty());
            if (c == 0) {
                c = m1.getSeverity().compareTo(m2.getSeverity());
                if (c == 0) {
                    c = m1.getMessage().compareTo(m2.getMessage());
                }
            }
        }
        else {
            c = (m1.getTimestamp() > m2.getTimestamp()) ? -1 : 1;
        }
        return c;
    }

Outro exemplo Apache ObjectUtils. Capaz de resolver outros tipos de objetos.

@Override
public int compare(Object o1, Object o2) {
    String s1 = ObjectUtils.toString(o1);
    String s2 = ObjectUtils.toString(o2);
    return s1.toLowerCase().compareTo(s2.toLowerCase());
}

Esta é a minha aplicação que eu uso para classificar meu ArrayList. as classes nulos são classificadas para o último.

para o meu caso, EntityPhone estende EntityAbstract e meu recipiente é List .

o método "compareIfNull ()" é usado para triagem segura nulo. Os outros métodos são para ser completo, mostrando como compareIfNull pode ser usado.

@Nullable
private static Integer compareIfNull(EntityPhone ep1, EntityPhone ep2) {

    if (ep1 == null || ep2 == null) {
        if (ep1 == ep2) {
            return 0;
        }
        return ep1 == null ? -1 : 1;
    }
    return null;
}

private static final Comparator<EntityAbstract> AbsComparatorByName = = new Comparator<EntityAbstract>() {
    @Override
    public int compare(EntityAbstract ea1, EntityAbstract ea2) {

    //sort type Phone first.
    EntityPhone ep1 = getEntityPhone(ea1);
    EntityPhone ep2 = getEntityPhone(ea2);

    //null compare
    Integer x = compareIfNull(ep1, ep2);
    if (x != null) return x;

    String name1 = ep1.getName().toUpperCase();
    String name2 = ep2.getName().toUpperCase();

    return name1.compareTo(name2);
}
}


private static EntityPhone getEntityPhone(EntityAbstract ea) { 
    return (ea != null && ea.getClass() == EntityPhone.class) ?
            (EntityPhone) ea : null;
}

Para o caso específico onde você sabe que os dados não têm valores nulos (sempre uma boa idéia para cordas) e os dados é muito grande, você ainda está fazendo três comparações antes de realmente comparar os valores, se você sabe para certo de que este é o seu caso , você pode otimizar um pouquinho. YMMV como código legível supera otimização menor:

        if(o1.name != null && o2.name != null){
            return o1.name.compareToIgnoreCase(o2.name);
        }
        // at least one is null
        return (o1.name == o2.name) ? 0 : (o1.name != null ? 1 : -1);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top