문제

나를 구현하 compareTo() 방법은 간단한 클래스 등이(용할 수 있는 Collections.sort() 및 기타 케이크를 제공하는 자바 플랫폼):

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's right;고정"이름을 둘이 null 입니다"위의 경우

에 대한 수락 응답

나는 이 질문에 다시,2009 년에 자바 1.6 의 과정,그리고 시간 순수한 JDK 솔루션 Eddie 내 preferred 받아 대답이다.나는 결코 라운드를 변화는 지금까지(2017 년).

거기에는 또한 3rd 파티 솔루션 라이브러리—2009Apache Commons 컬렉션 중 하나와 2013 년 구아바나,두 게시여 나는 않았아보세요 어떤 시점에서 시간입니다.

내가 지금 청정 Java8 솔루션을 통해 루카스 빅토르 용 대답이다.는 확실히 해야하는 경우 우선적으로 고려 Java8,그리고 이러한 일 Java8 해야 사용할 수 있는 거의 모든 프로젝트입니다.

도움이 되었습니까?

해결책

사용 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 Safe Comparator를 구현할 것입니다. 구현이있을 수 있지만, 이는 항상 내 자신의 굴러 가기 위해 구현하기가 매우 간단합니다.

참고 : 위의 비교기, if 둘 다 이름은 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);
}

편집 : 코드 샘플에서 오타가 고정되었습니다. 그것이 내가 그것을 먼저 테스트하지 않은 것에 대해 얻는 것입니다!

편집 : NullSafestring Comparator를 홍보했습니다.

Guava를 사용한 업데이트 된 (2013) 솔루션에 대한이 답변의 하단을 참조하십시오.


이것이 내가 궁극적으로 함께한 것입니다. 우리는 이미 Null-Safe 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);
}

이것은 도우미가 정의되는 방식입니다 (원하는 경우 널이 먼저 또는 마지막으로 오는지 여부를 정의 할 수 있도록 과부하가 발생합니다).

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

이제 이것은 매우 우아합니다. (단 하나의 작은 문제는 남아 있습니다 : 커먼즈 NullComparator 제네릭을 지원하지 않으므로 확인되지 않은 과제가 있습니다.)

업데이트 (2013) : Guava 버전

거의 5 년 후, 내가 원래 질문을 어떻게 해결하는지 여기에 있습니다. Java로 코딩하는 경우, 나는 물론 구아바. (그리고 확실히 ~ 아니다 아파치 커먼즈.)

"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가 컬렉션을 공통적으로 의존적으로 선호하기 때문에 선호됩니다. (처럼 모두가 동의합니다.)

당신이 궁금하다면 Ordering, 그것을 구현합니다 Comparator. 더 복잡한 분류 요구에 특히 편리하므로 예를 들어 몇 가지 순서를 사용하여 사용합니다. compound(). 읽다 주문 설명 이상!

나는 항상 아파치 커먼즈를 사용하는 것이 좋습니다. 또한 당신은 다시 '실제'작업을 수행하고 재창조 할 수 있습니다.

당신이 관심있는 수업은입니다 널 비교기. 널을 높거나 낮게 만들 수 있습니다. 또한 두 값이 널이 아닌 경우 사용할 자신의 비교기를 제공합니다.

귀하의 경우 비교를 수행하는 정적 멤버 변수를 가질 수 있습니다. 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);
}

}

자신만의 굴러 가기로 결정 하더라도이 클래스를 명심하십시오. 그 목록이 널 요소를 주문할 때 매우 유용하기 때문에이 클래스를 염두에 두십시오.

널 값을 뒷받침해야한다고 말했기 때문에 그것이 당신의 질문에 직접 답변되지 않을 수 있습니다.

하지만 비교에서 널 지원하는 것은 공식적으로 설명 된 비교 계약과 일치하지 않는다는 점에 주목하고 싶습니다. 비교할 수있는 Javadoc:

NULL은 클래스의 인스턴스가 아니며 E.comPareto (NULL)는 E.Equals (NULL)가 False를 반환하더라도 NULLPOINTEREXCEPTion을 던져야합니다.

그래서 나는 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); 

}

당신은 당신의 수업을 불변으로 설계 할 수 있습니다 (효과적인 Java 2nd ed. 이것에 대한 훌륭한 섹션, 항목 15 : 돌연변이를 최소화). 널 객체 패턴 필요한 경우). 그런 다음 모든 검사를 건너 뛰고 값이 널이 아니라고 안전하게 가정 할 수 있습니다.

뭔가를 찾고 있었 유사하며 이는 듯 조금 복잡하므로 내가 이것을 했다.나는 그것이 조금 더 쉽게 이해할 수 있습니다.으로 사용할 수 있습니다 비교 측정기 또는 하나의 라이너입니다.이 질문에 대한 당신이 변화를 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);
    }
}

우리는 Java 8을 사용하여 물체 사이의 널 친화적 인 비교를 할 수 있습니다. 나는 문자열 이름과 정수 시대의 2 개의 필드를 가진 소년 클래스라고 생각했고, 먼저 이름을 비교하고 나이를 비교하고 싶다.

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 비교기 사용 IT의 스프링 구현을 사용하는 것입니다. 아래는 다음을 참조 할 간단한 예 중 하나입니다.

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 Obserutils 예제. 다른 유형의 객체를 정렬 할 수 있습니다.

@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를 확장하고 내 컨테이너는 <EntityAbStract> 목록입니다.

"CompareIfnull ()"메소드는 NULL SAFE 정렬에 사용됩니다. 다른 방법은 완전성을위한 것입니다.

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

데이터에 NULLS가 없다는 것을 알고있는 특정 사례의 경우 (항상 문자열에 대한 좋은 아이디어) 데이터가 실제로 크다는 경우 실제로 값을 비교하기 전에 여전히 세 가지 비교를하고 있습니다. 당신이 확실히 알고 있다면 이것이 당신의 경우입니다, 당신은 약간의 비트를 최적화 할 수 있습니다. 읽을 수있는 코드로서의 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);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top