문제

http://leepoint.net/notes-java/data/expressions/22compareobjects.html

equals ()를 정의하는 것은 사소한 것이 아닙니다.실제로 특히 서브 클래스의 경우 올바르게 얻는 것이 적당히 어렵습니다.이 문제에 대한 최상의 처리는 Horstmann의 핵심 Java Vol 1에 있습니다.

equals()를 항상 재정의해야 한다면 객체 비교를 수행해야 하는 궁지에 몰리지 않는 좋은 접근 방식은 무엇입니까?좋은 "디자인" 대안은 무엇입니까?

편집하다:

이것이 제가 의도한 대로 진행되고 있는지 잘 모르겠습니다.어쩌면 질문은 "왜 두 개체를 비교하고 싶습니까?" 해당 질문에 대한 귀하의 답변을 바탕으로 비교에 대한 대체 솔루션이 있습니까?내 말은, 동등의 다른 구현을 의미하는 것이 아닙니다.내 말은, 평등을 전혀 사용하지 않는다는 것입니다.제 생각에 핵심은 왜 두 개체를 비교하고 싶은가라는 질문부터 시작하는 것입니다.

도움이 되었습니까?

해결책

equals ()가 항상 무시되어야한다면, 객체 비교를 수행하지 않아도되는 좋은 접근법은 무엇입니까?

당신은 착각합니다.가능하면 같음을 재정의해야 합니다.


이 모든 정보는 다음에서 제공됩니다. 효과적인 Java, 제2판 (조쉬 블로크).이에 대한 첫 번째 버전 장은 여전히 ​​무료로 제공됩니다. 다운로드.

효과적인 Java에서:

문제를 피하는 가장 쉬운 방법은 평등 방법을 무시하지 않는 것입니다.이 경우 클래스의 각 인스턴스는 그 자체와 동일합니다.

Equals/hashCode를 임의로 재정의하는 문제는 상속입니다.일부는 다음과 같이 테스트하는 구현을 옹호합니다.

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

실제로, (3.4) Java 편집기는 소스 도구를 사용하여 메소드를 생성할 때 바로 이 작업을 수행합니다.Bloch에 따르면 이는 다음 사항을 위반하는 실수입니다. Liskov 대체 원리.

효과적인 Java에서:

Equals 계약을 보존하는 동안 즉각적인 클래스를 연장하고 값 구성 요소를 추가 할 수있는 방법은 없습니다.

평등 문제를 최소화하는 두 가지 방법은 다음과 같습니다. 클래스와 인터페이스 장:

  1. 상속보다 구성을 선호
  2. 상속을 위한 설계 및 문서화 또는 금지

내가 보기에 유일한 대안은 클래스 외부의 형식으로 동등성을 테스트하는 것이며, 이를 수행하는 방법은 유형의 디자인과 이를 사용하려는 컨텍스트에 따라 달라집니다.

예를 들어, 비교 방법을 문서화하는 인터페이스를 정의할 수 있습니다.아래 코드에서 서비스 인스턴스는 런타임에 동일한 클래스의 최신 버전으로 대체될 수 있습니다. 이 경우 다른 ClassLoader가 있으면 같음 비교는 항상 false를 반환하므로 같음/해시코드를 재정의하는 것은 중복됩니다.

public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

"왜 두 물체를 비교하고 싶나요?"

명백한 예는 두 개의 문자열이 동일한지(또는 두 개의 문자열이 같은지 테스트하는 것입니다. 파일, 또는 URI).예를 들어, 구문 분석할 파일 세트를 구축하려는 경우 어떻게 해야 합니까?정의에 따르면 세트에는 고유한 요소만 포함됩니다.자바의 세트 type은 해당 요소의 고유성을 강화하기 위해 equals/hashCode 메소드를 사용합니다.

다른 팁

나는 평등이 항상 무시되어야한다는 것이 사실이라고 생각하지 않습니다. 내가 이해하는 규칙은 동등한 객체를 정의하는 방법에 대해 명확한 경우에만 상락하는 것이 의미가 있다는 것입니다. 이 경우 HashCode ()를 무시하여 다른 해시 코드와 동등한 반환으로 정의한 개체가 없도록합니다.

의미있는 동등성을 정의 할 수 없다면 이점이 보이지 않습니다.

그냥 옳은 일은 어떻습니까?

Josh Bloch의 효과적인 Java에서 적용되는 지식 인 내 평등 템플릿은 다음과 같습니다. 자세한 내용은 책을 읽으십시오.

@Override
public boolean equals(Object obj) {
    if(this == obj) {
        return true;
    }

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }

MMHH

일부 시나리오에서는 객체를 수정할 수없는 (읽기 전용)로 만들 수 있고 단일 지점 (공장 메소드)에서 만들 수 있습니다.

동일한 입력 데이터 (생성 매개 변수)가있는 두 개의 객체가 필요하면 공장은 동일한 인스턴스 Ref를 반환 한 다음 "=="를 사용하면 충분합니다.

이 접근법은 특정 상황에서만 유용합니다. 그리고 대부분의 시대는 과도하게 보일 것입니다.

보세요 이 답변 그러한 것을 구현하는 방법을 알고 있습니다.

경고 그것은 많은 코드입니다

짧게 래퍼 클래스가 어떻게 작동하는지 확인하십시오 Java 1.5 이후

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

a == b 

사실입니다

new Integer( 2 ) == new Integer( 2 )  

거짓입니다.

내부적으로 참조를 유지하고 입력 값이 동일하다면 반환합니다.

아시다시피 정수는 읽기 전용입니다

그 질문에 관한 문자열 클래스와 비슷한 일이 발생합니다.

어쩌면 요점을 놓치고 있을지도 모르지만 자신만의 방법을 정의하는 대신 같음을 사용하는 유일한 이유는 다음과 같습니다. 다른 이름으로 그 이유는 많은 컬렉션(그리고 JDK의 다른 항목이나 요즘 소위 말하는 다른 항목)이 일관적인 결과를 정의하는 equals 메소드를 기대하기 때문입니다.하지만 그 외에도 같음에서 수행하려는 세 가지 종류의 비교를 생각할 수 있습니다.

  1. 두 개체는 실제로 동일한 인스턴스입니다.==를 사용할 수 있기 때문에 같음을 사용하는 것은 의미가 없습니다.또한 Java에서 어떻게 작동하는지 잊어버린 경우 수정해 주세요. 기본 equals 메소드는 자동으로 생성된 해시 코드를 사용하여 이 작업을 수행합니다.
  2. 두 개체에는 동일한 인스턴스에 대한 참조가 있지만 동일한 인스턴스는 아닙니다.이건 유용해요, 어, 가끔은...특히 지속형 개체이고 DB의 동일한 개체를 참조하는 경우 더욱 그렇습니다.이를 수행하려면 equals 메소드를 정의해야 합니다.
  3. 두 개체에는 동일한 인스턴스일 수도 있고 아닐 수도 있지만 값이 동일한 개체에 대한 참조가 있습니다. 즉, 계층 전체에서 값을 비교합니다.

두 개체를 비교하려는 이유는 무엇입니까?글쎄요, 만약 그들이 동등하다면, 여러분은 한 가지 일을 하고 싶을 것이고, 그렇지 않다면 여러분은 다른 일을 하고 싶을 것입니다.

즉, 현재 상황에 따라 다릅니다.

대부분의 경우 equals ()를 무시하는 주된 이유는 특정 컬렉션 내에서 중복을 확인하는 것입니다. 예를 들어, 세트를 사용하여 생성 한 객체를 포함하려면 객체 내에서 equals () 및 hashcode ()를 무시해야합니다. 맵에서 사용자 정의 객체를 키로 사용하려는 경우에도 동일하게 적용됩니다.

이것은 많은 사람들이 equals () 및 hashcode ()를 우선적으로 세트 또는 맵에 추가하는 데 실수를하는 것을 보았 기 때문에 중요합니다. 이것이 특히 교활 할 수있는 이유는 컴파일러가 불만을 제기하지 않기 때문에 동일한 데이터를 포함하지만 복제를 허용하지 않는 컬렉션에 다른 참조가있는 여러 객체로 끝날 수 있기 때문입니다.

예를 들어 단일 문자열 속성 '이름'이있는 이름 비안이라는 간단한 콩이있는 경우 각각 동일한 '이름'속성 값 (예 : "Alice")을 갖는 두 개의 NameBean (예 : namebean 및 name2) 인스턴스를 구성 할 수 있습니다. 그런 다음 Name1과 Name2를 세트에 추가 할 수 있으며 세트는 크기 1이 아닌 크기 2입니다. 마찬가지로 이름 Bean을 다른 객체에 매핑하기 위해 맵과 같은 맵이 있고 먼저 이름 1을 문자열 "First"에 매핑하고 나중에 name2를 문자열 "Second"에 매핑했습니다. 키/값 쌍이 모두 있습니다. 지도에서 (예 : name1-> "first", name2-> "second"). 따라서지도 조회를 수행하면 전달한 정확한 참조에 매핑 된 값을 반환합니다.

다음은 실행 결과가 나오는 구체적인 예입니다.

산출:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

암호:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}

평등은 논리의 기본이며 (정체성 법칙 참조), 그것 없이는 할 수있는 프로그래밍은 많지 않습니다. 당신이 쓰는 클래스의 사례를 비교할 때, 그것은 당신에게 달려 있습니다. 컬렉션에서 찾을 수 있거나 맵에서 열쇠로 사용해야하는 경우 평등 점검이 필요합니다.

Java에서 사소한 도서관 몇 개 이상을 작성했다면, 특히 가슴의 유일한 도구가있을 때 평등이 제대로되기 어렵다는 것을 알게 될 것입니다. equals 그리고 hashCode. 평등은 클래스 계층 구조와 밀접하게 결합되어 부서지기 쉬운 코드를 만듭니다. 또한이 방법은 유형 객체의 매개 변수를 가져 오기 때문에 유형 확인이 제공되지 않습니다.

평등을 확인하는 방법이 있습니다. 에서 기능적 자바 도서관, 당신은 찾을 수 있습니다 Equal<A> (그리고 해당 Hash<A>) 평등이 단일 클래스로 분리되는 곳. 작곡 방법이 있습니다 Equal 기존 인스턴스의 클래스 인스턴스와 컬렉션 랩퍼, 반복, 해시 맵, 그리고 해시 세트, 그 사용 Equal<A> 그리고 Hash<A> 대신에 equals 그리고 hashCode.

이 접근법의 가장 좋은 점은 요구 될 때 평등과 해시 방법을 쓰는 것을 결코 잊을 수 없다는 것입니다. 유형 시스템은 기억하는 데 도움이됩니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top