183

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 : Eddie の権利。上記の「両方の名前が null」の場合を修正

採用された回答について

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

2009 Apache Commons Collections のものと 2013 Guava のもので、どちらも私が投稿したサードパーティのライブラリ ソリューションもあります。

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

4

17 に答える 17

232

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

result = ObjectUtils.compare(firstComparable, secondComparable)
于 2012-04-05T09:29:22.743 に答える
21

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


これが私が最終的に行ったものです。null セーフな文字列比較のためのユーティリティ メソッドが既にあることがわかったので、最も簡単な解決策はそれを利用することでした。(これは大きなコードベースです。この種のものを見逃すのは簡単です:)

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) { ... }

したがって、これは本質的にエディの答えと同じです(静的ヘルパーメソッドをコンパレーターとは呼びませんが)、uzhinの答えも同じです。

とにかく、一般的に、可能な限り確立されたライブラリを使用することは良い習慣だと思うので、私はパトリックのソリューションを強く支持したでしょう。( 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。Commons はジェネリックをサポートしていないため、チェックされていない割り当てがあります。)

更新 (2013): Guava バージョン

ほぼ 5 年後、元の質問にどのように取り組むかを以下に示します。Java でコーディングする場合、(もちろん) Guavaを使用します。(もちろん、 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()唯一の Guava 固有のものを使用しています。このバージョンは、依存関係として Commons Collections よりも Guava の方が望ましいという単純な理由で推奨されます。(誰もが同意するように。)

について疑問に思っている場合はOrdering、実装していることに注意してくださいComparator。特により複雑な並べ替えが必要な場合に非常に便利で、たとえば を使用して複数の Ordering を連鎖させることができますcompound()詳細については、注文の説明をお読みください。

于 2009-02-01T11:35:40.940 に答える
4

You could design your class to be immutable (Effective Java 2nd Ed. has a great section on this, Item 15: Minimize mutability) and make sure upon construction that no nulls are possible (and use the null object pattern if needed). Then you can skip all those checks and safely assume the values are not null.

于 2009-01-26T23:30:51.630 に答える
2

Java 8 を使用して、オブジェクト間の null フレンドリーな比較を行うことができます。String name と Integer age の 2 つのフィールドを持つ Boy クラスがあり、最初に名前を比較してから、両方が等しい場合は年齢を比較したいとします。

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
于 2016-03-18T18:24:19.710 に答える
2

私は似たようなものを探していましたが、これは少し複雑に思えたので、これを行いました。もう少し分かりやすいと思います。コンパレータとして、またはワンライナーとして使用できます。この質問では、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);
    }
}
于 2014-10-07T16:38:53.273 に答える
2

Spring を使用している人のために、これを行うクラス 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

于 2016-01-19T12:33:46.090 に答える
1

データに null がないことがわかっており (常に文字列の場合は良い考えです)、データが非常に大きい場合、実際に値を比較する前に 3 つの比較を行っています。少し最適化できます。読み取り可能なコードとしての YMMV は、マイナーな最適化に勝ります。

        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);
于 2016-07-12T02:40:55.637 に答える
0

これは、ArrayList をソートするために使用する実装です。null クラスは最後にソートされます。

私の場合、EntityPhone は EntityAbstract を拡張し、コンテナーは List < EntityAbstract> です。

「compareIfNull()」メソッドは、null セーフ ソートに使用されます。他のメソッドは完全を期すためのもので、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;
}
于 2016-03-15T18:43:38.833 に答える
0

別の 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());
}
于 2015-08-11T15:39:24.760 に答える