- 기본형을 통해서는 연속된 문자인 문자열을 어떻게 다루어야 할까?
- char는 문자 하나 밖에 저장하지 못하므로 문자열을 저장하기 위해서는 char[]를 선언해서 연달아 저장해주어야 한다
public class StringMain{
public static void main(String[] args) {
char[] charArr = {'H','e','l','l','o'};
System.out.println(charArr);
}
}
// 실행결과
// Hello
- 그렇지만 개발할 때 문자열은 굉장히 많이 사용되는 자료형이며 매번 배열을 통해서 문자열을 다루기란 쉽지 않다.
- 따라서 자바에서는 String 클래스를 제공해서 문자열을 쉽게 다루도록 돕고 있다
String을 통해 문자열을 생성하는 방법
- 쌍따옴표 사용 : String str = "Hello";
- 객체 생성 : String str = new String("Hello");
- String은 클래스이므로 객체를 생성해서 저장해주어야 하지만 편의상 쌍따옴표로 문자열을 감싸주면 자바에서 자동으로 new String("Hello")와 같은 역할을 해준다 ( 실제로는 인스턴스를 바로 생성하지는 않고 성능 최적화를 위해 문자열 풀을 사용하긴 한다 )
String 클래스 구조
- String 클래스는 대략 다음과 같이 생겼다
public final class String {
// private final char[] value; // java 9버전 이전
private final byte[] value;
public String concat(String str) {...}
...
}
속성 ( 필드 )
- String 클래스의 필드인 value에는 문자열 값이 저장된다. 문자열은 사실 byte[]에 저장되는 것을 알 수 있다 ( java 9 버전 이전으로는 char[]를 사용했다가 9버전부터 메모리 최적화를 위해 byte를 사용한다 )
- 즉 String 클래스는 char[]를 내부에 감추어 사용자가 문자열을 편리하게 사용할 수 있도록 만들어진 클래스이다
기능 ( 메서드 )
- 따라서 String 클래스는 문자열과 관련한 다양한 메서드를 제공한다
- 문자열 정보 조회, 문자열 비교, 문자열 검색, 문자열 조작 및 변환, 문자열 분할 및 조합, 기타 유틸리티에 활용할 수 있는 정말 많은 메서드가 존재한다
- 양이 너무 많기 때문에 따로 정리해보도록 하겠다
String의 사용
String의 연산
- String 은 클래스이므로 참조형이다
- 따라서 원칙 상 String 변수끼리 + 연산이 불가능하지만 편의상 String 타입 변수끼리 + 연산을 실행하면 String.concat() 메서드를 사용하는 것과 같은 효과를 준다
String의 비교
- String 클래스를 비교할 때는 == 을 통한 동일성 비교가 아니라 equals()를 통해서 동등성을 비교해야 한다
- 물론 어느 객체든 == 비교보다는 equals() 를 활용해야하긴 하지만..
- String 클래스에서 이미 Object.equals()를 오버라이딩 해두었기 때문에 바로 String 객체에서 equals() 메서드를 활용하게 되면 객체 내부의 문자열을 비교해서 동등성 비교 결과를 반환한다
문자열 리터럴, 문자열 풀
public class StringMain2 {
public static void main(String[] args) {
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("new String() == 비교 : " + (str1 == str2));
System.out.println("new String() equals 비교 : " + (str1.equals(str2)));
String str3 = "hello";
String str4 = "hello";
System.out.println("리터럴 == 비교 : " + (str3 == str4));
System.out.println("리터럴 == equals 비교 : " + (str3.equals(str4)));
}
실행 결과
new String() == 비교 : false
new String() equals 비교 : true
리터럴 == 비교 : true
리터럴 == equals 비교 : true
- new 생성자를 통해서 String 객체를 생성하게 되면 당연히 새로운 객체가 생겨나므로 동일성 비교에서 false를 반환한다.
- 그런데 " " 을 통해서 String 객체를 대입하게 되면 자바가 자동으로 new 생성자를 통해 매번 새로 인스턴스를 생성해주는 줄 알았는데 str3과 str4의 참조값이 같다. 왜 그럴까?
- 리터럴을 통한 String 사용시 자바는 메모리 효율성과 성능 최적화를 위해서 "문자열 풀" 이라는 것을 사용한다
- 자바가 처음 실행되는 시점에 문자열 리터럴을 활용한다면 문자열 풀에 해당 리터럴 문자열 인스턴스를 만들어서 미리 넣어둔다. 이 때 동일한 문자열에 대해서 인스턴스는 하나만 생성한다
- 런타임 시점에 문자열 리터럴이 등장하면 문자열 풀에 이미 생성되어 있는 인스턴스의 참조값을 가져와서 변수에 대입한다

- 따라서 사실은 리터럴이 나오면 new 생성자를 활용해 String 객체를 생성하는 것이 아니고, 문자열 풀에서 해당 문자열 인스턴스를 찾아 참조값을 가져오는 것이다
- 그렇기 때문에 str3, str4는 모두 문자열 풀에 존재하는 인스턴스의 참조값을 반환받아서 공유 참조를 하고 있던 것이다
- 문자열 풀 덕분에 동일한 리터럴을 사용하는 경우 메모리 사용을 줄이고, 매번 인스턴스를 생성하는 시간 또한 단축하여 성능 최적화를 할 수 있다
String 클래스 - 불변 객체
- String은 왜 불변 객체로 생성되었을까?
- 방금 자바는 문자열 풀을 통해서 메모리 최적화를 한다는 사실을 알게 되었다
- 그런데 만약 String이 불변 객체가 아니라면 공유 참조하고 있는 변수 중 하나에서 문자열을 수정하게 된다면? 해당 문자열 객체를 참조하고 있던 모든 변수에 영향을 미치게 될 것이다
- 따라서 String 클래스를 불변 객체로 만들게 된 것
- String.concat() 메서드를 활용해서 두 문자열을 이어붙일 때에도 기존 두 문자열은 그대로 존재하고, 새로운 문자열 객체를 만들어서 반환하는 것이다
StringBuilder - 가변 String
- 만약 1000만번 반복하며 String에 a를 한 번씩 이어붙여나간다면 어떨까?
- 각 반복마다 새로운 문자열 객체를 생성해야할테고, 점점 길어지는 문자열 객체를 저장하며 메모리는 메모리대로 잡아먹고, 시간 또한 많이 소요될 것이다
- 문자열을 자주 생성하거나 변경할 때 성능이 매우 떨어지게 된다
- 따라서 자바는 String을 가변으로 활용할 수 있는 클래스인 StringBuilder를 제공한다
StringBuilder
- String과 유사하지만 byte[]가 final로 선언되어 있지 않기 때문에 문자열을 수정하기에 용이하다
- append () 메서드로 현재 문자열의 뒤에 새로운 문자열을 덧붙일 수 있다
- insert () 메서드로 특정 위치에 문자열을 삽입할 수 있는 등 여러가지 문자열과 관련한 메서드를 제공한다
- StringBuilder를 활용해서 1000만번 반복하여 a를 하나씩 이어붙여나간다면 1000만번 반복 이후 단 한 번만 String 객체를 생성하면 되기 때문에 메모리적으로 성능적으로 최적화가 가능하다
StringBuilder 활용
- String이 아니라 StringBuilder를 활용해서 문자열을 처리해야 하는 경우는 다음과 같다
- 반복분에서 반복해서 문자열을 수정할 때
- 조건문을 통해 동적으로 문자열을 조합할 때
- 복잡한 문자열의 특정 부분을 변경해야 할 때
- 매우 긴 대용량 문자열을 다룰 때
String 최적화
문자열 리터럴 최적화
- 자바 컴파일러는 다음과 같이 문자열 리터럴을 더하는 부분을 자동으로 합쳐준다
문자열 리터럴 최적화
- 컴파일 전
- String helloWorld = “Hello” + “ World”;
- 컴파일 후
- String helloWorld = “Hello World”;
- 따라서 런타임에 별도의 문자열 결합 연산을 수행하지 않아도 된다
String 변수 최적화
- 문자열 변수의 경우 그 안에 어떤 값이 들어있는지 컴파일 시점에는 알 수 없기 때문에 단순히 합칠 수 없다
- 이런 경우 StringBuilder를 활용해서 더해 최적화를 수행한다
- 사실 간단한 문자열의 경우에는 위와 같이 최적화를 해주기 때문에 StringBuilder를 사용하지 않아도 괜찮다
'TIL > Java' 카테고리의 다른 글
| [Java] 열거형 - enum (0) | 2025.02.09 |
|---|---|
| [Java] 래퍼 클래스 (1) | 2025.02.09 |
| [Java] 불변 객체 (0) | 2025.02.06 |
| [Java] equals() (0) | 2025.02.05 |
| [Java] toString() (0) | 2025.02.04 |