String strNew1 = new String("ABC"); //(1)
String strNew2 = new String("ABC"); //(2)
String strLiteral1 = "ABC"; //(3)
String strLiteral2 = "ABC"; //(4)
==
로 확인해볼 수 있다.==
는 비교하는 대상이 객체인 경우라면 같은 주소값(같은 객체인지)의 여부를 비교해준다.public static void main(String[] args) {
String strNew1 = new String("ABC"); //(1)
String strNew2 = new String("ABC"); //(2)
String strLiteral1 = "ABC"; //(3)
String strLiteral2 = "ABC"; //(4)
System.out.println(strNew1 == strNew2); //⭐ false
System.out.println(strLiteral1 == strLiteral2); //⭐ true
System.out.println(strNew1 == strLiteral1); // false
System.out.println(strNew1 == strLiteral2); // false
}
위의 결과에서 중요한 것은 모든 String 변수에 같은 값 "ABC"가 할당되어 있더라도 new
로 만들었는지 또는 ""
(큰따옴표) 리터럴로 만들었는지 여부에 따라서 같은 객체가 아닐 수도 있다는 것이다.
그 이유는 String strNew1 = new String("ABC");
와 String strLiteral1 = "ABC";
이 객체를 만드는 과정에서 차이가 발생하기 때문이다.
new
연산자로 생성된 객체는 일반적으로 JVM의 HEAP 메모리에 저장된다.
new로 생성한 String 객체도 예외없이 해당 공식을 따른다.
그러나 String strLiteral1 = "ABC";
와 같이 문자열 리터럴로 생성한 객체는 HEAP 메모리 내부의 상수 풀(String Constant Pool)에 별도로 저장되어 관리한다.
아래와 같은 코드를 살펴보자.
String s1 = "Cat"; // (1)
String s2 = "Cat"; // (2)
(1) String 참조변수 s1에 "Cat"이라는 문자열 리터럴을 할당했다. (1)은 최초의 시도이므로, 자바 메모리 영역 Heap의 상수 풀(Constant Pool)에 생성된다.
그렇다면 s2로 "Cat"을 생성했을 때는 어떻게 될까?
해당 값이 JVM의 Heap, 그 중에서도 상수 풀에서 이미 관리되고 있는 객체인지 여부를 판단한다.
➡️ (ㄱ) 이미 관리되고 있다면 (기존에 값을 할당했다면) 새로운 객체를 생성하지 않고 해당 메모리 주소를 반환한다.
➡️ (ㄴ) 반면에 기존에 생성한 적 없는 새로운 값이라면 새 객체를 생성하고 해당 메모리 주소를 반환한다.
위의 경우에는 (ㄱ)의 경우이므로 참조변수 s2에는 s1과 일치하는 메모리 주소값이 반환되는 것이다.
따라서 System.out.println(s1 == s2)
는 true라는 값을 가지게 된다.
반면에 new 연산자로 생성한 String s3 = new String("Cat");
의 경우 같은 값을 가지고 있더라도 당연히 해당 객체의 내부 변수 값을 확인하지 않는다.
String s4 = new String("Cat")
,
String s5 = new String("Cat")
...
s3, s4, s5 모두 다른 객체임을 알 수 있다.
따라서 메모리 관리 측면에서는 String 객체를 생성할 때 HEAP 메모리 안에 new
연산자로 항상 새로운 객체를 생성하는 것보다, 리터럴으로 생성하여 상수풀에서 관리 받도록 함이 적절하다.
equals()
를 사용했을 때는 두 문자열의 내용(“abc”)을 비교하기 때문에 (원래는 두 객체의 주소값을 비교하는 메서드이지만 String 메서드에서는 두 String 객체의 value 값을 비교하도록 오버라이딩 했기 때문) 서로 다른 객체인지 여부를 비교하는 것이 아니라 문자열 내용을 비교하고 싶다면 equals()
메서드를 사용한다. public final class String implements java.io.Serializable, Comparable {
private char[ ] value;
...
String s = "abcd"; // 최초로 String 참조변수 s에 "abcd" 값 할당
s = "abcdef"; // s에 "abcdef" 재할당
덧셈 연산자 '+'를 사용해서 문자열을 결합하는 것은 매 연산 시 마다 새로운 문자열을 가진 String 인스턴스가 생성되어 메모리공간을 차지하게 되므로 (성능 저하를 막기위해) 가능한 한 결합횟수를 줄이는 것이 좋다.
concat()
또는 +
연산자를 사용해서 새로운 객체를 계속해서 만든다면 성능 저하가 발생할 수 있기 때문에 mutable 클래스인 StringBuilder
, StringBuffer
를 생성한다.
- 단, JDK 1.5 이후로는 + 연산자를 사용하더라도 StringBuilder 처럼 mutable한 방식으로 밸류를 변형할 수 있다는 의견이 존재한다.
https://www.slipp.net/questions/271
StringBuffer
, StringBuilder
클래스는 String
클래스와 달리 지정된 문자열의 변경이 가능하다. 즉, mutable하다.
출처 : https://ifuwanna.tistory.com/221
StringBuffer는 내부적으로 문자열 편집을 위한 버퍼(buffer) 를 가지고 있으며, StringBuffer인스턴스를 생성할 때 그 크기를 지정할 수 있다.
(버퍼buffer는 StringBuffer나 StringBuilder 등의 동적배열에 사용될 '크기'라고 볼 수 있다.) 편집할 문자열의 길이를 고려하여 버퍼의 길이를 충분히 잡아주는 것이 좋다.
동일한 API를 가지고 있는 StringBuffer, StringBuilder의 차이점:
- 가장 큰 차이점은 동기화의 유무로써 StringBuffer는 동기화 키워드를 지원하여 멀티쓰레드 환경에서 안전하다는 점(thread-safe) 입니다. 참고로 String도 불변성을 가지기때문에 마찬가지로 멀티쓰레드 환경에서의 안정성(thread-safe)을 가지고 있습니다.
- 반대로 StringBuilder는 동기화를 지원하지 않기때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 만큼 단일쓰레드에서의 성능은 StringBuffer 보다 뛰어납니다.
StringBuffer의 사용 예시
class Ex9_12 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("01"); //⭐ 내부 문자열값으로 "01"을 갖는 SB 객체 생성
StringBuffer sb2 = sb.append(23); // ⭐sb = sb2 = "0123"
sb.append('4').append(56); //⭐ sb = sb2 = "0123456"
StringBuffer sb3 = sb.append(78); // ⭐ sb = sb2 = sb3 = "012345678"
sb3.append(9.0); // ⭐ sb, sb2, sb3 모두 "0123456789.0"
System.out.println("sb ="+sb); //0123456789.0
System.out.println("sb2="+sb2); //0123456789.0
System.out.println("sb3="+sb3); //0123456789.0
// ⭐ 10인덱스인 .빼고 다시 sb리턴 "01234567890"
System.out.println("sb ="+sb.deleteCharAt(10));
// ⭐ 3~5 인덱스 제외한 sb리턴 "01267890"
System.out.println("sb ="+sb.delete(3,6));
// ⭐ 3인덱스 자리에 "abc" 추가 동객체 리턴 "012abc67890"
System.out.println("sb ="+sb.insert(3,"abc"));
// ⭐ 6인덱스부터 sb의 길이-1인덱스까디 "END"로 대체 "012abcEND"
System.out.println("sb ="+sb.replace(6, sb.length(), "END"));
System.out.println("capacity="+sb.capacity());
// ⭐ "012abcEND".length = 9
System.out.println("length="+sb.length());
}
}
StringBuffer(버퍼 크기)
/ StringBuffer("문자열")
: new
와 더불어 새 객체 생성append()
: 추가insert()
: 삽입delete()
: 삭제toString()
: StringBuffer 객체에서 String 객체로 변환.컴파일러에서 분석 할때 최적화에 따라 다른 성능이 나올 수도 있지만 일반적인 경우에는 아래와 같은 경우에 맞게 사용한다.
String
: 문자열 연산이 적고 멀티쓰레드 환경일 경우
StringBuffer
: 문자열 연산이 많고 멀티쓰레드 환경일 경우
StringBuilder
: 문자열 연산이 많고 단일쓰레드이거나 동기화를 고려하지 않아도 되는 경우