기존에 문자열을 생성하던 String 클래스는 인스턴스 생성과정에서 지정된 문자열을 변경할 수 없다는 문제점이 있습니다.
아래 코드를 보면 +=
연산자를 통해 쉽게 문자열을 변경한 것처럼 보입니다. 하지만 내부적으로는 두 문자열을 합친 새로운 String 객체를 만들고 str이 새로운 객체를 가리키게 만드는 것에 불과합니다. 이는 기존 문자열이 변경되었다고 보기는 어렵죠.
String str = "ban";
str += "ana";
System.out.println(str); //banana
이처럼 String 클래스는 불변한다는 특성을 갖고 있기 때문에 String 클래스에서 이용하는 여러 문자열 변경 메소드는 위 코드처럼 새로운 String 객체를 만들어서 새 문자열을 생성하도록 되어있습니다.
따라서 진짜로 해당 객체의 문자열을 바꾸고 싶다면 String 클래스가 아닌 StringBuffer, StringBuilder 클래스
를 사용합니다.
StringBuffer/StringBuilder 클래스
는 버퍼를 이용하여 문자열이 가변성을 갖게 해줍니다. StringBuffer 클래스로 선언된 문자열을 이용하면 문자열을 변경할 때 동일한 객체를 가지고 문자열을 변경할 수 있게 됩니다.
StringBuffer 이름 = new StringBuffer();
StringBuilder 이름 = new StringBuilder();
이때 StringBuffer/StringBuilder 클래스
의 생성자의 인수에 아무것도 전달하지 않으면 길이 16짜리의 배열이 생성됩니다.
두 클래스의 사용 방법 및 메소드는 완전히 동일합니다. 따라서 여기서는 StringBuffer 한 가지만 우선적으로 다루겠습니다.
주의할 점은 사용 방법이 동일하다는 것이고 내부적인 동작이 조금씩 다릅니다. 이 내부동작에 대해서는 잠시뒤에 따로 설명드리도록 하겠습니다.
public class Main {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("apple");
System.out.println(sb);
System.out.println(sb.hashCode());
sb.append(" and pineapple");
System.out.println(sb);
System.out.println(sb.hashCode());
}
}
append()
는 인수로 전달한 내용을 문자열 뒤에 이어붙이는 메소드입니다. 이어붙이기 전과 후의 객체의 주소를 hashcode()
를 통해 확인해보면 동일한 객체를 참조하고 있음을 알 수 있습니다.
위 코드를 String 객체를 이용해서 해보면, 결과 자체는 같지만 참조하는 객체가 달라짐을 확인할 수 있습니다.
public class Main { public static void main(String[] args) { String s = new String("apple"); System.out.println(s); System.out.println(s.hashCode()); s += " and pineapple"; System.out.println(s); System.out.println(s.hashCode()); } }
== 연산자
는 주소값을 비교하고, String.equals()
는 단순히 객체에 포함된 문자열을 비교합니다. 따라서 다음과 같이 문자열 리터럴로 생성한 문자열과 생성자를 이용한 문자열은 내용이 같더라도 어떤 비교를 사용하느냐에 따라서 결과가 달라집니다.
Object.equals()를 오버라이딩한 String.equals()는 객체의 동일여부는 판단하지 않고, 문자열의 내용만을 비교합니다.
public class Main {
public static void main(String[] args) {
String str1 = "str";
String str2 = new String("str");
System.out.println(str1 == str2); //주소가 다른 객체임
System.out.println(str1.equals(str2)); //객체 내부 문자열이 같음
}
}
하지만 StringBuffer/StringBuilder 클래스
의 equals()
메소드는 오버라이딩된 메소드가 아니기 때문에 == 연산자
와 동일한 작업을 수행합니다.
즉, StringBuffer/StringBuilder 클래스
의 equals()
메소드는 문자열을 비교하는 것이 아니라 동일한 StringBuffer/StringBuilder 객체인지를 판단하는 메소드가 됩니다.
public class Main {
public static void main(String[] args) {
StringBuffer sb1 = new StringBuffer("apple");
StringBuffer sb2 = new StringBuffer("apple");
System.out.println(sb1 == sb2);
System.out.println(sb1.equals(sb2));
}
}
위에서 언급했던대로 StringBuffer 클래스와 StringBuilder 클래스
는 사용방법, 멤버, 특성이 모두 동일합니다. 그럼 무엇때문에 두 가지 클래스로 구분지어서 만들어놓았을까요?
두 클래스의 유일한 차이는 동기화
여부입니다. StringBuffer 클래스
는 동기화를 지원하고, StringBuilder 클래스
는 동기화를 지원하지 않습니다.
StringBuffer 클래스
는 synchronized 키워드를 가지고 있어서 동기화를 지원하고 있습니다.
synchronized
는 한 작업이 어떤 자원을 사용 중일때 다른 작업에서의 접근을 막습니다.
동기화를 지원하게 되면 여러 스레드에서 동시에 접근해도 안전하게 데이터를 지켜낼 수 있습니다.
그래서 String, StringBuffer, StringBuilder
세 가지 문자열 클래스 중에 무엇을 고를지 오히려 고민이 되나요? 그래서 선택을 돕고자 세 클래스의 선택 조건을 다음과 같이 정리했습니다.