web-dev-qa-db-fra.com

comparateur avec des valeurs nulles

Nous avons un code qui trie une liste d'adresses en fonction de la distance entre leurs coordonnées. cela se fait via collections.sort avec un comparateur personnalisé.

Cependant de temps en temps une adresse sans coordonnées est dans la liste provoquant une NullPointerException. Mon idée initiale pour résoudre ce problème était de faire en sorte que le comparateur renvoie 0 comme distance pour les adresses où au moins une des coordonnées est nulle. Je crains que cela ne conduise à une corruption de l'ordre des éléments "valides" de la liste.

il en va de même pour le retour des valeurs "0" pour les données nulles dans un comparateur, ou existe-t-il un moyen plus propre de résoudre ce problème.

54
pvgoddijn

Gérez-le comme null signifie infiniment loin. Donc:

  • comp(1234, null) == -1
  • comp(null, null) == 0
  • comp(null, 1234) == 1

Avec cela, vous obtenez une commande cohérente.

75
Sjoerd

Juste pour développer la réponse de Willi Schönborn, je suis venu ici pour dire que google-collections est exactement ce que vous recherchez ici.

Dans le cas général, vous pouvez simplement écrire votre propre Comparator pour ignorer les valeurs nulles (supposez non nulles, afin qu'il puisse se concentrer sur la logique importante), puis utilisez Ordering pour gérer le nulls:

Collections.sort(addresses, Ordering.from(new AddressComparator()).nullsLast());

Dans votre cas, cependant, ce sont les données DANS L'ADRESSE (les coordonnées) qui sont utilisées pour trier, non? google-collections est même plus utile dans ce cas. Vous pourriez donc avoir quelque chose de plus comme:

// Seems verbose at first glance, but you'll probably find yourself reusing 
// this a lot and it will pay off quickly.
private static final Function<Address, Coordinates> ADDRESS_TO_COORDINATES = 
  new Function<Address, Coordinates>() {
      public Coordinates apply(Address in) {
          return in.getCoordinates();
      }
  };

private static final Comparator<Coordinates> COORDINATE_SORTER = .... // existing

puis quand vous voulez trier:

Collections.sort(addresses,
    Ordering.from(COORDINATE_SORTER)
            .nullsLast()
            .onResultOf(ADDRESS_TO_COORDINATES));

et c'est là que la puissance de google-collections commence vraiment à porter ses fruits.

23
Cowan

Mon point de vue est que tout ce que vous essayez de faire pour "corriger" les coordonnées null ne fait que recouvrir les fissures. Ce que vous devez vraiment faire est de trouver et de corriger les bogues qui injectent les coordonnées parasites null.

D'après mon expérience, les infestations de bogues NPE sont souvent causées par les mauvaises habitudes de codage suivantes:

  • validation inadéquate des paramètres d'entrée,
  • en utilisant null pour éviter de créer des tableaux ou des collections vides,
  • renvoyer null lorsqu'une exception aurait dû être levée, ou
  • utiliser null pour représenter "aucune valeur" lorsqu'il existe une meilleure solution.

(De meilleures solutions au problème "sans valeur" impliquent généralement la réécriture du code de sorte que vous n'avez pas besoin pour représenter cela et/ou en utilisant une valeur non nulle à la place; par exemple une chaîne vide, une instance spéciale , une valeur réservée. Vous ne pouvez pas toujours trouver une meilleure solution, mais vous le pouvez souvent.)

Si cela décrit votre application, vous devriez passer du temps à éliminer les problèmes de code plutôt que de penser à des façons de masquer les NPE.

8
Stephen C

Ma solution (peut être utile pour quelqu'un qui regarde ici) est de faire la comparaison normale, avec des valeurs nulles remplacées non par 0, mais la valeur maximale possible (par exemple Integer.MAX_VALUE). Renvoyer 0 n'est pas cohérent, si vous avez des valeurs qui sont elles-mêmes 0. Voici un exemple correct:

        public int compare(YourObject lhs, YourObject rhs) {
            Integer l = Integer.MAX_VALUE;
            Integer r = Integer.MAX_VALUE;
            if (lhs != null) {
                l = lhs.giveMeSomeMeasure();
            }
            if (rhs != null) {
                r = rhs.giveMeSomeMeasure();
            }
            return l.compareTo(r);
        }

Je voulais juste ajouter que vous n'avez pas nécessairement besoin de la valeur maximale pour l'entier. Cela dépend de ce que votre méthode giveMeSomeMeasure () peut retourner. Si, par exemple, vous comparez les degrés Celsius pour la météo, vous pouvez définir l et r sur -300 ou +300, selon l'endroit où vous souhaitez définir les objets nuls - en tête ou en queue de la liste.

8
stoilkov

Si vous utilisez Java 8, vous avez 2 nouvelles méthodes statiques dans la classe Comparator, qui sont utiles:

public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)

La comparaison sera null safe et vous pouvez choisir où placer les valeurs null dans la séquence triée.

L'exemple suivant:

List<String> monkeyBusiness = Arrays.asList("Chimp", "eat", "sleep", "", null, "banana",
            "throw banana peel", null, "smile", "run");
Comparator<? super String> comparator = (a, b) -> a.compareTo(b);
monkeyBusiness.stream().sorted(Comparator.nullsFirst(comparator))
            .forEach(x -> System.out.print("[" + x + "] "));

affichera: [null] [null] [] [Chimp] [banane] [manger] [courir] [dormir] [sourire] [jeter la peau de banane]

4
Adrian Cosma

Vous ne voulez probablement pas retourner 0 car cela implique que les adresses sont équidistantes et vous ne savez vraiment pas. C'est un problème assez classique où vous essayez de traiter de mauvaises données d'entrée. Je ne pense pas que ce soit la responsabilité du comparateur d'essayer de déterminer dans quelle mesure l'adresse est en termes réels lorsque vous ne sais pas la distance. Je supprimerais ces adresses de la liste avant de trier.

Le hack serait de les déplacer en bas de la liste (mais c'est moche!)

3
LenW

Non, il n'y a pas de moyen plus propre. Peut-être:

  • si les coordonnées des deux objets comparés sont nulles, retournez 0
  • si les coordonnées de l'un des objets sont nulles, retournez -1/1 (selon qu'il s'agit du premier ou du deuxième argument)

Mais plus important encore - essayez de vous débarrasser de/remplissez les coordonnées manquantes, ou mieux: ne mettez pas d'adresses avec des coordonnées manquantes dans la liste.

En fait, ne pas les mettre dans la liste est le comportement le plus logique. Si vous les mettez dans la liste, le résultat ne sera pas réellement trié par distance.

Vous pouvez créer une autre liste, contenant les adresses avec des coordonnées manquantes, et faire comprendre à quiconque a besoin de ces informations (utilisateur final, utilisateur de l'API), que la première liste contient uniquement des adresses avec les données nécessaires, tandis que la deuxième liste contient des adresses qui manque d'informations requises.

1
Bozho

Personnellement, je déteste traiter des cas nuls spéciaux partout dans mes comparateurs, donc je cherchais une solution plus propre et j'ai finalement trouvé des collections Google. Leur commande est tout simplement géniale. Ils prennent en charge les comparateurs composés, proposent de trier les valeurs nulles en haut et à la fin et permettent d'exécuter certaines fonctions avant de comparer. Écrire des comparateurs n'a jamais été aussi simple. Tu devrais essayer.

1
whiskeysierra

Au lieu de regarder cela comme un problème technique avec le comparateur, il est préférable de revoir les exigences: qu'est-ce que vous essayez vraiment de faire ici, qu'allez-vous faire avec cette liste triée?

  • Si vous essayez de les trier pour montrer d'abord les solutions les plus pertinentes à un utilisateur, ce peut être une bonne idée de mettre les emplacements inconnus en dernier, alors traitez-les comme infinis (renvoyant 0/-1/1 selon lequel d'entre eux est nul).
  • Si vous allez utiliser ce résultat pour dessiner un graphique ou faire d'autres calculs qui dépendent de leur tri réel en fonction de leur distance, alors les valeurs nulles n'auraient probablement pas dû y être de toute façon (alors supprimez-les d'abord ou lancez un exception si, à ce moment-là, il n’était pas censé y avoir d’adresses avec un emplacement nul).

Comme vous le savez déjà, retourner toujours 0 lorsque l'un d'eux est nul n'est pas une bonne idée ici; cela peut en effet corrompre le résultat. Mais ce que vous devez faire à la place dépend de ce dont vous avez besoin, pas de ce que les autres font/ont généralement besoin. Le comportement de votre programme avec des adresses qui n'ont pas d'emplacement (donc ce que l'utilisateur va voir) ne devrait pas dépendre de certains détails techniques comme la "meilleure pratique" pour les comparateurs. (Pour moi, demander ici quelles sont les "meilleures pratiques", revient à demander quelles sont les "meilleures exigences").

1
Wouter Coekaerts