Java에서 String 클래스는 불변성을 갖는다. 그래서 변하지 않는 문자열을 자주 사용하는 경우엔 좋은 성능을 기대할 수 있다. 하지만 문자열이 자주 변경되는 경우엔 String을 사용하기 보다는 StringBuilder , StringBuffer을 사용하는 것이 더 효율적이다.
ava에서 String 객체는 한번 값이 할당되면 그 공간은 변하지 않는다. 하지만 Stringbuilder나 StringBuffer 객체는 한번 값이 할당되더라도 한번 더 다른 값이 할당되면 할당된 공간이 변하는 특성을 갖고 있다.
여기서 할당된 공간이 변하지 않는 특성을 불변(Immutable)성이라고 하고,
할당된 공간이 변하는 특성을 가변(mutable)성이라고 한다.
String 변수에 값을 할당하는 방법은 2가지가 있다.
리터럴 변수를 대입하는 방법
new 키워드를 사용하는 방법

3개의 변수 모두 같은 값의 문자열을 가졌는데 주소비교 결과가 다르다. 이유는 String 타입 값 할당 방식에 따른 저장 방식이 다르기 때문이다.
String 을 리터럴 값으로 할당하는 경우엔 Heap 메모리 영역안의 특별한 메모리 공간인 String constant pool 에 저장된다.
만약 String constant pool에 존재하는 리터럴 값을 사용하게 된다면 새롭게 리터럴 값을 만들어 String constant pool에 저장하는 것이 아닌, 현재 존재하는 값을 사용하게 된다.

위와 같은 이유로 System.out.println(strA==strC); 의 실행 결과가 true 가 나오게 된다.
new 키워드를 통해 String 변수에 값을 할당하게 되면 일반적인 객체와 동일하게 Heap 영역에 동적으로 메모리 공간이 할당되게 된다.
마찬가지로 같은 문자열이더라도 new 키워드를 한번 더 사용하게 되면 같은 값이지만 다른 메모리 공간(Heap 영역 안)을 참조하게 된다.

위와 같은 이유로 System.out.println(strB==strD); 에서 false 를 출력하게 되는 것이다.
또한 System.out.println(strA==strB); 에서도 false 를 출력하는 것도 서로 상이한 메모리 공간을 참조하기 때문이다.
strA → Heap → String Constant Pool
strB → Heap
String 과 달리 StringBuilder와 StringBuffer는 둘 다 크기가 유연하게 변하는 가변성을 갖는다.
StringBuilder , StringBuffer에 문자열을 추가하게 되면 추가할 문자열의 크기(길이)만큼 현재의 문자열을 저장하는 배열의 공간을 늘려주고, 늘려준 공간에 추가할 문자열을 넣어주는 방식으로 되어있다면
내부동작을 통해 값이 변경되더라도 같은 주소공간을 참조하게 되는 것이며, 값이 변경되는 가변성을 띄게 된다.
위와 같이 두 클래스의 기능은 동일하지만 한 가지 차이점이 존재한다. 바로 동기화(Synchronization)에서의 차이점이다.
StringBuilder는 동기화를 지원하지 않는 반면, StringBuffer는 동기화를 지원하여 멀티 스레드 환경에서도 안전하게 동작할 수 있다.
그 이유는 StringBuffer는 메서드에서 synchronized 키워드를 사용하기 때문인데
java에서 synchronized 키워드는 여러개의 스레드가 한 개의 자원에 접근할려고 할 때, 현재 데이터를 사용하고 있는 스레드를 제외하고 나머지 스레드들이 데이터에 접근할 수 없도록 막는 역할을 수행한다.
예를 들어 멀티스레드 환경에서 A 스레드와 B스레드 모두 같은 StringBuffer 클래스 객체 sb의 append() 메서드를 사용하려고 하면, 다음과 같은 절차를 수행하게 된다.
String 은 불변성을 갖는다.
변하지 않는 문자열을 자주 사용할 경우.
StringBuilder는 동기화를 지원하지 않는 반면, 속도면에선 StringBuffer 보다 성능이 좋다.
그렇기 때문에 단일 스레드 환경 과 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuilder를 사용하는 것이 성능면에서 유리하다.
StringBuffer는 동기화를 지원하여 멀티 스레드 환경에서도 안전하게 동작할 수 있다.
그렇기 때문에 멀티 스레드 환경 과 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 StringBuffer를 사용하는 것이 성능면에서 유리하다.
instanceof : 인스턴스의 핏줄까지 전부 비교한다.
getClass().getName() : 클래스의 이름 비교를 통해 클래스를 비교한다..
isInstance : 인스턴스 자체와 비교를 통해 구분한다.