.equals() VS == - 둘의 차이점

JP·2022년 2월 5일
0

자바

목록 보기
5/10

코드를 통해 .equals()== 를 이용한 값의 비교에 대해 알아보자

public class EqualsTutorial {
    public static void main(String[] args) {
        int a = 1;
        int b = 1;
        if(a == b) {
            System.out.println("두 값은 같다.");
        } else {
            System.out.println("두 값은 다르다.");
        }
    }
}

위 코드를 실행하면 "두 값은 같다." 라는 문장이 콘솔에 찍힐 것이다.

위의 코드에서는 int의 값을 ==를 통해 비교했고 정확히 동작할 것이다.
하지만 기본형 타입(primitive type)이 아닌 참조형 타입(reference type) 의 경우는 어떻게 해야할까?

자바에서는 String 이라는 클래스가 존재한다.
기본형은 아니지만 여전히 simple한 유형으로 char들의 array형태이다.

String string1 = new String("hello");
String string2 = new String("hello");
if(string1 == string2) {
    System.out.println("두 값은 같다.");
} else {
    System.out.println("두 값은 다르다.");
}

위 코드를 실행 할 경우 "두 값은 다르다." 라는 문장이 콘솔에 찍히게 된다.

.equals()로 비교해보자.

String string1 = new String("hello");
String string2 = new String("hello");
if(string1.equals(string2)) {
    System.out.println("두 값은 같다.");
} else {
    System.out.println("두 값은 다르다.");
}

이번에는 "두 값은 같다." 라는 문장이 찍히게 된다.

분명 같은 비교인데도 == 로 비교했을 때와 .equals()로 비교했을 때 차이가 존재했다. 왜 그럴까?

둘의 차이점

우리가 ==를 통해 비교를 할 때, 자바는 ==의 앞의 값과 뒤의 값의 메모리 주소값을 비교하게 된다.

String string1 = new String("hello");
String string2 = new String("hello");

우리가 String string1 = new String("hello")를 통해 새로운 객체를 생성하고 이는 고유의 메모리 주소를 갖게 된다.
string2의 객체를 생성할때도 마찬가지이다.

string1 과 string2 가 위치해 있는 메모리 주소가 다르기에 우리가 ==를 통해 두 객체를 비교한다면 false 값을 얻게 되는 것이다.

그렇다면 앞서에 비교했던 int 형 데이터 타입에서는 어떻게 ==를 이용해 값을 비교하고 true를 얻을 수 있던 것일까?

이는 바로 자바의 기본형 데이터 타입(primitive type - int, float , double, short, long , char, byte, boolean)들은 메모리에 위치해있는 주소값이 아닌 값의 실제 값을 가지고 비교를 하기 때문이다.

그렇기에 우리가 기본형 데이터 타입을 비교할 때는 == 를 이용한 비교를 할 수 있는 것이고
String을 포함한 객체들의 실제 값을 비교할 때는 .euqlas()를 통한 비교를 해야 하는 것이다.

String.equals()를 조금 더 살펴보면

String.equals() 메서드를 조금 더 살펴보면

 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                  : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }

우선, 두 String 객체가 같은 메모리 주소를 참조하는지 비교한다
그 다음 StringUTF16.equals(value, aString.value) 메서드를 통해 값을 비교하는데
value는 byte[]를 말한다.

StringUTF16.equals 는

public static boolean equals(byte[] value, byte[] other) {
        if (value.length == other.length) {
            int len = value.length >> 1;
            for (int i = 0; i < len; i++) {
                if (getChar(value, i) != getChar(other, i)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

와 같이 이루어져있다.
String의 모든 char를 가지고와 비교하여 true 혹은 false를 반환한다.

.equals()로 객체 비교해보기

.euqlas()를 이용한 객체값 비교를 위해 Member 클래스를 만들었다.

public class Member {

    private Long id;
    private String name;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    
}

그리고 아래와 같이 member1 과 member2를 만들어 비교를 해보자.

        Member member1 = new Member();
        Member member2 = new Member();
        if(member1.equals(member2)) {
            System.out.println("두 값은 같다.");
        } else {
            System.out.println("두 값은 다르다.");
        }

콘솔에 찍히는 값은 "두 값은 다르다" 이다.
왜 그럴까?

이유는 바로 equals 메서드는 아직 Member 클래스에 implemented 되지 않아서이기 때문이다.
그러면 우린 자바 최상위 클래스인 Object의 .equals() 를 사용하게 되는데
Object.class에 정의되어 있는 .euqlas() 메서드는

public boolean equals(Object obj) {
        return (this == obj);
    }

와 같이 == 를 이용하여 메모리 주소값을 비교하기 때문이다.

따라서 euqlas 메서드를 Member 클래스에 재정의 해주어야 한다.

@Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Member other = (Member) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

이후 위에서 진행했던

        Member member1 = new Member();
        Member member2 = new Member();
        if(member1.equals(member2)) {
            System.out.println("두 값은 같다.");
        } else {
            System.out.println("두 값은 다르다.");
        }

코드를 다시 실행해보면 분명히 "두 값은 같다." 라는 로그를 볼 수 있다.

String 은 이상하다

위의 예제들에서 우리는 String을 생성할 때 new String("hello")
처럼 new를 이용한 새로운 객체를 생성해냈다.

하지만 일반적으로 우리는 String 객체를 정의할 때 다음과 같이 한다.

String a = "hello";
String b = "hello";
if(a == b) {
    System.out.println("두 값은 같다.");
} else {
    System.out.println("두 값은 다르다.");
}

위 코드를 실행해보면 "두 값은 같다" 라는 결과를 얻게 된다.
?!
분명히 위의 코드를 통한 설명을 보았을 때
== 를 통한 비교는 메모리 주소값,
.equals() 를 통한 비교는 실제 값이라고 하였다.

String a 와 String b 는 서로 다른 객체이기 때문에 서로 다른 메모리 주소를 가지고 있을텐데? 라는 의문점이 들지만, 이는 자바가 가진 언어적 특성때문이다.

String a 를 "hello"를 이용하여 고정된(literal) 값을 만든다면, 자바는 이 값을 메모리의 한쪽에 저장해두고 String b = "hello" 라고 b를 또 다시 만들 때 b또한 a가 가지고 있는 메모리 주소를 참조하게끔 하기 때문이다.

이를 자바에서는 interning 이라고 부른다.

interning 이란, 내용이 같은 모든 문자열이 동일한 메모리를 공유하는 것을 말한다.
이는 자바에서 메모리를 절약하기 위해서 사용되는 전략으로 같은 문자열을 가진 리터럴들에 관해 하나의 값만 참조하는 것이다!

하지만 우리가 new String("hello") 처럼 String을 선언한다면 위의 interning을 전혀 사용하지 못하게 된다.

따라서 String은 리터럴값으로 선언하는게 옳은 방법이겠다.

결론

String 을 보더라도 같은 값("hello") new 를 통한 객체를 선언과 리터럴을 이용한 값을 만드는 것에 따라 ==.equals() 의 결과가 다르다.

따라서 기본형 타입(primitive type - int, float , double, short, long , char, byte, boolean)에서는 ==를,

기본형을 제외한 모든 참조형 타입(reference type)에서는 .euqlas() 를 사용하자.

profile
to Infinity and b

0개의 댓글