int
, boolean
과 같은 기본형이 아니라 참조형이다.new
키워드를 통한 객체 생성보다 성능 최적화를 위해 문자열 풀을 사용하는 것이 좋다.✔️속성(필드)
private final char[] value;
여기에는 String
의 실제 문자열 값이 보관된다. 문자 데이터 자체는 char[]
에 보관된다.
String
클래스는 개발자가 직접 다루기 불편한 char[]
을 내부에 감추어 String
클래스를 사용하는 개발자가 편리하게 문자열을 다룰 수 있도록 다양한 기능을 제공한다. 그리고 메서드 제공을 넘어서 자바 언어 차원에서도 여러 편의 문법을 제공한다.
❗참고: 자바 9 이후 String 클래스 변경 사항
private final byte[] byte;
자바에서 문자 하나를 표현하는
char
는 2byte를 차지한다. 그런데 영어, 숫자는 보통 1byte로 표현이 가능하다. 그래서 단순 영어, 숫자로만 표현된 경우 1byte를 사용하고 그렇지 않은 나머지의 경우 2byte인 UTF-16 인코딩을 사용한다. 따라서 메모리를 더 효율적으로 사용할 수 있게 변경되었다.
✔️기능(메서드)
String
클래스는 문자열로 처리할 수 있는 다양한 기능을 제공한다. 기능이 방대하므로 필요한 기능이 있다면 API문서를 참고할 수 있도록....
public class StringEqualMainV1 {
public static void main(String[] args) {
String str1 = new String("Hello!");
String str2 = new String("Hello!");
System.out.println("str1 == str2 → " + (str1 == str2));
System.out.println("str1.equals(str2) → " + str1.equals(str2));
}
}
실행 결과
str1 == str2 → false
str1.equals(str2) → true
str1
과 str2
는 new String()
을 사용해 각각 인스턴스를 생성했다. 서로 다른 인스턴스이기에 ==
비교에 실패한다.Hello!
을 가지고 있기 때문에 논리적으로 같다. 따라서 동등성(equals()
) 비교에 성공한다. 참고로 String
클래스는 내부 문자열 값을 비교하도록 equals()
메서드를 재정의 해두었다.String
인스턴스를 미리 만들어둔다. 이 때, 같은 문자열이 있으면 만들지 않는다.String str3 = "hello"
와 같이 문자열 리터럴을 사용하면 문자열 풀에서 "hello"
라는 문자를 가진 String
인스턴스를 찾는다. 그리고 찾은 인스턴스의 참조를 반환한다.String str4 = "hello"
의 경우 "hello"
문자열 리터럴을 사용하므로 문자열 풀에서 str3
과 같은 참조를 사용한다.==
비교를 할 때 같은 참조값을 가지므로 true를 반환하게 된다.❗참고: 풀(Pool)
풀(Pool)은 자원이 모여있는 곳을 의미한다. 프로그래밍에서 풀(Pool)은 공용 자원을 모아둔 곳을 뜻한다. 여러 곳에서 함께 사용할 수 있는 객체를 필요할 때마다 생성하고, 제거하는 것은 비효율적이다. 대신에 이렇게 문자열 풀에 필요한String
인스턴스를 미리 만들어두고 여러 곳에서 재사용할 수 있다면 성능과 메모리를 더 최적화 할 수 있다.
참고로 문자열 풀은 힙 영역을 사용한다. 그리고 문자열 풀에서 문자를 찾을 때는 해시 알고리즘을 사용하기 때문에 매우 빠른 속도로String
인스턴스를 찾을 수 았다.
String
은 불변 객체이다. 따라서 생성 이후에 절대로 내부의 문자열 값을 변경할 수 없다.
불변인 String
클래스에도 단점이 있다. 불변인 String
의 내부 값은 변경할 수 없다. 따라서 변경된 값을 기반으로 새로운 String
객체를 생성한다.
불변인 String
클래스의 단점은 문자를 더하거나 변경할 때마다 계속해서 새로운 객체를 생성해야 한다는 점이다. 문자를 자주 더하거나 변경해야 하는 상황이라면 더 많은 String
객체를 만들고, GC해야 한다. 결과적으로 컴퓨터의 CPU, 메모리, 자원을 더 많이 사용하게 된다. 그리고 문자열의 크기가 클수록, 문자열을 더 자주 변경할수록 시스템의 자원을 더 많이 소모한다.
이런 문제를 해결하기 위해 바로 불변이 아닌 가변 String
이 존재하면 된다. 가변은 내부의 값을 변경하면 되기 때문에 새로운 객체 생성이 필요 없다. 따라서 성능과 메모리 사용면에서 불변보다 더 효율적이다. 자바는 StringBuilder
라는 가변 String
을 제공한다. 물론 가변의 경우 사이드 이펙트라는 점에 주의해서 사용해야 한다. StringBuilder
는 내부에 final
이 아닌 변경할 수 있는 byte[]
을 가지고 있다.
public class StringBuilderMain1 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("A");
sb.append("B");
sb.append("C");
sb.append("D");
System.out.println("sb = " + sb);
sb.insert(4, "Java");
System.out.println("sb = " + sb);
sb.delete(4, 8);
System.out.println("sb = " + sb);
sb.reverse();
System.out.println("sb = " + sb);
String string = sb.toString();
System.out.println("string = " + string);
}
}
String
은 불변하다. 즉, 한 번 생성되면 그 내용을 변경할 수 없다. 따라서 문자열에 변화를 주려고 할 때마다 새로운 String
객체가 생성되고, 기존 객체는 버려진다. 이 과정에서 메모리의 처리 시간을 더 많이 소모한다.StringBuilder
는 가변적이다. 하나의 StringBuilder
객체 안에서 문자열을 추가, 삭제, 수정할 수 있으며, 이 때마다 새로운 객체를 생성하지 않는다. 이로 인해 메모리 사용을 줄이고 성능을 향상시킬 수 있다. 단 사이드 이펙트를 주의해야 한다.StringBuilder
는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한 불변 String
으로 변환하는 것이 좋다.❗참고: StringBuilder vs StringBuffer
StringBuilder
와 똑같은 기능을 수행하는StringBuffer
클래스가 있다.StringBuffer
클래스는 내부에 동기화가 되어 있어 멀티 쓰레드 상황에서는 안전하지만 동기화 오버헤드로 인해 성능이 느리다.StringBuilder
는 멀티 쓰레드 상황에서 안전하지 않으나 오버헤드가 없으므로 속도가 빠르다.StringBuffer
와 동기화에 관한 내용은 멀티 쓰레드를 학습하면 이해할 수 있다.
public class ValueAdder {
private int value;
public ValueAdder(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public ValueAdder add(int value) {
this.value += value;
return this;
}
}
public class MethodChainingMain {
public static void main(String[] args) {
ValueAdder valueAdder = new ValueAdder(3);
ValueAdder result = valueAdder.add(1).add(2).add(3);
System.out.println(result.getValue());
}
}
메서드 호출의 결과로 자기 자신의 참조값을 반환하면 반환된 참조값을 사용해서 메서드 호출을 계속 이어갈 수 있다. 코드를 보면 메서드를 계속 연결해서 사용하는 것을 볼 수 있다. 이러한 기법을 메서드 체이닝이라 한다.
public class StringBuilderMain2 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append("A").append("B").append("C").append("D");
String string = sb.toString();
System.out.println(string);
}
}