Midnight Coder's Lounge

[Java] String 변수를 == 로 비교할 때 : 똑같은 값인데, 왜 false가 나오죠? 본문

Language

[Java] String 변수를 == 로 비교할 때 : 똑같은 값인데, 왜 false가 나오죠?

AtomicLiquors 2022. 8. 13. 22:57

요약


Q.

서로 다른 두 String 변수를 == 로 비교해 보았습니다. 분명히 똑같은 값인데 왜 false가 나오죠?

 

A.

String끼리 값을 비교할 땐 '=='를 쓰지 말고 '.equals()'를 쓰세요.

 


상세


String 변수 str1을 선언하고, "hello"라는 값을 저장했습니다.

또다른 String 변수 str2를 선언하고,  Scanner를 통해 값을 입력받도록 했습니다.

입력받은 str2의 값이 str1과 일치하면  "입력 내용 일치!"라는 메시지가 뜨게 하고 싶습니다.

 

코드: 

//str1과 str2 선언
String str1 = "hello";
		
Scanner sc = new Scanner(System.in);
String str2 = sc.next();


//str1과 str2를 비교하고 조건문 실행
if(str1==str2) {
	System.out.println("입력 내용 일치!");
}else {
	System.out.println("입력 내용이 일치하지 않습니다.");
}

//str1==str2가 true/false가 나오는지 확인
System.out.println(str1==str2);

 

그리고 콘솔에 똑같이 "hello"라고 입력했는데,

계속 "입력 내용이 일치하지 않습니다."라는 메시지만 뜹니다.

str1==str2를 직접 출력해 봤더니 결과가 false가 나오네요.

 

콘솔 :

hello
입력 내용이 일치하지 않습니다.
false

 

분명히 str1과 str2의 값이 같은데, true가 나와야 하지 않을까요?

왜 이렇게 되는 걸까요?

 


'.equals()'를 이용해 String의 값 비교하기


서로 다른 두 String 변수의 값을 비교하고 싶다면, '=='를 쓰지 말고 '.equals()'를 써야 합니다.

'.equals()'를 쓰도록 코드를 고쳐 보겠습니다.

 

코드:

String str1 = "hello";
		
Scanner sc = new Scanner(System.in);
String str2 = sc.next();

if(str1.equals(str2)) {
//'==' 대신 '.equals()'를 사용해 봅시다.
    System.out.println("입력 내용 일치!");
}else {
    System.out.println("입력 내용이 일치하지 않습니다.");
}

System.out.println(str1.equals(str2));

콘솔 :

hello
입력 내용 일치!
true

.equals()를 사용하니,

의도한 대로 "입력 내용 일치!" 메시지도 잘 출력되고,

str1==str2의 결과도 true가 나오게 됩니다.

 

 


'=='로 값을 비교해도 되는 경우  : String이 아닌 '기본형 변수'를 비교할 때


Q.

int를 갖고 똑같은 코드를 작성했을 땐, '=='로 값을 비교해도 잘 되던데요...

 

A.

그렇습니다.

만약에 String이 아니라, 

[int, byte, short, long] / [float, double] / [boolean]으로

비슷한 코드를 작성했다면 원래 의도대로 true를 얻을 수 있을 것입니다.

 

int로 예를 들어 봅시다. 

 

코드: 

int int1 = 25;
		
Scanner sc = new Scanner(System.in);
int int2 = sc.nextInt();
//정수형 변수 int1에 값 25를 넣고, 콘솔에 25를 입력해 봅시다.
//sc.nextLine(); 또한 sc.next();로 수정했습니다.

if(int1==int2) {
    System.out.println("입력 내용 일치!");
}else {
    System.out.println("입력 내용이 일치하지 않습니다.");
}

System.out.println(int1==int2);

 

콘솔 :

25
입력 내용 일치!
true

코드 수정 후 int1과 int2의 값이 일치하도록 입력해 보면

"입력 내용 일치!" 메시지도 잘 나오고,

int1==int2의 결과도 true가 나오네요.

 

왜 String은 그렇게 되지 않는 걸까요?

 

 


 

String은 '기본형'이 아닌 '참조형'


int, String 등 자료형data type의 구분에 대해서 자세히 알아보도록 합시다.

  • int는 기본형Primitive Type에 속하며, 실제 데이터를 저장하는 유형입니다.
    int 외에도 [byte, short, long] / [float, double] / [char] / [boolean]까지 총 8가지 유형이 '기본형'에 해당합니다.

자료출처 :https://loustler.io/languages/Java-primitive-type/

 

 

  • String은 참조형Reference Type으로, 실제 데이터가 아닌 데이터가 저장되어 있는 '주소'를 저장합니다.
    Java에서 String은 '객체Object'의 일종입니다.
String str1 = "hello";

위와 같이 새로운 String 변수 str1을 선언하고 연산자 '='로 문자열 "hello"를 대입했을 때,
str1에는 실제 문자열인 "hello"가 저장되는 것이 아니라,
그 "hello"가 존재하는 객체의 주소가 저장되는 것이지요.

 

Java 공식 문서의 String 정의. String은 java.lang.Object의 하위에 있는, 일종의 '객체'입니다.

 

 


 

기본형끼리는 "값"을, 참조형끼리는 "객체 주솟값"을 비교하는 '=='


비교 연산자 '=='는 기본형에 대해서는 "값"을 비교하고, 
참조형에 대해서는 "객체 주솟값"을 비교합니다.

그래서 참조형 변수를 비교할 때는 객체의 내용이 같더라도,

서로 다른 주소를 가진 별개의 객체라면 false를 반환하게 되는데요,

이는 String 변수도 마찬가지입니다.

 

객체가 메모리에서 갖는 해쉬 주소값을 출력하는 메서드인,
"System.identifyHashcode"를 사용하여 앞의 예제를 다시 살펴봅시다.

 

코드 : 

String str1 = "hello";
//String 변수 str1을 선언한 다음 "hello"를 대입해 주고,

Scanner sc = new Scanner(System.in);
String str2 = sc.next();
//str2는 Scanner로 입력을 받아, 콘솔에 hello를 입력한 다음, 

System.out.println("str1의 해쉬 주솟값 : " + System.identityHashCode(str1));
System.out.println("str2의 해쉬 주솟값 : " + System.identityHashCode(str2));
//String 변수 str1, str2가 실제로 가리키는 해쉬 주솟값을 출력합니다.

System.out.println(str1 == str2);
//그리고 str1 == str2를 출력해 봅시다.

콘솔 : 

hello
str1의 해쉬 주솟값 : 1617791695
str2의 해쉬 주솟값 : 1012570586 
false

str2에도 똑같은 "hello"라는 문자열을 입력해 줬지만,

str1의 해쉬 주솟값은 1617791695,
str2의 해쉬 주솟값은 1012570586로 나타난 것을 확인할 수 있습니다.

*출력되는 해쉬 주솟값은 PC마다 상이합니다.

 

'=='는 참조형에 대해서는 "값"은 물론 "객체 주솟값"까지 비교한다고 말씀드렸죠.

두 참조형 변수 str1, str2를 '=='으로 비교했을 때,

가리키는 문자열은 str1, str2 모두 "hello"로 일치할지 몰라도,

그 "hello"가 저장된 주소가 서로 다르기 때문에 

str1==str2의 결과 역시 false로 나오게 되는 것입니다.

 

 


 

String의 값을 비교해 주는 '.equals()'


더보기

Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object.

[https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#equals-java.lang.Object-]

Java 공식 문서에서 설명하는 .equals()의 기능입니다.

이 String을 다른 객체와 비교하는데,
그 객체가 null이 아니고, 똑같은 문자열을 나타내는 String 객체일 경 true를 반환한다는 내용입니다.

앞서 보여드린 예시와 같이, String의 주솟값이 아니라 String이 가리키는 문자열을 비교하고 싶을 때에 알맞은 기능이죠.

 

그렇기 때문에 서로 다른 String의 값을 비교해 줄 때는 '=='가 아닌 '.equals()'메서드를 써야 하는 것입니다.

 

 


 

String이라도 '=='이 통하는 경우 : '리터럴 방식'으로 두 String을 선언했을 때


Q. 

방금 String 변수 두 개를 만들어서 '=='으로 비교해 봤는데, true가 잘 나오는데요...

 

A.

다음과 같이 '=='을 사용하셨다면 true가 나오는 게 맞습니다.

 

코드 : 

String str3 = "hello";
String str4 = "hello";

System.out.println(str3==str4);

콘솔 : 

true

 

 

보통 String을 사용할 때는 다음과 같은 방식으로 선언해서 사용할 것입니다. 

String str = "hello";

 

이는 마치 int, char, boolean 같은 기본형 변수를 선언하는 방식과 흡사합니다.

(그런 방식을 '리터럴 방식'이라고 합니다.)

 

그런데 앞서 말씀드렸듯이 String은 엄연히 참조형이고, 또 일종의 객체입니다.

일반적인 객체들을 선언하려면, 객체를 생성해주는 연산자 'new'를 사용해야 되겠죠. 

실제로 String도 마찬가지로 new를 이용해 선언할 수 있습니다.

ClassType c = new ClassType();
//일반적으로 객체를 선언하는 방식

String str = new String("hello");
//다른 객체들처럼 new를 이용해 String 선언하기

 

String이 다른 객체들과 달리 'new' 없이도 '리터럴 방식'으로 선언할 수 있게 만들어진 이유는,

String의 특성상 리터럴 방식이 메모리 절약 차원에서 유리하기 때문입니다.

자세한 내용은 다음 링크를 참고해 주세요.

https://bbubbush.tistory.com/22

 

이처럼 String이 리터럴 방식으로 선언되었을 때는,

'new'로 선언했을 때와는 다르게, 변수마다 각자 다른 개별 공간이 아닌

'스트링 상수 풀String constant pool'이라는 공동 공간에 문자열이 저장됩니다.

공동 공간에 저장된다면, 메모리 주소 또한 같은 메모리 주소를 갖는다는 말이겠지요.

 

과연 실제로 그런지 서로 다른 두 방식으로 String 변수를 선언해 보고, 

똑같은 문자열을 집어넣은 다음,

각각 주솟값을 출력해 보고 '=='으로 비교도 해 보겠습니다.

 

코드 : 

String str3 = "hello";
String str4 = "hello";
//리터럴 방식을 이용해 두 String 선언

String str5 = new String("hello");
String str6 = new String("hello");
//객체를 생성하는 new를 이용해 두 String 선언

System.out.println("str3의 해쉬 주솟값 : " + System.identityHashCode(str3));
System.out.println("str4의 해쉬 주솟값 : " + System.identityHashCode(str4));
System.out.println("str5의 해쉬 주솟값 : " + System.identityHashCode(str5));
System.out.println("str6의 해쉬 주솟값 : " + System.identityHashCode(str6));
//System.identityHashCode()를 이용해 각 String의 주솟값을 확인

System.out.println("str3==str4 : " + (str3==str4));
System.out.println("str5==str6 : " + (str5==str6));
//'=='을 사용하여 일치 여부 확인.
/*반드시 str3==str4, str5==str6을 괄호로 감싸고 실행합시다. 
산술 연산자의 우선순위가 비교연산자의 우선순위보다 높기 때문입니다.*/

 

콘솔 : 

str3의 해쉬 주솟값 : 1617791695
str4의 해쉬 주솟값 : 1617791695
//리터럴 방식으로 선언한 두 변수는 주솟값이 일치합니다.

str5의 해쉬 주솟값 : 125993742
str6의 해쉬 주솟값 : 1192108080

str3==str4 : true
//리터럴 방식으로 선언한 두 변수를 '=='으로 비교한 결과, "true"가 나타났습니다.
str5==str6 : false
//new 연산자를 이용한 두 변수를 '=='으로 비교한 결과는 "false"로 나타났습니다.

위와 같이,

리터럴 방식으로 선언한 두 변수는 서로 일치하는 주솟값을 갖고,

'=='으로 비교했을 때도 결과가 true로 나타나는 것을 확인할 수 있습니다.

 

 

다만 실제 프로그래밍에서 String을 비교할 때는,

Scanner를 이용하는 등 주솟값이 서로 다른 두 String을 비교하는 경우가 대부분이기 때문에, 

'=='가 아닌 '.equals()'를 사용해야 정확한 결과를 얻을 수 있을 것입니다.

 

 

 

[참고자료]

- Java의 정석, 3rd Edition, 남궁성

- https://bbubbush.tistory.com/22

Comments