質問

実装してるよ compareTo() このような単純なクラスのメソッド (使用できるようにするため) Collections.sort() Java プラットフォームによって提供されるその他の便利な機能):

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

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

欲しいのは 自然な順序付け これらのオブジェクトは次のようになります。1) 名前で並べ替え、2) 名前が同じ場合は値で並べ替えます。どちらの比較でも大文字と小文字を区別しないでください。どちらのフィールドでも null 値は完全に許容されるため、 compareTo このような場合に壊れてはなりません。

思い浮かぶ解決策は次のようなものです(ここでは「ガード句」を使用していますが、他の人は単一のリターンポイントを好むかもしれませんが、それは重要ではありません)。

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

これで問題は解決しましたが、私はこのコードに完全に満足しているわけではありません。確かにそうではありません とても 複雑ですが、非常に冗長で退屈です。

質問は、 これをどのように冗長にしますか (機能を維持しながら)?役立つ場合は、Java 標準ライブラリまたは Apache Commons を参照してください。これを(少し)簡単にする唯一のオプションは、独自の「NullSafeStringComparator」を実装し、それを両方のフィールドの比較に適用することですか?

編集 1 ~ 3:エディは正しい。上記の「両方の名前が null である」ケースを修正しました

受け入れられた回答について

私は 2009 年にこの質問をしましたが、もちろん Java 1.6 で、当時は Eddie による純粋な JDK ソリューション これが私の好ましい受け入れられた答えでした。今(2017年)までそれを変えることはできませんでした。

もあります サードパーティのライブラリ ソリューション— 2009 年の Apache Commons Collections のものと 2013 年の Guava のもので、どちらも私が投稿したもので、ある時点で私はそっちのほうが好きでした。

今はきれいにしました Lukasz Wiktor による Java 8 ソリューション 受け入れられた答え。Java 8 を使用する場合は、これが望ましいことは間違いなく、最近では Java 8 はほぼすべてのプロジェクトで利用できるはずです。

役に立ちましたか?

解決

使用する 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);
}

他のヒント

簡単に使用できます Apache Commons Lang:

result = ObjectUtils.compare(firstComparable, secondComparable)

私はヌル安全なコンパレータを実装します。そこにそこに実装することが、これは、私は常に自分自身を巻いたことを実現するのは簡単ですがあります。

注:上記のあなたのコンパレータ、の両方の名前がnullの場合、偶数値のフィールドを比較しません。私は、これはあなたが望むものではないと思います。

私は、次のようなものでこれを実装します:

// 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:静的に昇格nullSafeStringComparator

Guava を使用した更新された (2013) ソリューションについては、この回答の最後を参照してください。


これが私が最終的に採用したものです。null セーフな String 比較のためのユーティリティ メソッドがすでに存在することが判明したため、最も簡単な解決策はそれを利用することでした。(これは大きなコードベースです。この種のことは見逃しがちです:)

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

これはヘルパーの定義方法です (必要に応じて、null が最初に来るか最後に来るかを定義できるようにオーバーロードされています)。

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

したがって、これは本質的に次と同じです エディの答え (ただし、私は静的ヘルパー メソッドを コンパレータ) そして ウジンのこと あまりにも。

とにかく、一般的に、私は強く支持したでしょう パトリックの解決策, 、可能な限り確立されたライブラリを使用することをお勧めします。(図書館を知り、利用する Josh Bloch が言っているように) しかし、この場合、最もクリーンで最も単純なコードは得られなかったでしょう。

編集 (2009):Apache Commons Collections バージョン

実際、Apache Commons に基づいてソリューションを作成する方法は次のとおりです。 NullComparator もっとシンプルに。と組み合わせます。 大文字小文字を区別しません Comparator で提供される 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);
}

これはかなりエレガントだと思います。(小さな問題が 1 つだけ残っています:コモンズ NullComparator はジェネリックをサポートしていないため、チェックされていない割り当てがあります。)

アップデート (2013):グアババージョン

ほぼ 5 年が経ち、当初の質問に私がどのように対処するかは次のとおりです。Java でコーディングする場合は、(もちろん) を使用します グアバ. 。(そしてまったく間違いなく ない Apache Commons。)

この定数をどこかに置きます。たとえば「StringUtils」クラス内:

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

次に、 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);
}    

もちろん、これは Apache Commons のバージョンとほぼ同じです (どちらも JDKの CASE_INSENSITIVE_ORDER)、 の用法 nullsLast() グアバ特有のものはこれだけです。このバージョンが推奨されるのは、単純に、依存関係として Commons Collections よりも Guava が推奨されるためです。(として 誰もが同意する.)

疑問に思っていたら Ordering, を実装していることに注意してください。 Comparator. 。これは、特により複雑な並べ替えのニーズに非常に便利で、たとえば、次を使用して複数の注文を連鎖させることができます。 compound(). 。読む 注文の説明 多くのための!

私はいつもそれが最も可能性の高い自分で書くことができるものよりも良くなるので、Apacheのコモンズを使用することをお勧めします。プラス、あなたはその後、改革というし、「本当の」作業を行うことができます。

あなたが興味を持っているクラスは、<のhref = "http://commons.apache.org/collections/api-release/org/apache/commons/collections/comparators/NullComparator.html" のrel = "noreferrerです"タイトル=" NullComparator ">ヌルコンパレータに。それはあなたがヌルが高いか低い行うことができます。あなたはまた、それを二つの値がnullでないときに使用する独自のコンパレータを与えます。

あなたのケースでは、比較を行い、その後、あなたのcompareTo方法はちょうどそれを参照する静的メンバ変数を持つことができます。

のように

気にいら

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

}

null要素thatcontainリストを注文するとき、それは非常に便利ですので、

あなたが独自のロールすることを決定した場合であっても、心の中でこのクラスを維持します。

私は、あなたがnull値をサポートする必要があることを言ったので、それが直接、あなたの質問に答えられないことができることを知っています。

しかし、私はただのcompareToにヌルをサポートする公式の<のhref = "http://docs.oracle.com/javase/8/docs/api/java/langで説明したのcompareTo契約に沿ったものではないことに注意したいです匹敵の/Comparable.html#compareTo-T-」のrel = "noreferrer">のjavadocます:

  

ヌルは、任意のクラスのインスタンス、及びe.compareTo(NULL)でないことに注意してください   NullPointerExceptionがスローする必要がありますにもかかわらずe.equals(ヌル)を返します   偽ます。

だから私は明示的にNullPointerExceptionがスローういずれか、またはちょうどそれがnull引数が間接参照されている最初の時間をスローさせます。

あなたは、メソッドを抽出することができます:

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

}

クラスを不変になるように設計することもできます (Effective Java 2nd Ed.これに関する素晴らしいセクション、項目 15 があります。変更可能性を最小限に抑え、構築時に null が可能でないことを確認します (そして、 null オブジェクト パターン 必要に応じて)。そうすれば、これらのチェックをすべてスキップして、値が null ではないと安全に想定できます。

私は似たような探していたと私はこれをしなかったので、これは少し複雑に見えました。私はそれを理解することが少し簡単だと思います。あなたは、コンパレータとして、または1つのライナーとして使用することもできます。この質問のためにあなたがcompareToIgnoreCaseに変化するであろう()。であるとして、ヌルが浮上します。あなたは、1を反転させることができます-1あなたがそれらをシンクするかどうます。

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

私たちは、オブジェクト間のヌルフレンドリーcomparasionを行うために、Java 8を使用することができます。 私は2つのフィールドを持つ少年クラスHAVAはず:文字列の名前と整数の年齢をし、両方が等しい場合、私は最初の名前と、その後年齢を比較したい。

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

と結果:

    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

スプリングを使用した場合、誰には、同様にあなたのためにこれを行うクラスorg.springframework.util.comparator.NullSafeComparatorがあります。ただ、このようにそれを使用して独自に匹敵を飾る

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


    }

}

出力は

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]

NullSafeコンパレータを使用しての簡単な方法の一つには、以下に、そのスプリングの実装を使用することである参照する単純な例の一つである

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

別のApache ObjectUtils例。他のタイプのオブジェクトを並べ替えることができます。

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

これは私が私のArrayListをソートするために使用私の実装です。ヌルクラスは最後に並べ替えられています。

私の場合のために、EntityPhoneはEntityAbstractを拡張し、私のコンテナがList です。

「compareIfNull()」メソッドは、ヌル安全選別するために使用されます。他の方法はcompareIfNullを使用することができるかを示す、完全性のためである。

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

あなたはヌル(文字列の常に良いアイデアを)持っていないデータを知っているし、データはあなたが知っていれば、のあなたはまだ、実際の値を比較する前に、3つの比較を行っている、本当に大きくて、特定のケースでは確かこれは、あなたが少しビットを最適化することができます。のあなたのケースです。読み取り可能なコードとしてメーリングリストへは、マイナーな最適化の切り札ます:

        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);
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top