1. 소개

객체 비교는 객체 지향 프로그래밍 언어의 필수 기능입니다.

이 예제에서는 객체를 비교할 수있는 Java 언어의 일부 기능을 살펴 보겠습니다. 또한 외부 라이브러리에서 이러한 기능을 살펴볼 것입니다.

2. ==! = 연산자

두 개의 Java 객체가 각각 동일한 지 여부를 알 수 있는 ==! = 연산자 부터 시작하겠습니다 .

2.1. 기초 요소

기본 유형의 경우 동일하다는 것은 동일한 값을 갖는 것을 의미합니다.

assertThat(1 == 1).isTrue();

자동 언 박싱 덕분에 기본 값을 래퍼 유형 대응 항목과 비교할 때도 작동합니다 .

Integer a = new Integer(1);
assertThat(1 == a).isTrue();

두 정수의 값이 서로 다른 경우 == 연산자는 false를 반환 하고 ! = 연산자는 true를 반환 합니다 .

2.2. 사물

동일한 값을 가진 두 개의 Integer 래퍼 유형 을 비교한다고 가정 해 보겠습니다 .

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a == b).isFalse();

두 개체를 비교하면 해당 개체의 값은 1이 아닙니다. 두 개체가 모두 new 연산자를 사용하여 생성 되었기 때문에 스택메모리 주소 가 다릅니다 . ab를 할당했다면 다른 결과를 얻었을 것입니다.

Integer a = new Integer(1);
Integer b = a;

assertThat(a == b).isTrue();

이제 Integer # valueOf 팩토리 메서드를 사용할 때 어떤 일이 발생하는지 살펴 보겠습니다 .

Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);

assertThat(a == b).isTrue();

이 경우 동일한 것으로 간주됩니다. 이는 valueOf () 메서드 가 동일한 값으로 너무 많은 래퍼 객체를 생성하지 않도록 Integer 를 캐시에 저장 하기 때문 입니다. 따라서 메서드는 두 호출에 대해 동일한 Integer 인스턴스를 반환 합니다.

Java는 String대해서도이 작업을 수행합니다 .

assertThat("Hello!" == "Hello!").isTrue();

그러나 new 연산자를 사용하여 생성 된 경우 동일하지 않습니다.

마지막으로 두 개의 null 참조는 동일한 것으로 간주되지만 null아닌 개체는 null다른 것으로 간주됩니다 .

assertThat(null == null).isTrue();

assertThat("Hello!" == null).isFalse();

물론 같음 연산자의 동작은 제한적일 수 있습니다. 서로 다른 주소에 매핑 된 두 개체를 비교하고 내부 상태에 따라 동일한 것으로 간주하려면 어떻게해야합니까? 다음 섹션에서 방법을 살펴 보겠습니다.

3. Object # equals 메서드

이제 equals () 메서드를 사용하여 더 넓은 개념의 평등에 대해 이야기 해 보겠습니다 .

이 메소드는 모든 Java 객체가 상속하도록 Object 클래스에 정의되어 있습니다. 기본적 으로 구현은 객체 메모리 주소를 비교하므로 == 연산자 와 동일하게 작동합니다 . 그러나 객체에 대해 평등이 의미하는 바를 정의하기 위해이 메서드를 재정의 할 수 있습니다.

먼저 Integer 와 같은 기존 객체에 대해 어떻게 작동하는지 살펴 보겠습니다 .

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a.equals(b)).isTrue();

이 메서드는 두 개체가 동일 할 때 여전히 true를 반환 합니다 .

메서드의 인수로 null 객체를 전달할 수 있지만 물론 메서드를 호출하는 객체로 는 전달할 수 없습니다.

우리는 우리 자신의 객체와 함께 equals () 메소드를 사용할 수 있습니다 . Person 클래스 가 있다고 가정 해 보겠습니다 .

public class Person {
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

내부 세부 사항을 기반으로 Person을 비교할 수 있도록이 클래스 equals () 메서드를 재정의 할 수 있습니다 .

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person that = (Person) o;
    return firstName.equals(that.firstName) &&
      lastName.equals(that.lastName);
}

자세한 내용은이 주제에 대한 기사를 참조하십시오 .

4. Objects # equals 정적 메서드

이제 Objects # equals 정적 메서드를 살펴 보겠습니다 . 앞에서 첫 번째 개체의 값으로 null사용할 수 없다고 언급했습니다. 그렇지 않으면 NullPointerException 이 발생합니다.

Objects 도우미 클래스 equals () 메서드 는 이러한 문제를 해결합니다. 두 개의 인수를 사용하여 비교하고 널값 도 처리 합니다.

Person 객체를 다시 비교해 봅시다 :

Person joe = new Person("Joe", "Portman");
Person joeAgain = new Person("Joe", "Portman");
Person natalie = new Person("Natalie", "Portman");

assertThat(Objects.equals(joe, joeAgain)).isTrue();
assertThat(Objects.equals(joe, natalie)).isFalse();

앞서 말했듯이이 메서드는 null 값을 처리 합니다. 따라서 두 인수가 모두 null 이면 true 를 반환 하고 그중 하나만 null이면 false 를 반환 합니다.

이것은 정말 편리 할 수 ​​있습니다. Person 클래스에 선택적 생년월일을 추가한다고 가정 해 보겠습니다 .

public Person(String firstName, String lastName, LocalDate birthDate) {
    this(firstName, lastName);
    this.birthDate = birthDate;
}

그런 다음 equals () 메서드 를 업데이트해야 하지만 null 처리를 사용합니다. equals () 메소드에 다음 조건을 추가하여이를 수행 할 수 있습니다 .

birthDate == null ? that.birthDate == null : birthDate.equals(that.birthDate);

그러나 클래스에 nullable 필드를 많이 추가하면 정말 지저분해질 수 있습니다. equals () 구현 에서 Objects # equals 메소드를 사용하면 훨씬 깔끔하고 가독성이 향상됩니다.

Objects.equals(birthDate, that.birthDate);

5. 비슷한 인터페이스

비교 논리를 사용하여 특정 순서로 개체를 배치 할 수도 있습니다. 필적 인터페이스 객체 간의 순서를 정의 할 수있게 해준다 객체가 다른 것보다,보다 같거나 작은지를 판정함으로써.

필적 인터페이스는 일반적이며 단지 하나에있어서, 보유 compareTo와 () 일반적인 타입의 인자를 반환하고, 지능 . 경우, 리턴 값은 음수 다르게 인자, 0가 동일한 경우, 긍정적보다 낮다.

Person 클래스에서 Person 객체를 성 을 기준으로 비교하려고 한다고 가정 해 보겠습니다 .

public class Person implements Comparable<Person> {
    //...

    @Override
    public int compareTo(Person o) {
        return this.lastName.compareTo(o.lastName);
    }
}

은 compareTo () 메소드는 네가티브 리턴 지능 로 호출 될 경우 사람 보다 큰 성을 갖는 경우 동일한 성 및 포지티브 그렇지 제로.

자세한 내용은이 주제 에 대한 기사를 참조하십시오 .

6. 비교기 인터페이스

비교기 인터페이스는 일반적인이며,이 비교 하는 일반적인 유형의 두 인수를하고 반환 방법 정수 . 우리는 이미 Comparable 인터페이스로 그 패턴을 이미 보았습니다 .

비교기 는 비슷합니다. 그러나 클래스의 정의와는 분리되어 있습니다. 따라서 하나의 Comparable 구현 만 제공 할 수있는 클래스에 대해 원하는 만큼의 비교기를 정의 할 수 있습니다 .

테이블보기에 사람을 표시하는 웹 페이지가 있고 사용자에게 성이 아닌 이름으로 정렬 할 수있는 가능성을 제공한다고 가정 해 보겠습니다. 현재 구현을 유지하려는 경우 Comparable로는 불가능 하지만 자체 Comparators를 구현할 수 있습니다.

이름으로 만 비교 하는 Person Comparator만들어 보겠습니다 .

Comparator<Person> compareByFirstNames = Comparator.comparing(Person::getFirstName);

이제 해당 Comparator를 사용하여 사람 List정렬 해 보겠습니다 .

Person joe = new Person("Joe", "Portman");
Person allan = new Person("Allan", "Dale");

List<Person> people = new ArrayList<>();
people.add(joe);
people.add(allan);

people.sort(compareByFirstNames);

assertThat(people).containsExactly(allan, joe);

compareTo () 구현 에서 사용할 수 있는 Comparator 인터페이스 에는 다른 메서드 가 있습니다 .

@Override
public int compareTo(Person o) {
    return Comparator.comparing(Person::getLastName)
      .thenComparing(Person::getFirstName)
      .thenComparing(Person::getBirthDate, Comparator.nullsLast(Comparator.naturalOrder()))
      .compare(this, o);
}

이 경우 먼저 성을 비교 한 다음 이름을 비교합니다. 그런 다음 생년월일을 비교하지만 nullable 이므로 이를 처리하는 방법을 말해야 하므로 자연 순서에 따라 비교해야하지만 null 값이 마지막으로 유지된다는 두 번째 인수를 제공합니다 .

7. 아파치 커먼즈

이제 Apache Commons 라이브러리를 살펴 보겠습니다 . 먼저 Maven 의존성을 가져옵니다 .

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>

7.1. ObjectUtils # notEqual 메서드

먼저 ObjectUtils # notEqual 메서드 에 대해 이야기 해 보겠습니다 . 고유 한 equals () 메서드 구현 에 따라 두 개의 Object 인수를 사용하여 같지 않은지 확인합니다 . 또한 null 값을 처리 합니다.

문자열 예제를 재사용 해 보겠습니다 .

String a = new String("Hello!");
String b = new String("Hello World!");

assertThat(ObjectUtils.notEqual(a, b)).isTrue();

또한 주목해야한다 ObjectUtils가 갖는다 등호 () 방법. 그러나 Objects # equals가 나타나면 Java 7부터 사용되지 않습니다.

7.2. ObjectUtils # compare 메서드

이제 ObjectUtils # compare 메서드 와 개체 순서를 비교해 보겠습니다 . 해당 제네릭 유형의 Comparable 인수 두 개를 취하고 Integer를 반환하는 제네릭 메서드입니다 .

문자열을 다시 사용하는 것을 보겠습니다 .

String first = new String("Hello!");
String second = new String("How are you?");

assertThat(ObjectUtils.compare(first, second)).isNegative();

기본적으로 메서드는 null 값을 더 큰 것으로 간주하여 처리합니다 . 부울 인수 를 사용하여 해당 동작을 반전하고 덜 고려하는 오버로드 된 버전을 제공합니다 .

8. 구아바

이제 Guava를 살펴 보겠습니다 . 먼저 의존성을 가져 오겠습니다 .

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

8.1. Objects # equal 메서드

Apache Commons 라이브러리와 유사하게 Google은 두 개체가 동일한 지 확인하는 방법 인 Objects # equal을 제공 합니다. 구현이 다르지만 동일한 결과를 반환합니다.

String a = new String("Hello!");
String b = new String("Hello!");

assertThat(Objects.equal(a, b)).isTrue();

더 이상 사용되지 않는 것으로 표시되지는 않았지만이 메서드의 JavaDoc은 Java 7에서 Objects # equals 메서드를 제공하므로 사용되지 않는 것으로 간주되어야한다고 말합니다 .

8.2. 비교 방법

이제 Guava 라이브러리는 두 개체를 비교하는 방법을 제공하지 않지만 (다음 섹션에서이를 달성하기 위해 수행 할 수있는 작업을 살펴 ​​보겠습니다) 원시 값을 비교하는 방법을 제공합니다 . Ints 도우미 클래스를 사용하여 compare () 메서드가 어떻게 작동 하는지 살펴 보겠습니다 .

assertThat(Ints.compare(1, 2)).isNegative();

평소와 같이 첫 번째 인수가 각각 두 번째 인수보다 작거나 같거나 큰 경우 음수, 0 또는 양수인 정수반환합니다 . bytes를 제외하고 모든 기본 유형에 대해 유사한 메소드가 존재 합니다 .

8.3. ComparisonChain 클래스

마지막으로 Guava 라이브러리는 비교 체인을 통해 두 개체를 비교할 수 있는 ComparisonChain 클래스를 제공합니다 . Person 객체를 성과 이름으로 쉽게 비교할 수 있습니다 .

Person natalie = new Person("Natalie", "Portman");
Person joe = new Person("Joe", "Portman");

int comparisonResult = ComparisonChain.start()
  .compare(natalie.getLastName(), joe.getLastName())
  .compare(natalie.getFirstName(), joe.getFirstName())
  .result();

assertThat(comparisonResult).isPositive();

기본 비교는 compareTo () 메서드를 사용하여 이루어 지므로 compare () 메서드에 전달 된 인수는 프리미티브 또는 Comparable 이어야합니다 .

9. 결론

이 기사에서는 Java에서 객체를 비교하는 다양한 방법을 살펴 보았습니다. 우리는 동일성, 동등성 및 순서의 차이를 조사했습니다. 또한 Apache Commons 및 Guava 라이브러리에서 해당 기능을 살펴 보았습니다.

평소처럼이 기사의 전체 코드는 GitHub 에서 찾을 수 있습니다 .