String a = "Android";
String b = "Android";
String c = new String("Android");
System.out.println(a == b); // true
System.out.println(a == c); // false
Systen.out.println(a.equals(c)) // true
String 생성하는 것에 literal을 이용하는 것(String interning)과 생성자(new String())를 이용하는 것이 있다.
a처럼 만드는 방식을 ‘literal’(interning)을 이용했다고 한다. a에서 literal을 이용해 “Android”의 값을 가진 객체를 생성하면 그 값은 String pool이라는 곳에 들어간다. 그리고 b가 literal로 “Android”를 생성하면 마찬가지로 String pool로 가게 되는데, 이미 “Android” 값이 있기 때문에 넣지 않고 이미 존재하는 “Android”값을 참조하게 된다. 자세히 말하면 String pool에 있는 객체를 a와 b가 공유하게 된다. 이렇게 되면 메모리와 성능 효율이 더 좋다.
하지만 new String()을 이용해 객체를 생성한 c는, 존재하는 값이 있더라도 String pool로 저장되지 않고 Heap의 별도로 값이 저장된다. 그래서 a와 c는 서로 참조하는 주소값이 다르기 때문에 false가 나오는 것이다.
new String()으로 같은 값을 계속 만들 경우 메모리를 비효율적으로 차지하게된다.
이때 더 추가로 알 수 있는 것은 String은 immutable하다고 한다. 즉 String 클래스의 객체는 불변하는 것인데, 이 immutable 때문에 String pool에 있는 객체를 공유하여 사용할 수 있는 것이다.
만약 a가 참조하는 값인 String pool의 “Android”가 “Studio”로 변경이 가능하다면 b가 참조하고 있는 값도 바뀌게 되는 아찔아찔한 상황이 된다.
만약 수정한다면(a의 참조 값을 바꾼다고 하면) a = “Studio”
로 “Studio”를 String pool에 저장해 참조하게 되는 것이다. 이때 b = “Studio”
가 추가되면 “”Android”는 사용하지 않게 되어 GC의 대상이 된다.
== 는 참조하는 주소를 비교한다.
a과 b는 String pool에 있는 “Android”라는 문자열을 참조한다. 하지만 c는 new String()으로 객체가 생성되어 a와 b가 참조하고 있는 주소값과는 다른 주소값을 참조하게 된다. 이는 String을 생성하는 방식과 그것을 메모리에 담는 방식 그리고 참조하는 값이 달라서 생긴 문제다.
equals()는 참조하는 값을 비교한다.
== 연산자가 주소를 비교했다면 equal는 값을 비교한다는 것이다. 그래서 a와 c가 참조하는 문자열 값은 같기 때문에 a.equals(c)
가 true가 나오는 것이다.
equals()는 처음에 == 연산자와 같이 참조하는 주소값을 비교하고 만약 다르게 될 경우 그 때 값을 비교하게 된다.
참고로 a.equals(b)
에서 a가 null이면 NullPointerException
이 발생한다. 하지만 그 반대인 b가 null인 경우는 발생하지 않는다.
String x = "Android";
String y = new String("Android");
String z = y.intern();
System.out.println(x == y); // false
System.out.println(x == z); // true
참고
위 내용의 예시와 정리가 좋은 글
https://codechacha.com/ko/java-string-compare/
intern메서드 정의를 깊게 분석한 글
https://www.latera.kr/blog/2019-02-09-java-string-intern/
equals()의 NullPointerException를 다루는 팁
https://devpouch.tistory.com/3