자바에서 참조형 변수를 사용할 때
Stack 영역과 Heap 영역은 항상 함께 동작한다.
Stack 영역에는 참조변수가 저장되고
해당 변수가 가리키는 실제 객체는 Heap 영역에 저장된다.
즉, Stack에는 객체의 실제 데이터가 아니라, Heap에 생성된 객체의 주소값(참조값)만 저장된다.
이러한 Stack과 Heap의 참조 구조는 모든 객체에 공통적으로 적용된다.
그 중 문자열 비교는 이 구조를 가장 쉽게 확인할 수 있는 대표적인 예제이다.
String str_1 = "test_1";
String str_2 = "test_1";
System.out.println(str_1 == str_2);
System.out.println(str_1 == "test_1");
System.out.println(str_1 == new String("test_1"));

어째서 이런 결과가 나오는가에 대해서 알아보자.
// true
str_1 == str_2
해당 코드에서 변수 str_1 과 str_2는 모두 문자열 리터럴을 사용하고 있다.
이때, 문자열 리터럴은 다음과 같은 특징을 가진다.
문자열 리터럴은 Heap 내부의 String Constant Pool(String Pool) 영역에 저장된다.
String str_1 = "test_1";
String str_2 = "test_1";
위 코드가 실행되면 JVM은 먼저 String Pool에 "A"가 존재하는지 확인한다.
따라서 동일한 문자열 리터럴은 Pool에서 하나의 객체를 공유한다.
(즉, str_1과 str_2는 같은 객체를 참조한다.)
이때, == 연산자는 참조 주소값을 비교하는 연산자이다.
str_1과 str_2는 동일한 String Pool 객체를 참조하고 있으므로
주소값이 같아 true가 출력되는것이다.
System.out.println(str_1 == "test_1");
해당 코드 또한 마찬가지로 문자열 리터럴을 입력받고 있기 떄문에 true를 반환한다.
반대로, 다음과 같은 경우는 다른 결과를 부른다.
String str_3 = new String("test_1");
new 키워드를 사용하여 String 객체를 생성한다면
String Pool과 관계없이 Heap 영역에 새로운 String 객체를 생성한다.
따라서, 서로 다른 객체를 참조하므로 false가 출력되는것이다.
String str_1 = "test_1";
String str_3 = new String("test_1");
그렇다면 위 코드의 비교를 하기 위해서는 무엇을 해야할까?
class String extends Object
Java의 모든 클래스는 java.lang.Object 클래스를 상속받기 때문에
.equals() 메서드를 기본적으로 가진다.
기존의 Object 클래스의 .equals() 메서드는 참조값 비교만 수행한다.
하지만, String 클래스는 .equals()를 오버라이딩하여
문자열 내용을 비교하도록 재정의했기 때문에
서로 다른 객체라도 같은 문자열이면 true를 반환한다.
String str_1 = "test_1";
String str_3 = new String("test_1");
// 객체의 내용을 비교 가능
System.out.println(str_1.equals(str_3));

드디어 정상적으로 객체 간 내용 비교를 할 수 있다.
String str_1 = null;
System.out.println(str_1.equals("test"));
만약 위 코드처럼 equals( ) 메서드를 호출하는 객체가
null 을 가지고 있다면 메서드 호출 시 어떻게 될까??

JVM이 메서드 호출 시점에 바로 예외를 터뜨리는 NPE 가 발생한다.
즉, 비교 이전에 메서드 호출 자체가 불가능하다.
따라서, 우리는 NPE를 방지하기 위해서
다음과 같은 null-safe equals 패턴을 사용해준다.
String str_1 = null;
// "test" != str_1
"test".equals(str_1);
// 실행 시 false 출력 (NPE가 발생하지 않는다.)
이 패턴은 문자열 리터럴은 절대 null이 될 수 없음을 사용하는 것이다.
또, 다른 방안으로는 다음과 같다.
String a = "test_1";
Objects.equals(a, "test_2");
Objects.equals(객체, 객체) 메서드 호출 시
객체 둘 다 null 이라면 → true 리턴
객체 하나만 null 이라면 → false 리턴
객체 둘 다 값이 null이 아니라면 → equals( ) 호출
이렇게 사용한다면
가독성 + 안전성 모두 챙길 수 있다.