195 votes

Comment simplifier une implémentation CompareTo () null-safe ?

Je suis la mise en œuvre de compareTo() méthode d'une classe simple comme ceci (pour être en mesure d'utiliser Collections.sort() et autres goodies offerts par la plate-forme Java):

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

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

Je veux que l' ordre naturel pour ces objets: 1) triée par nom et 2) triées par valeur si le nom est le même; les deux comparaisons doivent être insensibles à la casse. Pour les deux domaines les valeurs null sont parfaitement acceptables, alors compareTo ne doit pas casser dans ces cas.

La solution qui vient à l'esprit est le long de la lignes de la suite (je suis à l'aide de "garde des clauses" ici, tandis que d'autres préfèrent un seul point de retour, mais c'est à côté de la question):

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

De ce fait le travail, mais je ne suis pas parfaitement heureux avec ce code. Certes, il n'est pas très complexe, mais elle est très longue et fastidieuse.

La question est, comment voulez-vous faire de cette moins verbeux (tout en gardant la fonctionnalité)? Se sentir libre de se référer à la norme Java bibliothèques ou Apache Commons, si elles aident. Serait la seule option pour faire de la (un peu) plus simple à mettre en place mes propres "NullSafeStringComparator", et de l'appliquer pour comparer les deux champs?

Modifications 1-3: Eddie droit; fixe la "les deux noms sont null" cas ci-dessus

242voto

Dag Points 1566

Puisqu’il n’est pas encore mentionné, vous pouvez simplement utiliser Apache Commons Lang:

222voto

Lukasz Wiktor Points 663

À l’aide de Java 8:

96voto

Eddie Points 27755

Je voudrais mettre en œuvre un null sûr comparateur. Il y a peut être une mise en œuvre là-bas, mais c'est tellement simple à mettre en œuvre que j'ai toujours roulé mon propre.

Remarque: Votre comparateur ci-dessus, si les deux noms sont nuls, n'a même pas de comparer la valeur des champs. Je ne pense pas que ce est ce que vous voulez.

Je voudrais mettre en œuvre avec quelque chose comme ce qui suit:

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(final Metadata other) {
    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: correction de fautes dans le code de l'échantillon. C'est ce que je reçois pour ne pas le tester en premier!

EDIT: Promu nullSafeStringComparator à la statique.

23voto

Jonik Points 18905

Voir le bas de cette réponse pour la mise à jour (2013) de la solution à l'aide de la Goyave.


C'est ce que j'ai finalement allé avec. Il s'est avéré que nous avions déjà une utilité de la méthode pour les nuls-sûr de comparaison de Chaîne, de sorte que la solution la plus simple était de faire usage de. (C'est une grosse base de code; facile de rater ce genre de chose :)

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

C'est la façon dont l'aide est défini (c'est surchargé de sorte que vous pouvez également définir si les valeurs null venir en premier ou en dernier, si vous voulez):

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

Donc, c'est essentiellement le même que Eddie réponse (bien que je ne dirais pas statique méthode d'aide d'un comparateur) et que de uzhin trop.

De toute façon, en général, j'aurais fortement favorisée Patrick solution, car je pense que c'est une bonne pratique d'utiliser des bibliothèques établies chaque fois que possible. (Connaître et utiliser les bibliothèques comme Josh Bloch dit.) Mais dans ce cas qui n'auraient pas donné le plus propre, le plus simple code.

Edit (2009): Apache Commons Collections de version

En réalité, voici une façon de faire de la solution basée sur Apache Commons NullComparator plus simple. Le combiner avec de la casse, Comparator prévus en String classe:

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

Maintenant, c'est assez élégante, je crois. (Juste un petit problème demeure: les Communes NullComparator ne prend pas en charge les médicaments génériques, il y a donc un décoché affectation).

Mise à jour (2013): Goyave version

Près de 5 ans plus tard, voici comment j'allais attaquer à ma question initiale. Si le codage en Java, je voudrais (bien sûr) être à l'aide de Goyave. (Et très certainement pas Apache Commons.)

Mettre cette constante quelque part, par exemple dans "StringUtils" de la classe:

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

Puis, en 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);
}    

Bien sûr, cela est presque identique à celle d'Apache Commons version (à la fois utiliser Du JDK de CASE_INSENSITIVE_ORDER), l'utilisation de l' nullsLast() étant le seul Goyave-chose en particulier. Cette version est préférable de tout simplement parce que la Goyave est préférable, comme une dépendance, à Commons Collections. (Comme tout le monde est d'accord.)

Si vous vous demandez à propos de Ordering, note qu'il implémente Comparator. C'est assez pratique, surtout pour les plus complexes de tri besoins, vous permettant par exemple de chaîne de plusieurs Rangements à l'aide de compound(). Lire la Commande Expliqué plus!

14voto

Patrick Points 1664

Je le recommande toujours à l'aide d'Apache commons, car il sera probablement mieux que vous pouvez écrire sur votre propre. De Plus, vous pourrez alors faire une "vrai" travail, plutôt que de les réinventer.

La classe qui vous intéresse est le Null Comparateur. Il vous permet de faire des zéros de haute ou basse. Vous aussi donnez votre propre comparateur à utiliser lorsque les deux valeurs ne sont pas null.

Dans votre cas, vous pouvez avoir une variable membre statique qui ne la comparaison et puis votre compareTo méthode de références.

Quelque chose comme

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

}

Même si vous décidez de rouler votre propre, garder cette classe, car il est très utile lors de la commande des listes thatcontain éléments null.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X