2008-09-24 36 views
20

En Java, utilizo una clase en la que algunos campos pueden ser null. Por ejemplo:¿Qué hacer con campos nulos en compare()?

class Foo { 
    String bar; 
    //.... 
} 

Quiero escribir un BarComparator para esta clase,

private static class BarComparator 
      implements Comparator<Foo> { 
     public int compare(final Foo o1, final Foo o2) 
     { 
      // Implementation goes here 
     } 
    } 

¿Hay una manera estándar de tratar con el hecho de que cualquiera de o1, o2, o1.bar, o2.bar puede haber null , sin escribir muchos anidados if ... else?

¡Salud!

Respuesta

36

Creo que se puede envolver la llamada al método de campo compareTo con un pequeño método estático para ordenar los nulos alta o baja:

static <T extends Comparable<T>> int cp(T a, T b) { 
    return 
     a==null ? 
     (b==null ? 0 : Integer.MIN_VALUE) : 
     (b==null ? Integer.MAX_VALUE : a.compareTo(b)); 
} 

Uso simple (múltiples campos es como lo haría normalmente):

public int compare(final Foo o1, final Foo o2) { 
    return cp(o1.field, o2.field); 
} 
+1

Fuera de tema Sé, pero ¿cuál es el motivo para preferir MIN/MAX_VALUE sobre -/+ 1? –

+6

Perdón por la demora en responder. Es para asegurar que tengamos la desigualdad triangular. Para a> b> c, a.compareTo (b) + b.compareTo (c) <= a.compareTo (c). No es que a nadie le importe ... –

+0

Aún puede arrojar una 'NullPointerException', si o1/o2 es nulo. ¿Cómo se debe tratar el o1/o2 como nulo? OP mencionado: o1, o2, o1.bar, o2.bar puede ser nulo. ¿O es parte de un contrato de comparación: la comparación de nulos debería arrojar un NPE? – Daniel

1

Me parece que no hay un método para hacerlo, pero de todos modos el código no es tan largo.

0

creo sentencias de retorno primeros serían la otra alternativa a un montón de IFS

por ejemplo,

if(o1==null) return x; 
if(o2==null) return x; 
if(o1.getBar()==null) return x; 
if(o2.getBar()==null) return x; 

// No null checks needed from this point. 
6

Depende de si se tiene en cuenta una entrada nula a ser un valor de valor de cadena válida de comparación. es nulo < o> "manzana". Lo único que puedo decir con certeza es que null == null. Si puede definir dónde encaja nulo en el orden, entonces puede escribir el código apropiadamente.

En este caso, podría optar por arrojar una excepción NullPointerExcpetion o IllegalArgumentException y tratar de manejar el nulo en un nivel superior al no ponerlo en la comparación en primer lugar.

+0

Me gusta esta respuesta. ¡Gracias! – Burkhard

2

La clave aquí es determinar cómo te gustaría que los nulos sean tratados. Algunas opciones son: a) suponer que los nulos vienen antes que todos los demás objetos en orden de clasificación b) suponer que los nulos vienen después de todos los demás objetos en orden de clasificación c) tratar nulo como equivalente a algún valor predeterminado d) tratar nulos como condiciones de error. El que elija dependerá por completo de la aplicación en la que esté trabajando.

En el último caso, por supuesto, lanza una excepción. Para los demás, necesitas un caso de cuatro vías if/else (alrededor de tres minutos de codificación, uno en el que hayas determinado cuáles son los resultados).

2

Si está utilizando Google collections, puede encontrar que la clase Comparators es útil. Si tiene métodos de ayuda para ordenar nulos como el elemento más grande o menos en la colección. Puede usar compound comparators para ayudar a reducir la cantidad de código.

8

Gracias por las respuestas! El método genérico y Google Comparators se ven interesantes.

Y he encontrado que hay una NullComparator en el Apache Commons Collections (que actualmente estamos utilizando):

private static class BarComparator 
     implements Comparator<Foo> 
{ 
    public int compare(final Foo o1, final Foo o2) 
    { 
     // o1.bar & o2.bar nulleness is taken care of by the NullComparator. 
     // Easy to extend to more fields. 
     return NULL_COMPARATOR.compare(o1.bar, o2.bar); 
    } 

    private final static NullComparator NULL_COMPARATOR = 
              new NullComparator(false); 
} 

Nota: me he centrado en el campo bar aquí para mantenerlo al punto.

+1

Parece que el enlace a los Javadocs está muerto. Ahora se encuentra en http://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html. – JBert

1

No debe usar el NullComparator de la manera en que lo hace; está creando una nueva instancia de la clase para cada operación de comparación, y si, por ejemplo, está ordenando una lista con 1000 entradas, que serán 1000 * log2 (1000) objetos que son completamente superfluos. Esto puede volverse problemático rápidamente.

De cualquier subclase, o delegar en ella, o simplemente poner en práctica su propio cheque nulo - no es realmente tan complejo:

private static class BarComparator 
     implements Comparator<Foo> { 
    private NullComparator delegate = new NullComparator(false); 

    public int compare(final Foo o1, final Foo o2) 
    { 
     return delegate.compare(o1.bar, o2.bar); 
    } 
} 
+0

Tiene razón, el NullComparator debe ser un campo estático privado. Lo escribí de esa manera en el ejemplo para mantener el foco en la nulidad. –

3

Usted puede escribir su Comparador por ello. Digamos que tienes una persona de clase con nombre de cadena como campo privado. getName() y setName() método para acceder al nombre del campo. Debajo está el Comparador para la clase Persona.

Collections.sort(list, new Comparator<Person>() { 
     @Override 
     public int compare(Person a, Person b) { 
      if (a == null) { 
       if (b == null) { 
        return 0; 
       } 
       return -1; 
      } else if (b == null) { 
       return 1; 
      } 
      return a.getName().compareTo(b.getName()); 
     } 
    }); 

Actualización:

A partir de Java 8 se puede utilizar por debajo de la API de la lista.

// Push nulls at the end of List 
Collections.sort(subjects1, Comparator.nullsLast(String::compareTo)); 

// Push nulls at the beginning of List 
Collections.sort(subjects1, Comparator.nullsFirst(String::compareTo)); 
2

También existe la clase org.springframework.util.comparator.NullSafeComparator en el Spring Framework se puede utilizar.

Ejemplo (Java 8):

SortedSet<Foo> foos = new TreeSet<>((o1, o2) -> { 
     return new NullSafeComparator<>(String::compareTo, true).compare(o1.getBar(), o2.getBar()); 
    }); 

    foos.add(new Foo(null)); 
    foos.add(new Foo("zzz")); 
    foos.add(new Foo("aaa")); 

    foos.stream().forEach(System.out::println); 

Esto imprimirá:

Foo{bar='null'} 
Foo{bar='aaa'} 
Foo{bar='zzz'} 
0

Teniendo en cuenta al cliente como una respuesta POJO.My sería:

Comparator<Customer> compareCustomer = Comparator.nullsLast((c1,c2) -> c1.getCustomerId().compareTo(c2.getCustomerId())); 

O

Comparator<Customer> compareByName = Comparator.comparing(Customer::getName,nullsLast(String::compareTo));