java, 동일성과 동등성

동일성과 동등성

  • 자바에서의 동일성이란 완전하게 같음을 의미한다. 변수에 할당된 값이 일치해야 한다. 동일성 비교는 == 을 사용한다.
    • 기본타입의 경우 값을 비교한다. 값이 같으면 true를 반환한다.
    • 참조변수는 주소가 같은 위치를 가리킴을 의미한다.
  • 동등성은 동일성보다 넓은 개념이다. 주소가 같지 않더라도 값이 동일할 경우 동등하다고 본다.
    • equals 메서드를 사용한다.
    • Object 클래스는 equals를 기본적으로 동일성 비교한다.
    • 다만 equals는 재정의 가능하다. equals와 hashcode를 재정의한 클래스는 동일성을 false로 반환하더라도 동등성을 true로 반환할 수 있다.
  • String은 객체지만 특별하다. 동등한 객체는 동시에 동일한 객체이다. 같은 문자열을 가진 String은 equals와 == 모두 true를 반환한다. 다만 new String() 을 사용할 경우 더 이상 동일성을 보장받지 못환다. 방어적으로 equals 메서드를 사용하는 것이 낫다.

기본변수의 비교

int i1 = 1;
int i2 = 1;
System.out.println("(i1==i2) = " + (i1==i2)); // true

참조변수의 비교

  • 아래는 두 개의 클래스가 있다.
  • Sample은 hashcode와 equals가 재정의 되어 있지 않다. 이 말은 Object가 정의한 내용 그대로 동일성을 비교한다.
  • SampleEquality는 equals와 hashcode를 동등성 비교를 할 수 있도록 재정의 하였다. name 필드가 동일하면 equals가 true를 반환한다.
    • 참고로 equals 및 hashcode를 동등성 비교를 위하여 오버라이딩할 경우, IDE가 해주는 그대로 사용하는 것을 권장한다. 이러한 방식이 사이드 이펙트를 최소화 한다. 아래 작성된 메서드는 인텔리제이가 만들어줬다.
class Sample{
    private String name;

    public Sample() {
    }

    public Sample(String name) {
        this.name = name;
    }
}

class SampleEquality{
    private String name;

    public SampleEquality() {
    }

    public SampleEquality(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SampleEquality that = (SampleEquality) o;
        return Objects.equals(name, that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}
  • 위의 객체를 비교하면 다음과 같다.
  • 특별한 오버라이딩이 없는 Sample은 equals와 ==의 리턴 값이 같음을 확인할 수 있다.
Sample sample1 = new Sample("lee");
Sample sample2 = sample1;
System.out.println("(sample1==sample2) = " + (sample1==sample2)); // true
System.out.println("(sample1 equals sample2) = " + (sample1.equals(sample2))); // true
Sample sampleA = new Sample("kim");
Sample sampleB = new Sample("kim");

System.out.println("(sampleA==sampleB) = " + (sampleA==sampleB)); // false
System.out.println("(sampleA equals sampleB) = " + (sampleA.equals(sampleB))); // false
  • equals를 재정의한 SampleEquality의 결과는 아래와 같다.
SampleEquality sampleA = new SampleEquality("kim");
SampleEquality sampleB = new SampleEquality("kim");
System.out.println("(sampleA == sampleB) = " + (sampleA == sampleB)); // false
System.out.println("(sampleA.equals(sampleB)) = " + (sampleA.equals(sampleB))); // true

HashMap과 HashSet은 어떻게 중복 여부를 판별하는가?

  • HashSet과 HashMap 등 자료구조는 중복 삽입을 허용하지 않는다. 이때 중복은 어떻게 판별하는가?
  • HashSet이 중복여부는 동일성과 동등성을 기준으로 한다. 동등성을 비교할 때는 메서드는 equals와 hashcode를 사용한다.

  • Sample은 값이 동일하더라도 equals를 false로 반환한다. 그러므로 다른 값으로 인식한다.
Set<Sample> samples = new HashSet<>();

Sample s1 = new Sample("kim");
Sample s2 = s1;
Sample s3 = new Sample("kim");
samples.add(s1);
samples.add(s2);
samples.add(s3);

System.out.println("samples.size() = " + samples.size()); // 2
  • SampleEquality은 name 객체가 같은 경우 equals가 true이다. 그러므로 같은 값으로 인식한다.
Set<SampleEquality> samples = new HashSet<>();

SampleEquality s1 = new SampleEquality("kim");
SampleEquality s2 = s1;
SampleEquality s3 = new SampleEquality("kim");
samples.add(s1);
samples.add(s2);
samples.add(s3);

System.out.println("samples.size() = " + samples.size()); // 1