다음 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
경우에는 깨지지 않아야합니다.
마음에 떠오르는 해결책은 다음과 같습니다 (여기서는 “guard 조항”을 사용하고 있지만 다른 사람들은 단일 리턴 포인트를 선호 할 수도 있지만 요점은 아닙니다) :
// 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”인 경우 수정
허용 된 답변
물론 Java 1.6에서 2009 년에이 질문을했습니다. 당시 Eddie의 순수한 JDK 솔루션 이 제가 가장 선호하는 대답이었습니다. 나는 지금까지 (2017)까지 그것을 바꾸지 못했습니다.
도 있습니다 제 3 자 라이브러리 솔루션 2009 아파치 코 몬즈 컬렉션 하나 2013 구아바 하나 -a, 모두에 의해 게시 나-것을 나는 어떤 시점에서 선호 않았다가.
이제 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 안전 비교기를 구현합니다. 구현이있을 수 있지만 구현하기가 너무 간단하여 항상 내 자신을 굴 렸습니다.
참고 : 위의 비교기 는 두 이름이 모두 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);
}
편집 : 코드 샘플의 오타가 수정되었습니다. 그것이 먼저 테스트하지 않은 결과입니다!
편집 : nullSafeStringComparator를 정적으로 승격했습니다.
답변
구아바를 사용한 업데이트 된 (2013) 솔루션에 대해서는이 답변의 하단을 참조하십시오.
이것이 내가 궁극적으로했던 것입니다. 우리는 이미 널 안전 문자열 비교를위한 유틸리티 메소드를 가지고 있었으므로 가장 간단한 해결책은 그것을 사용하는 것입니다. (이것은 큰 코드베이스입니다. 이런 종류를 놓치기 쉽습니다 🙂
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) { ... }
따라서 이것은 본질적으로 Eddie의 대답 (정적 도우미 메서드를 비교 자라고 부르지는 않지만 )과 uzhin의 답변 과 동일 합니다 .
어쨌든, 가능한 한 확립 된 라이브러리를 사용하는 것이 좋은 습관이라고 생각하기 때문에 Patrick의 솔루션을 강력하게 선호했을 것 입니다. ( 라이브러리를 알고 사용하십시오 조쉬 블로흐가 말하는대로.)하지만이 경우 그 깨끗한, 간단한 코드를 굴복 않았을 것이다.
편집 (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) : 구아바 버전
거의 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에 대한 종속성으로 선호되기 때문에 간단합니다. ( 모두 동의 .)
궁금한 Ordering
점이 있으면 구현한다는 점에 유의하십시오 Comparator
. 더 복잡한 정렬 요구에 특히 편리합니다. 예를 들어을 사용하여 여러 주문을 연결할 수 있습니다 compound()
. 자세한 내용은 주문을 읽으십시오 !
답변
필자는 아파치 커먼즈를 사용하는 것이 좋습니다. 아파치 커먼즈는 스스로 작성할 수있는 것보다 낫기 때문입니다. 또한 재창조보다는 ‘실제’작업을 수행 할 수 있습니다.
관심있는 클래스는 Null Comparator 입니다. 널을 높이거나 낮출 수 있습니다. 또한 두 값이 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 요소를 포함하는 목록을 주문할 때 매우 유용하기 때문입니다.
답변
null 값을 지원해야한다고 말했기 때문에 귀하의 질문에 직접 대답하지 못할 수도 있습니다.
그러나 compareTo에서 null 지원 은 Comparable 공식 javadocs에 설명 된 compareTo 계약과 일치하지 않습니다 .
null은 어떤 클래스의 인스턴스도 아니며 e.compareTo (null)은 e.equals (null)이 false를 반환하더라도 NullPointerException을 발생시켜야합니다.
따라서 NullPointerException을 명시 적으로 throw하거나 null 인수가 역 참조 될 때 처음으로 throw되도록합니다.
답변
방법을 추출 할 수 있습니다.
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);
}