Frage

I compareTo() Methode für eine einfache Klasse wie diese bin Umsetzung (in der Lage sein Collections.sort() und andere Leckereien von der Java-Plattform nutzen):

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

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

Ich möchte, dass die natürliche Ordnung für diese Objekte zu sein: 1) sortiert nach Namen und 2) nach Wert sortiert, wenn der Name der gleiche ist; beiden Vergleiche sollten Groß- und Kleinschreibung sein. Für beide Felder sind Nullwerte durchaus akzeptabel, so compareTo muss in diesen Fällen nicht brechen.

Die Lösung, die in dem Sinne kommt ist entlang der Linien der folgenden (ich verwende „Guard-Klauseln“ hier während andere einen einzigen Umkehrpunkt bevorzugen, aber das ist nebensächlich):

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

Das macht den Job, aber ich bin nicht perfekt mit diesem Code glücklich. Zwar ist es nicht sehr komplex, aber ist recht ausführlich und langwierig.

Die Frage ist, Wie würden Sie diese weniger ausführlich machen (während der Funktionalität beibehalten)? Fühlen Sie sich frei, um Java-Standardbibliotheken oder Apache Commons zu verweisen, wenn sie helfen. Wäre die einzige Möglichkeit, diese (ein wenig) zu machen einfacher sein, meinen eigenen „NullSafeStringComparator“, umsetzen und anwenden es beiden Felder für den Vergleich?

Edits 1-3 : Eddies Recht; reparierte den oben

Fall „beiden Namen null sind“

über die akzeptierte Antwort

ich diese Frage wieder im Jahr 2009 gebeten, auf Java 1.6 natürlich, und zu der Zeit der reine JDK Lösung von Eddie war meine bevorzugte akzeptierte Antwort. Ich nie dazu gekommen, um zu ändern, dass bis heute (2017).

Es gibt auch 3rd-Party-Bibliothek Lösungen -a 2009 Apache Commons Sammlungen ein und ein 2013 Guava ein erzielte beide von Mein, dass ich irgendwann in der Zeit hat es vorziehen.

Ich habe jetzt die saubere Java 8-Lösung von Lukasz Wiktor der akzeptierten Antwort. Das auf jeden Fall bevorzugt werden sollten, wenn auf Java 8, und in diesen Tagen Java 8 sollte für fast alle Projekte zur Verfügung.

War es hilfreich?

Lösung

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

Andere Tipps

Sie können einfach verwenden Apache Commons Lang :

result = ObjectUtils.compare(firstComparable, secondComparable)

Ich würde einen Null sicher Komparator implementieren. Es kann eine Implementierung da draußen sein, aber das ist so einfach zu implementieren, dass ich meine eigenen immer gerollt.

Hinweis: Der Komparator oben, wenn beide Namen sind null, wird nicht einmal die Wertfelder vergleichen. Ich glaube nicht, das ist, was Sie wollen.

Ich würde implementieren diese mit etwas wie folgt aus:

// 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: Fest Fehler in Codebeispiel. Das ist, was ich für die Prüfung nicht zuerst!

EDIT:. Promoted nullSafeStringComparator statischen

Sehen Sie die Unterseite dieser Antwort aktualisiert (2013) Lösung unter Verwendung von Guava.


Dies ist, was ich mit letztlich ging. Es stellte sich heraus, dass wir bereits eine Hilfsmethode für null sicheren String Vergleich hatte, so war die einfachste Lösung Gebrauch davon zu machen. (Es ist eine große Code-Basis, leicht, diese Art der Sache zu verpassen:)

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

Dies ist, wie die Helfer definiert ist (es überlastet ist, so dass Sie auch, ob nulls erste oder letzte kommen definieren können, wenn Sie möchten):

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

Das ist also im Wesentlichen die gleiche wie Eddies Antwort (obwohl ich würde kein statisches Helfer-Methode aufrufen a Komparator ) und das auch von uzhin .

Wie auch immer, im Allgemeinen, würde ich stark begünstigt habe Patrick Lösung , wie ich denke, es ist eine gute Praxis etablierte Bibliotheken zu verwenden, wann immer möglich. ( Wissen und die Bibliotheken verwendet als Josh Bloch sagt.) Aber in diesem Fall, dass nicht nachgegeben habe die sauberste, einfachste Code.

Edit (2009): Apache Commons Sammlungen Version

Eigentlich ist hier ein Weg, um die Lösung zu machen, basierend auf Apache Commons NullComparator einfacher. Kombinieren Sie es mit dem Groß- und Kleinschreibung Comparator in String-Klasse:

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

Nun ist diese ziemlich elegant ist, glaube ich. (Nur ein kleines Problem bleibt:. Die Commons NullComparator nicht Generika nicht unterstützt, so gibt es eine ungeprüfte Zuordnung)

Update (2013): Guava Version

Fast 5 Jahre später, hier ist, wie ich meine ursprüngliche Frage angehen würde. Wenn in Java-Codierung, würde ich (natürlich) sein mit Guava . (Und ganz sicher nicht Apache Commons).

Setzen Sie diese Konstante irgendwo, z.B. in "StringUtils" Klasse:

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

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

Natürlich ist dies fast identisch mit der Apache Commons Version (beide verwenden JDK CASE_INSENSITIVE_ORDER ), die Verwendung von nullsLast() die einzigen Guava spezifische Sache zu sein. Diese Version ist vorzuziehen, nur weil Guava ist vorzuziehen, als Abhängigkeit, auf Commons Sammlungen. (Wie sich alle einig .)

Wenn Sie sich wundern über Ordering , beachten sie, dass es Comparator implementiert. Es ist ziemlich praktisch, vor allem für komplexere Sortier Bedürfnisse, so dass Sie beispielsweise an der Kette mehr Ordnungen mit compound(). Lesen Sie Bestellung erklärt für mehr!

ich empfehlen die Verwendung von Apache commons immer, da es höchstwahrscheinlich besser sein wird als eine Sie selbst schreiben kann. Darüber hinaus können Sie dann tun ‚echte‘ Arbeit eher dann neu zu erfinden.

Die Klasse, die Sie interessiert sind, ist die Null Vergleicher . Es ermöglicht Ihnen, nulls hoch oder niedrig zu machen. Sie geben auch sie Ihre eigene Hand zu verwenden, wenn die beiden Werte nicht gleich null sind.

In Ihrem Fall können Sie eine statische Membervariable, die den Vergleich tut und dann compareTo Methode verweist nur das.

Somthing wie

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

}

Auch wenn Sie sich entscheiden, Ihre eigene Rolle, halten diese Klasse im Auge, da es sehr nützlich ist bei der Bestellung Listen null Elemente thatcontain.

Ich weiß, dass es nicht direkt auf Ihre Frage beantworten kann, weil Sie gesagt, dass Nullwerte unterstützt werden müssen.

Aber ich möchte nur zu beachten, dass NULL-Werte in compareTo ist nicht im Einklang mit compareTo Vertrag in offiziellen javadocs für Vergleichbare :

  

Beachten Sie, dass null nicht eine Instanz einer Klasse ist, und e.compareTo (null)   sollte eine Nullpointer werfen obwohl e.equals (null) zurückgibt   false.

So würde ich entweder werfen Nullpointer explizit oder lass es einfach erstes Mal geworfen werden, wenn Null-Argument dereferenziert wird.

Sie können Methode extrahieren:

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

}

Sie Ihre Klasse entwerfen könnte unveränderlich zu sein (. Effective Java 2nd Ed einen großen Abschnitt über das hat, Punkt 15: Minimieren Veränderlichkeit) und vergewissern Sie sich auf Konstruktion, dass keine NULL-Werte möglich sind (und die null-Objekt Muster falls erforderlich). Dann können Sie alle diese Kontrollen überspringen und sicher annehmen, die Werte nicht null.

Ich war auf der Suche nach etwas ähnliches, und dies schien ein wenig kompliziert, so tat ich dies. Ich denke, es ist ein wenig leichter zu verstehen. Sie können es als einen Komparator verwenden oder als Einzeiler. Für diese Frage möchten Sie compareToIgnoreCase (ändern). Wie, nulls aufschwimmen. Sie können die 1-Flip, -1, wenn Sie wollen, dass sie versenken.

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

Wir können Java-8 verwenden, um eine Nullfreundliche comparasion zwischen Objekt zu tun. soll ich mit zwei Feldern eine Boy-Klasse hava. String name und Integer Alter und ich möchte Vornamen vergleichen und dann im Alter, wenn beide gleich sind

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

und das Ergebnis:

    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

Falls jemand mit Spring, gibt es eine Klasse org.springframework.util.comparator.NullSafeComparator, dass dies auch für Sie tut. Nur dekorieren Sie Ihre eigenen vergleichbar mit ihm wie folgt

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


    }

}

Ausgang

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]

Einer der einfache Weg von mit Nullsafe Vergleicher ist Spring Umsetzung es zu verwenden, unten ist eine der einfachen Beispiel zu verweisen:

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

Ein weiteres Apache ObjectUtils Beispiel. Die Lage, andere Arten von Objekten zu sortieren.

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

Das ist meine Implementierung, die ich meine Arraylist sortieren. die Nullklassen zum letzten sortiert werden.

für meinen Fall, EntityPhone erstreckt EntityAbstract und mein Behälter ist List .

"compareIfNull ()" Methode wird für null sichere Sortierung verwendet. Die anderen Verfahren sind für die Vollständigkeit, die zeigen, wie compareIfNull verwendet werden kann.

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

Für den speziellen Fall, in dem Sie die Daten nicht wissen, nulls haben (immer eine gute Idee für Streicher) und die Daten sind wirklich groß, haben Sie immer tun, drei Vergleiche, bevor sie tatsächlich die Werte zu vergleichen, , wenn Sie wissen sicher, dass dies Ihr Fall , können Sie ein bisschen etwas optimieren. YMMV als lesbarer Code Trümpfe kleinere Optimierung:

        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);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top