[Java] String 비교 방법 (equals, == 연산자, compare)

2021. 11. 17. 19:40☕️ Java

신입 수습기간에 자바를 거의 모른채로 spring 프로젝트를 진행한 적이 있습니다.

당시 아래 예시처럼 string 객체를 생성해놓고 문자열을 비교했는데, 의도한 결과와 다른 값이 나와 온통 println() 투성이인 코드를 짠 기억이 납니다.

이제는 말할 수 있습니다 둘의 차이를!

 

String strAA = new String("AA");

if (strAA == "AA") {
	System.out.println("its same");
} else {
	System.out.println("using '==' , its not same"); // 해당 라인 출력
}

if (strAA.equals("AA")) {
	System.out.println("using equals(), now its same"); // 해당 라인 출력
}

 

차이점

equals() 는 객체간의 값(value)을 비교할 수 있고,  == 은 대상의 주소(reference)를 비교합니다.

이게 무슨 뜻인지 코드와 그림으로 확인해보겠습니다. 

 

동일한 benz 라는 값이 서로 다른 주소공간에 저장된다.

String car = "benz";
String vehicle = car;
String car2 = new String("benz");

System.out.println("vehicle => " + vehicle);  // benz
System.out.println("vehicle.equals(car) => " + vehicle.equals(car)); // true
System.out.println("car.equals(car2) => " + car.equals(car2)); // true

System.out.println(car == vehicle); // true
System.out.println(car == car2); // false

먼저 car 변수는 주소 100을 할당받는데, 여기엔 'benz' 라는 값이 저장돼 있습니다.

vehicle 변수에 car 를 할당하는데, 두 변수는 결국 같은 주소를 바라보게된다. 그리고 이 주소엔 'benz' 라는 값이 저장돼 있습니다.

 

반면 car2 변수는 new String()을 이용하여 새로운 객체를 리턴하며, 이전의 두 변수와는 다른 주소 200을 할당받습니다. 

(여기서 주소 100, 200은 편의상 표현한 것 뿐이다)

 

== 연산자는 두 객체의 주소를 비교한다고 밝혔습니다.

주소 100을 가르키는 car 와 vehicle 를 == 연산자로 비교하면, 동일한 주소를 가르키기에 True 를 리턴합니다.

 

서로 다른 주소를 가르키는 car 와 car2 를 == 연산자로 비교하면 False 를 리턴합니다.

 

 

 

equals() 메서드의 두 얼굴

한 가지 짚고 갈 점이 있다. 우리가 이번 포스트에서 다루는 equals() 메소드는 String 클래스의 메서드임을 밝힌다. Object 클래스의 equals() 메소드를 사용하면 결과가 달라지기 때문에 이 점을 유의해야한다.

그럼 Object 클래스의 equals() 메서드는 언제 사용되느냐? 당연히 .equals() 앞의 객체가 Object 타입일 때 해당된다.

.equals() 앞의 객체가 String 타입이면 당연히 String 타입의 equals() 메소드가 호출된다.

그게 뭐 그리 대수인지 되물을 수 있지만,  동일한 이름의 메소드임에도 둘의 내부 구현이 다르기 때문에 차이가 발생한다.

 

Object.equals()

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

Object 클래스의 equals 메소드는, == 연산자로 두 객체를 비교한다. 즉 두 객체의 주소값이 같으면 true 를 , 그렇지 않으면 false 를 리턴합니다. 이 경우 true 를 리턴하기 위해선, 주소값이 같은 객체를 비교해야하며, 이는 곧 동일한 객체인지의 여부를 판별하는 것과 상통합니다.

 

코드로 확인해보겠습니다. 

public class StringEquals {

    public String name;

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

    public static void main(String[] args) {

        StringEquals str1 = new StringEquals("객체");
        StringEquals str2 = new StringEquals("객체");

        System.out.println(str1.equals(str2)); // >>> false
    }
}

str1 과 str2 는 name 필드가 "객체" 인 같은 값을 가지지만, 엄연히 다른 객체 입니다. 서로 다른 객체는 상이한 주소값을 가지며, 이는 == 연산자로 비교할 때 false 를 리턴 받습니다.

 

 

 

String.equals()

 

// String.java
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (!COMPACT_STRINGS || this.coder == aString.coder) {
                return StringLatin1.equals(value, aString.value);
            }
        }
        return false;
    }

 

위는 String 클래스의 equals() 메서드가 구현된 코드입니다. Object 의 equals() 메서드와 공통점은, 우선적으로 == 연산자를 이용해 같은 주소값을 가르키는지 판별합니다. 같은 주소를 가르키지 않을 땐, 두 객체의 내용을 비교하기 위해 아래의 if  조건문을 태운다. 두 String 값을 비교하기 위해 서로의 bytecode 를 비교하여 동일 여부를 판단합니다.

 

 

 

String 객체가 가지는 내용(값)을 확인하기 위해 아래 코드를 보겠습니다. 

String 자료형은 println() 메소드내에서 자동으로 toString() 메소드를 호출하며 해당 객체가 가지는 리터럴 값을 리턴합니다.

그렇기 때문에 위의 서로 다른 그렇기 때문에 위의 서로 다른 string1, string2, string3 는 같은 리터럴 값 "객체"를 출력함을 확인할 수 있습니다. 는 같은 리터럴 값 "객체"를 출력함을 아래 코드에서 확인할 수 있습니다.

public class StringEquals {

    public static void main(String[] args) {

        String string1 = new String("객체");
        String string2 = new String("객체");
        String string3 = "객체";

        System.out.println(string1 + " " + string2 + " " +string3); // >>> 객체 객체 객체

    }
}

즉 string1, string2, string3 는 서로 다른 주소값을 가질지 모르지만, 같은 내용을 가지기에 equals() 메소드로 비교하면 모두 true 를 리턴하게 된다.

public class StringEquals {

    public static void main(String[] args) {

        String string1 = new String("객체"); 
        String string2 = new String("객체");
        String string3 = "객체";

        System.out.println(string1 == string2); // false
        System.out.println(string1 == string3); // false
        System.out.println(string2 == string3); // false

        System.out.println(string1.equals(string2)); // true
        System.out.println(string1.equals(string3)); // true
        System.out.println(string2.equals(string3)); // true

    }
}

 

String.equals()  사용 시 주의점 (feat.NullPointerException)

위 코드에서 만약 param 값이 null 이면 어떻게 될까요?

메서드의 파라미터로 String 타입을 받더라도 null 을 검증하는 부분이 없기 때문에 .equals() 메서드를 사용하면 NullPointerException이 터질 수 있습니다. ( equals() 의 인자는 null 이라도 NullPointerException 이 발생하지 않습니다.) 이럴 경우엔 위에서 언급한 Object.equals() 를 사용하여 두 객체를 비교할 수 있습니다. 그 이유는 Object.equals() 내부에서 자체적으로 param 값이 null 인지 체크하기 때문입니다.

 

마무리

결국 문자열을 비교할 땐 equals() 메소드를 사용하는 것이 바람직하다. 우리가 문자열을 비교하는 이유는 대부분, 동일한 리터럴 값을 가지냐를 판단하기 위해서이지, 동일한 주소값을 가지는지 파악하려는 것이 아니다.