자바에서 문자열은 정말 자주 사용합니다.
화면에 보여줄 문장, 사용자가 입력한 이름, 파일명, URL, 이메일, 로그 메시지까지 대부분 문자열로 다룹니다.
그런데 String은 단순히 글자를 담는 상자가 아닙니다.
자바에서 String은 클래스이고, 참조형이며, 동시에 특별하게 다뤄지는 객체입니다.
이 글에서는 String을 중급 관점에서 정리합니다.
문자열을 어떻게 만들고, 왜 == 대신 equals()를 써야 하는지, 왜 String이 불변인지, 언제 StringBuilder를 써야 하는지까지 한 흐름으로 살펴보겠습니다.
String을 배우기 전에 알아둘 점
자바에서 문자 하나는 char로 표현할 수 있습니다.
char letter = 'A';
하지만 실제 개발에서는 문자 하나보다 여러 글자가 이어진 문자열을 훨씬 많이 다룹니다.
문자 여러 개를 직접 배열로 관리할 수도 있습니다.
char[] letters = {'h', 'e', 'l', 'l', 'o'};
System.out.println(letters);
하지만 매번 char[]로 문자열을 다루면 불편합니다.
길이를 구하고, 일부를 잘라내고, 다른 문자열과 합치고, 특정 단어를 찾는 작업을 직접 만들어야 하기 때문입니다.
그래서 자바는 문자열을 편하게 다루기 위해 String 클래스를 제공합니다.
String message = "hello";
System.out.println(message);
String은 문자열 데이터를 감싸고, 문자열을 다루는 여러 메서드를 함께 제공하는 클래스입니다.
String은 기본형이 아니라 참조형입니다
int, boolean, double 같은 타입은 기본형입니다.
값 자체를 변수에 담는 방식으로 이해하면 됩니다.
반면 String은 클래스입니다.
즉, 변수에는 문자열 객체를 가리키는 참조값이 들어갑니다.
String name = "deep";
위 코드는 name이라는 변수에 "deep"이라는 문자열 객체의 위치를 담는다고 볼 수 있습니다.
다만 String은 워낙 자주 쓰이기 때문에 자바가 특별한 문법을 허용합니다.
원래 객체를 만들 때는 new를 사용하지만, 문자열은 큰따옴표만으로도 만들 수 있습니다.
String a = "java";
String b = new String("java");
두 코드 모두 문자열을 만들지만, 실제 동작 방식에는 차이가 있습니다. 이 차이는 문자열 비교에서 중요하게 이어집니다.
String을 만드는 두 가지 방식
문자열을 만드는 방법은 크게 두 가지입니다.
String literal = "hello";
String object = new String("hello");
첫 번째 방식은 문자열 리터럴을 사용하는 방식입니다.
두 번째 방식은 new로 직접 String 객체를 생성하는 방식입니다.
일반적으로는 문자열 리터럴을 사용합니다.
String title = "Java String";
new String("Java String")처럼 직접 객체를 만드는 방식은 특별한 이유가 없다면 잘 사용하지 않습니다.
불필요한 객체를 새로 만들 수 있기 때문입니다.
String 내부 구조를 단순하게 이해하기
String은 내부적으로 실제 문자 데이터를 가지고 있습니다.
자바 버전에 따라 내부 구현은 달라졌습니다.
예전에는 char[]를 사용했고, 자바 9 이후에는 메모리를 더 효율적으로 쓰기 위해 byte[] 기반 구조를 사용합니다.
중요한 점은 내부 구현을 외우는 것이 아닙니다.
핵심은 이것입니다.
String은 문자열 데이터를 직접 노출하지 않고,
메서드를 통해 안전하게 다루도록 만든 클래스입니다.
그래서 우리는 내부 배열을 직접 고치지 않고, length(), substring(), replace() 같은 메서드로 문자열을 다룹니다.
문자열 비교는 equals를 사용합니다
String을 비교할 때 가장 많이 하는 실수가 ==를 사용하는 것입니다.
String a = new String("java");
String b = new String("java");
System.out.println(a == b);
System.out.println(a.equals(b));
실행 결과는 다음과 같습니다.
false
true
왜 이런 차이가 생길까요?
==는 두 변수가 같은 객체를 가리키는지 확인합니다.
반면 equals()는 두 문자열의 내용이 같은지 확인합니다.
정리하면 다음과 같습니다.
| 비교 방식 | 확인하는 것 | String 비교에 적합한가 |
|---|---|---|
== | 참조값이 같은지 | 보통 적합하지 않음 |
equals() | 문자열 내용이 같은지 | 적합함 |
문자열은 거의 항상 내용 비교가 목적입니다.
따라서 String 비교는 기본적으로 equals()를 사용합니다.
String input = "admin";
if (input.equals("admin")) {
System.out.println("관리자입니다.");
}
문자열 리터럴과 문자열 풀
문자열 리터럴은 자바가 효율적으로 관리합니다.
String a = "hello";
String b = "hello";
System.out.println(a == b);
이 코드는 true가 나올 수 있습니다.
이유는 자바가 같은 문자열 리터럴을 문자열 풀이라는 공용 저장소에 보관하기 때문입니다.
"hello"가 이미 문자열 풀에 있으면 새로 만들지 않고 기존 객체를 함께 사용합니다.
문자열 풀
└─ "hello" 객체
├─ a
└─ b
그래서 리터럴끼리는 우연히 == 비교가 성공하는 것처럼 보일 수 있습니다.
그렇다면 new String("hello")는 왜 다르게 동작할까요?
new는 “새 객체를 만들어라”는 명령입니다.
문자열 내용이 같더라도 new String("hello")를 사용하면 일반 heap 영역에 새로운 String 객체가 만들어집니다.
String a = "hello";
String b = "hello";
String c = new String("hello");
메모리 구조를 단순하게 그리면 다음과 같습니다.
String Pool
┌────────────────────┐
│ "hello" │
└─────────┬──────────┘
│
a ──┘
b ──┘
Heap
┌────────────────────┐
│ new String("hello")│
└─────────┬──────────┘
│
c ──┘
a와 b는 문자열 풀의 같은 객체를 가리킵니다.
하지만 c는 heap에 새로 만들어진 별도 객체를 가리킵니다.
그래서 아래 비교 결과가 달라집니다.
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a.equals(c));
실행 결과는 다음과 같습니다.
true
false
true
a == c는 서로 다른 객체라서 false입니다.
하지만 내용은 둘 다 "hello"이므로 a.equals(c)는 true입니다.
intern은 무엇일까?
String에는 intern()이라는 메서드도 있습니다.
intern()은 문자열과 같은 값을 문자열 풀에서 찾고, 있으면 풀에 있는 객체의 참조를 반환합니다.
없으면 문자열 풀에 등록한 뒤 그 참조를 반환합니다.
String a = "hello";
String c = new String("hello");
String d = c.intern();
System.out.println(a == c);
System.out.println(a == d);
실행 결과는 다음과 같습니다.
false
true
d는 c 자체가 아니라 문자열 풀에 있는 "hello" 객체를 가리킵니다.
그래서 a == d는 true가 됩니다.
다만 일반적인 코드에서 문자열 비교를 위해 intern()을 직접 사용할 일은 많지 않습니다.
문자열 내용 비교는 여전히 equals()를 사용하는 것이 기본입니다.
하지만 이 동작에 기대면 안 됩니다.
문자열이 리터럴로 만들어졌는지, new로 만들어졌는지, 메서드 결과로 만들어졌는지 항상 눈으로 확신하기 어렵기 때문입니다.
문자열 내용 비교는 어떤 상황에서도 equals()를 쓰는 습관이 안전합니다.
String은 불변 객체입니다
String의 가장 중요한 특징 중 하나는 불변입니다.
불변이라는 말은 한 번 만들어진 문자열 객체의 내용이 바뀌지 않는다는 뜻입니다.
String text = "hello";
text.concat(" java");
System.out.println(text);
실행 결과는 다음과 같습니다.
hello
concat()을 호출했는데 왜 "hello java"가 되지 않았을까요?
String은 기존 객체를 고치지 않습니다.
대신 변경 결과를 담은 새로운 String 객체를 반환합니다.
따라서 결과를 사용하려면 반환값을 변수에 다시 담아야 합니다.
String text = "hello";
String result = text.concat(" java");
System.out.println(text);
System.out.println(result);
실행 결과는 다음과 같습니다.
hello
hello java
text가 가리키던 기존 문자열은 그대로이고, result가 새 문자열을 가리킵니다.
String이 불변이어야 하는 이유
문자열은 여러 곳에서 공유될 수 있습니다. 특히 문자열 풀에서는 같은 리터럴을 여러 변수가 함께 가리킬 수 있습니다.
String a = "hello";
String b = "hello";
만약 String이 중간에 마음대로 바뀔 수 있다면 문제가 생깁니다.
a를 바꿨는데 같은 객체를 공유하던 b까지 같이 바뀌는 일이 생길 수 있습니다.
그래서 String은 불변으로 설계되어 있습니다.
불변 덕분에 얻는 장점은 다음과 같습니다.
| 장점 | 설명 |
|---|---|
| 안전한 공유 | 같은 문자열 객체를 여러 곳에서 함께 사용해도 값이 변하지 않습니다. |
| 문자열 풀 최적화 | 같은 리터럴을 재사용하기 쉬워집니다. |
| 예측 가능한 동작 | 메서드를 호출해도 원본 문자열이 바뀌지 않습니다. |
| 해시 기반 자료구조에 유리 | HashMap의 키처럼 값이 바뀌면 곤란한 곳에 안정적으로 사용할 수 있습니다. |
불변은 처음에는 불편해 보일 수 있습니다. 하지만 자주 공유되는 문자열에는 매우 중요한 안전장치입니다.
자주 쓰는 String 메서드 한 번에 보기
String에는 많은 메서드가 있습니다.
전부 외울 필요는 없습니다.
중요한 것은 어떤 종류의 기능이 있는지 알고, 필요할 때 찾아서 쓰는 것입니다.
문자열 정보 확인
String text = "Hello Java";
System.out.println(text.length());
System.out.println(text.isEmpty());
System.out.println(text.isBlank());
System.out.println(text.charAt(6));
| 메서드 | 역할 |
|---|---|
length() | 문자열 길이를 반환합니다. |
isEmpty() | 길이가 0인지 확인합니다. |
isBlank() | 비어 있거나 공백만 있는지 확인합니다. |
charAt(index) | 특정 위치의 문자를 반환합니다. |
isEmpty()와 isBlank()는 다릅니다.
System.out.println("".isEmpty());
System.out.println(" ".isEmpty());
System.out.println(" ".isBlank());
실행 결과는 다음과 같습니다.
true
false
true
빈 문자열과 공백 문자열은 구분해서 생각해야 합니다.
문자열 비교와 시작, 끝 확인
String title = "Java String Class";
System.out.println(title.equals("java string class"));
System.out.println(title.equalsIgnoreCase("java string class"));
System.out.println(title.startsWith("Java"));
System.out.println(title.endsWith("Class"));
| 메서드 | 역할 |
|---|---|
equals() | 대소문자를 구분해서 내용이 같은지 확인합니다. |
equalsIgnoreCase() | 대소문자를 무시하고 내용이 같은지 확인합니다. |
compareTo() | 문자열을 사전 순서로 비교합니다. |
compareToIgnoreCase() | 대소문자를 무시하고 사전 순서로 비교합니다. |
startsWith() | 특정 문자열로 시작하는지 확인합니다. |
endsWith() | 특정 문자열로 끝나는지 확인합니다. |
compareTo()는 정렬 기준을 만들 때 자주 만납니다.
결과가 0이면 두 문자열이 같고, 음수나 양수면 앞뒤 순서가 다르다는 뜻입니다.
문자열 검색
문자열 안에 특정 단어가 있는지 확인할 때는 contains()나 indexOf()를 사용할 수 있습니다.
String log = "ERROR: user login failed";
System.out.println(log.contains("ERROR"));
System.out.println(log.indexOf("user"));
System.out.println(log.lastIndexOf("login"));
| 메서드 | 역할 |
|---|---|
contains() | 특정 문자열이 포함되어 있는지 확인합니다. |
indexOf() | 특정 문자열이 처음 나오는 위치를 반환합니다. |
indexOf(value, fromIndex) | 지정한 위치부터 다시 검색합니다. |
lastIndexOf() | 특정 문자열이 마지막으로 나오는 위치를 반환합니다. |
찾지 못하면 indexOf()는 -1을 반환합니다.
String fileName = "report.pdf";
if (fileName.indexOf(".txt") == -1) {
System.out.println("txt 파일이 아닙니다.");
}
문자열 자르기와 변경
substring()은 문자열의 일부를 잘라냅니다.
String fileName = "memo.txt";
String name = fileName.substring(0, 4);
String extension = fileName.substring(4);
System.out.println(name);
System.out.println(extension);
실행 결과는 다음과 같습니다.
memo
.txt
주의할 점은 끝 인덱스가 포함되지 않는다는 것입니다.
String text = "abcdef";
System.out.println(text.substring(1, 4));
실행 결과는 다음과 같습니다.
bcd
변경 계열 메서드도 자주 사용합니다.
String sentence = "java is fun. java is powerful.";
System.out.println(sentence.replace("java", "Java"));
System.out.println(sentence.replaceFirst("java", "Java"));
System.out.println(sentence.toUpperCase());
System.out.println(sentence.toLowerCase());
| 메서드 | 역할 |
|---|---|
substring() | 문자열 일부를 잘라냅니다. |
concat() | 문자열을 뒤에 붙입니다. |
replace() | 일치하는 문자열을 모두 바꿉니다. |
replaceFirst() | 처음 일치하는 문자열만 바꿉니다. |
replaceAll() | 정규식 기준으로 모두 바꿉니다. |
toUpperCase() | 대문자로 바꾼 새 문자열을 반환합니다. |
toLowerCase() | 소문자로 바꾼 새 문자열을 반환합니다. |
여기서도 원본 문자열은 바뀌지 않습니다. 결과를 사용하려면 반환값을 받아야 합니다.
trim과 strip
문자열 앞뒤 공백을 제거할 때는 trim()이나 strip()을 사용할 수 있습니다.
String input = " hello java ";
System.out.println("'" + input.trim() + "'");
System.out.println("'" + input.strip() + "'");
trim()은 오래전부터 있던 메서드입니다.
strip()은 자바 11부터 사용할 수 있고, 유니코드 공백 처리에 더 적합합니다.
앞쪽만 제거하거나 뒤쪽만 제거할 수도 있습니다.
String input = " hello java ";
System.out.println("'" + input.stripLeading() + "'");
System.out.println("'" + input.stripTrailing() + "'");
사용자 입력값을 처리할 때 공백 제거는 자주 필요합니다.
split과 join
문자열을 나누고 다시 합칠 때는 split()과 join()을 사용합니다.
String email = "deep@example.com";
String[] parts = email.split("@");
System.out.println(parts[0]);
System.out.println(parts[1]);
실행 결과는 다음과 같습니다.
deep
example.com
나누어진 문자열 배열을 다시 합칠 수도 있습니다.
String[] words = {"java", "spring", "jpa"};
String result = String.join(" -> ", words);
System.out.println(result);
실행 결과는 다음과 같습니다.
java -> spring -> jpa
split()의 인자는 단순 구분자처럼 보이지만 실제로는 정규식입니다.
따라서 . 같은 문자는 특별한 의미가 있으니 조심해야 합니다.
String path = "a.b.c";
String[] wrong = path.split(".");
String[] right = path.split("\\.");
정규식 문자를 구분자로 사용할 때는 이스케이프가 필요할 수 있습니다.
변환과 기타 메서드
다른 값을 문자열로 바꿀 때는 String.valueOf()를 사용할 수 있습니다.
int count = 10;
boolean active = true;
String countText = String.valueOf(count);
String activeText = String.valueOf(active);
System.out.println(countText);
System.out.println(activeText);
문자열을 문자 배열로 바꿀 수도 있습니다.
String text = "Java";
char[] chars = text.toCharArray();
for (char c : chars) {
System.out.println(c);
}
형식이 있는 문자열을 만들 때는 format()을 사용할 수 있습니다.
String message = String.format("이름: %s, 점수: %d", "정민", 90);
System.out.println(message);
정규식과 맞는지 확인할 때는 matches()를 사용할 수 있습니다.
String phone = "010-1234-5678";
boolean valid = phone.matches("\\d{3}-\\d{4}-\\d{4}");
System.out.println(valid);
정리하면 다음과 같습니다.
| 메서드 | 역할 |
|---|---|
valueOf() | 여러 타입의 값을 문자열로 바꿉니다. |
toCharArray() | 문자열을 char[]로 바꿉니다. |
format() | 형식 문자열을 만듭니다. |
matches() | 정규식과 일치하는지 확인합니다. |
CharSequence도 알아두면 좋습니다
contains() 같은 메서드를 보면 인자 타입이 String이 아니라 CharSequence인 경우가 있습니다.
boolean result = "hello java".contains("java");
CharSequence는 문자열처럼 문자들이 순서대로 이어진 구조를 표현하는 상위 타입입니다.
String, StringBuilder, StringBuffer 등이 이 타입과 관련됩니다.
처음에는 깊게 외우지 않아도 됩니다.
다만 메서드 문서에서 CharSequence가 보이면 “문자열 계열 값을 받을 수 있구나” 정도로 이해하면 됩니다.
문자열 더하기와 성능
문자열은 +로 쉽게 합칠 수 있습니다.
String name = "Java";
String message = "Hello, " + name;
간단한 문자열 연결은 +를 사용해도 괜찮습니다.
컴파일러와 JVM이 내부적으로 최적화를 해주기 때문입니다.
예를 들어 리터럴끼리 더하는 코드는 컴파일 시점에 하나의 문자열로 합쳐질 수 있습니다.
String text = "Hello, " + "Java";
위 코드는 사실상 아래처럼 처리될 수 있습니다.
String text = "Hello, Java";
변수가 포함된 문자열 연결도 내부적으로는 StringBuilder나 JVM의 문자열 연결 최적화를 사용할 수 있습니다.
하지만 반복문 안에서 문자열을 계속 더하는 코드는 조심해야 합니다.
String result = "";
for (int i = 0; i < 100000; i++) {
result += "java";
}
String은 불변이기 때문에 매번 새 문자열이 만들어질 수 있습니다.
반복 횟수가 많으면 중간 문자열이 많이 생기고, 메모리와 시간이 낭비됩니다.
이럴 때 StringBuilder를 사용합니다.
StringBuilder는 변하는 문자열을 다룰 때 사용합니다
StringBuilder는 내부 문자열을 바꿀 수 있는 객체입니다.
문자열을 여러 번 추가하거나 수정해야 할 때 유용합니다.
StringBuilder sb = new StringBuilder();
sb.append("Java");
sb.append(" ");
sb.append("String");
sb.append("Builder");
String result = sb.toString();
System.out.println(result);
실행 결과는 다음과 같습니다.
Java StringBuilder
StringBuilder는 대표적으로 다음 메서드를 제공합니다.
| 메서드 | 역할 |
|---|---|
append() | 뒤에 문자열을 추가합니다. |
insert() | 특정 위치에 문자열을 삽입합니다. |
delete() | 특정 범위의 문자열을 삭제합니다. |
reverse() | 문자열 순서를 뒤집습니다. |
toString() | 최종 결과를 String으로 바꿉니다. |
예시를 보겠습니다.
StringBuilder sb = new StringBuilder("abcd");
sb.insert(4, "-java");
System.out.println(sb);
sb.delete(4, 9);
System.out.println(sb);
sb.reverse();
System.out.println(sb);
실행 결과는 다음과 같습니다.
abcd-java
abcd
dcba
StringBuilder를 직접 쓰면 좋은 경우
항상 StringBuilder를 써야 하는 것은 아닙니다.
짧고 단순한 문자열 연결은 +가 더 읽기 좋습니다.
String message = name + "님, 환영합니다.";
하지만 아래 상황에서는 StringBuilder를 고려하는 것이 좋습니다.
| 상황 | 이유 |
|---|---|
| 반복문에서 문자열을 계속 붙일 때 | 중간 문자열 생성을 줄일 수 있습니다. |
| 조건에 따라 여러 조각을 조립할 때 | 단계적으로 추가하기 좋습니다. |
| 긴 문자열을 만들어야 할 때 | 메모리 사용을 줄이는 데 유리합니다. |
| 중간 삽입, 삭제, 뒤집기가 필요할 때 | insert(), delete(), reverse()를 사용할 수 있습니다. |
예를 들어 검색 조건을 문자열로 조립한다고 생각해보겠습니다.
StringBuilder query = new StringBuilder();
query.append("category=java");
query.append("&level=mid");
query.append("&sort=recent");
System.out.println(query.toString());
실행 결과는 다음과 같습니다.
category=java&level=mid&sort=recent
StringBuilder와 StringBuffer 차이
StringBuilder와 비슷한 클래스로 StringBuffer가 있습니다.
둘 다 변하는 문자열을 다룰 수 있습니다. 차이는 동기화 여부입니다.
| 구분 | 특징 |
|---|---|
StringBuilder | 동기화가 없어서 일반적으로 더 빠릅니다. |
StringBuffer | 동기화가 있어 멀티스레드 환경에서 안전하게 사용할 수 있습니다. |
입문과 중급 초반에서는 대부분 StringBuilder를 사용한다고 생각하면 됩니다.
StringBuffer는 여러 스레드가 같은 문자열 객체를 동시에 수정하는 상황을 배운 뒤에 더 깊게 이해할 수 있습니다.
메서드 체이닝
메서드 체이닝은 메서드 호출을 이어 붙이는 방식입니다.
object.method1().method2().method3();
이 방식이 가능하려면 각 메서드가 다음 메서드를 호출할 수 있는 객체를 반환해야 합니다.
간단한 예시를 만들어보겠습니다.
class ScoreBox {
private int score;
public ScoreBox add(int value) {
score += value;
return this;
}
public int getScore() {
return score;
}
}
add()는 점수를 더한 뒤 자기 자신인 this를 반환합니다.
그래서 다음처럼 이어서 호출할 수 있습니다.
ScoreBox box = new ScoreBox();
int result = box.add(10)
.add(20)
.add(30)
.getScore();
System.out.println(result);
실행 결과는 다음과 같습니다.
60
StringBuilder의 append(), insert(), delete(), reverse()도 이런 방식으로 사용할 수 있습니다.
String result = new StringBuilder()
.append("Java")
.append(" ")
.append("String")
.reverse()
.toString();
System.out.println(result);
메서드 체이닝은 코드를 짧고 자연스럽게 만들 수 있습니다. 다만 너무 길게 이어 쓰면 읽기 어려워질 수 있으니 적당히 끊는 것이 좋습니다.
예제 1. URL이 https로 시작하는지 확인하기
String url = "https://www.deep-c.dev";
boolean secure = url.startsWith("https://");
System.out.println(secure);
실행 결과는 다음과 같습니다.
true
startsWith()는 URL, 파일 경로, 접두어 검사처럼 “앞부분이 맞는지” 확인할 때 유용합니다.
예제 2. 단어 길이 합계 구하기
String[] words = {"java", "string", "builder", "object"};
int sum = 0;
for (String word : words) {
System.out.println(word + ":" + word.length());
sum += word.length();
}
System.out.println("sum = " + sum);
실행 결과는 다음과 같습니다.
java:4
string:6
builder:7
object:6
sum = 23
문자열 길이를 누적할 때는 length()를 사용합니다.
예제 3. 파일명과 확장자 분리하기
파일명에서 확장자를 분리할 때는 indexOf()와 substring()을 함께 사용할 수 있습니다.
String file = "profile.png";
String extension = ".png";
int extensionIndex = file.indexOf(extension);
String name = file.substring(0, extensionIndex);
String extName = file.substring(extensionIndex);
System.out.println("name = " + name);
System.out.println("extName = " + extName);
실행 결과는 다음과 같습니다.
name = profile
extName = .png
이렇게 기준 위치를 먼저 찾고, 그 위치를 기준으로 잘라내면 하드코딩을 줄일 수 있습니다.
예제 4. 특정 단어가 몇 번 나오는지 세기
indexOf()는 시작 위치를 지정할 수 있습니다.
이 기능을 사용하면 같은 단어가 몇 번 등장하는지 셀 수 있습니다.
String text = "java spring java jpa java";
String keyword = "java";
int count = 0;
int index = text.indexOf(keyword);
while (index >= 0) {
count++;
index = text.indexOf(keyword, index + keyword.length());
}
System.out.println("count = " + count);
실행 결과는 다음과 같습니다.
count = 3
찾은 위치 다음부터 다시 검색하는 것이 핵심입니다.
예제 5. 공백 제거와 단어 변경
사용자 입력은 앞뒤에 공백이 붙어 들어올 수 있습니다.
String input = " hello java spring ";
String cleaned = input.strip();
String replaced = cleaned.replace("java", "jvm");
System.out.println(replaced);
실행 결과는 다음과 같습니다.
hello jvm spring
String은 불변이므로 strip()과 replace()의 반환값을 새 변수에 담아야 합니다.
예제 6. 이메일을 아이디와 도메인으로 나누기
String email = "deep@example.com";
String[] parts = email.split("@");
String id = parts[0];
String domain = parts[1];
System.out.println("ID: " + id);
System.out.println("Domain: " + domain);
실행 결과는 다음과 같습니다.
ID: deep
Domain: example.com
split()을 사용할 때는 결과 배열의 길이를 확인하는 습관도 좋습니다.
실제 사용자 입력은 예상과 다를 수 있기 때문입니다.
예제 7. 나눈 문자열 다시 합치기
String fruits = "apple,banana,mango";
String[] splitFruits = fruits.split(",");
for (String fruit : splitFruits) {
System.out.println(fruit);
}
String joined = String.join(" -> ", splitFruits);
System.out.println("joined = " + joined);
실행 결과는 다음과 같습니다.
apple
banana
mango
joined = apple -> banana -> mango
나누기에는 split(), 합치기에는 join()을 사용하면 됩니다.
예제 8. 문자열 뒤집기
문자열을 뒤집을 때 직접 반복문을 작성할 수도 있지만, StringBuilder의 reverse()를 사용하면 간단합니다.
String text = "Hello Java";
String reversed = new StringBuilder(text)
.reverse()
.toString();
System.out.println(reversed);
실행 결과는 다음과 같습니다.
avaJ olleH
StringBuilder로 바꾸고, 뒤집고, 다시 String으로 바꾸는 흐름입니다.
실수하기 쉬운 부분
String을 사용할 때는 아래 실수를 자주 합니다.
| 실수 | 왜 문제인가 |
|---|---|
==로 문자열 내용을 비교함 | 참조값 비교라서 의도와 다를 수 있습니다. |
replace() 결과를 변수에 담지 않음 | String은 불변이라 원본이 바뀌지 않습니다. |
반복문에서 +로 계속 문자열을 붙임 | 중간 문자열이 많이 만들어질 수 있습니다. |
substring() 끝 인덱스를 포함한다고 착각함 | 끝 인덱스는 포함되지 않습니다. |
split(".")처럼 정규식 문자를 그대로 사용함 | .은 정규식에서 특별한 의미를 가집니다. |
이 중 가장 중요한 것은 equals()와 불변성입니다.
문자열 비교는 equals().
문자열 변경 메서드는 새 문자열 반환.
이 두 가지만 확실히 잡아도 많은 오류를 줄일 수 있습니다.
정리
String은 문자열을 다루는 클래스입니다.
문자열 리터럴은 문자열 풀을 통해 재사용될 수 있습니다.
하지만 문자열 내용을 비교할 때는 항상 equals()를 사용하는 것이 안전합니다.
String은 불변 객체입니다.
그래서 concat(), replace(), substring(), toUpperCase() 같은 메서드는 원본을 바꾸지 않고 새 문자열을 반환합니다.
짧고 단순한 문자열 연결은 +를 사용해도 괜찮습니다.
하지만 반복문에서 문자열을 계속 붙이거나, 긴 문자열을 단계적으로 조립해야 한다면 StringBuilder를 사용하는 것이 좋습니다.
마지막으로 String 메서드는 외우는 대상이라기보다 필요할 때 찾아 쓰는 도구입니다.
처음에는 equals(), length(), substring(), indexOf(), replace(), split(), join(), StringBuilder 정도를 먼저 익히면 충분합니다.