문자열을 만들고, 비교하고, 찾고, 자르고, 바꾸고, 포맷하는 데 쓰는 API를 한 곳에 모았다. 원리 설명은 최소화하고 메서드 → 예제 → 실제 실행 결과만 적었다. 찾는 메서드가 있으면
Ctrl+F로 메서드명(예:strip,formatted,replaceAll)을 검색하면 된다. 모든 예제는 JDK 21에서 실행해 결과를 확인했다.
가장 자주 쓰는 20개. 이것만 알아도 실무 문자열 처리의 80%는 해결된다.
| 메서드 | 설명 | 예제 | 결과 |
|---|---|---|---|
length() |
문자 수(char 단위) | "Hello, Java".length() |
11 |
charAt(i) |
i번째 문자 | "Hello".charAt(1) |
'e' |
equals(o) |
내용 비교 (== 금지) |
"abc".equals("abc") |
true |
equalsIgnoreCase(s) |
대소문자 무시 비교 | "ABC".equalsIgnoreCase("abc") |
true |
isBlank() |
공백만 있거나 빈 문자열 | " ".isBlank() |
true |
contains(s) |
부분 문자열 포함 여부 | "Hello, Java".contains("Java") |
true |
indexOf(s) |
첫 위치(없으면 -1) | "Hello, Java".indexOf("Java") |
7 |
substring(a, b) |
[a, b) 구간 추출 | "Hello, Java".substring(0, 5) |
"Hello" |
strip() |
앞뒤 공백 제거(유니코드 인식) | " hi ".strip() |
"hi" |
toUpperCase() |
대문자 변환 | "Hello".toUpperCase() |
"HELLO" |
replace(a, b) |
문자/문자열 전부 치환(리터럴) | "a-b-c".replace('-', '+') |
"a+b+c" |
replaceAll(regex, r) |
정규식 치환 | "a1b22c333".replaceAll("\\d+", "#") |
"a#b#c#" |
split(regex) |
정규식으로 분할 | "a,b,c".split(",") |
[a, b, c] |
String.join(sep, ...) |
구분자로 결합 | String.join("-", "a", "b", "c") |
"a-b-c" |
repeat(n) |
n번 반복 | "ab".repeat(3) |
"ababab" |
formatted(args) |
포맷(인스턴스 메서드) | "%s님 %d세".formatted("홍길동", 30) |
"홍길동님 30세" |
String.valueOf(x) |
무엇이든 문자열로 | String.valueOf(42) |
"42" |
Integer.parseInt(s) |
문자열 → int | Integer.parseInt("42") + 1 |
43 |
new StringBuilder().append() |
가변 문자열 누적 | new StringBuilder("abc").append("d").append(1) |
"abcd1" |
matches(regex) |
전체 일치 검사 | "2024-01-15".matches("\\d{4}-\\d{2}-\\d{2}") |
true |
| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
"리터럴" |
String Pool에 저장, 같은 리터럴은 같은 객체 | ("ja" + "va") == "java" |
true (컴파일 타임 상수 접기) |
new String("x") |
항상 새 객체 (거의 쓸 일 없음) | new String("a") == "a" |
false |
String.valueOf(Object) |
null-safe 변환 | String.valueOf((Object) null) |
"null" |
String.valueOf(char[]) |
char 배열 → 문자열 | String.valueOf(new char[]{'a','b'}) |
"ab" |
String.copyValueOf(char[]) |
valueOf(char[])와 동일 | String.copyValueOf(new char[]{'x','y'}) |
"xy" |
new String(byte[], Charset) |
바이트 → 문자열 (5절 참고) | new String(bytes, StandardCharsets.UTF_8) |
"한글" |
length() |
UTF-16 코드 유닛 개수 | "😀a".length() |
3 (이모지는 2) |
codePointCount(0, len) |
실제 유니코드 문자 수 | "😀a".codePointCount(0, 3) |
2 |
isEmpty() |
길이 0 | "".isEmpty() |
true |
isBlank() |
공백뿐(탭/개행 포함) | " ".isBlank() |
true |
intern() |
Pool의 동일 문자열 반환 | ("ja" + "va").intern() == "java" |
true |
hashCode() |
내용 기반 해시(캐시됨) | "abc".hashCode() |
96354 |
toCharArray() |
char 배열로 | "abc".toCharArray() |
[a, b, c] |
chars() |
IntStream으로 | "abc".chars().boxed().toList() |
[97, 98, 99] |
describeConstable() |
Optional로 감싸기(거의 안 씀) | "x".describeConstable() |
Optional[x] |
String은 불변이다. s.toUpperCase()를 호출해도 s는 바뀌지 않고 반환값을 받아야 한다.==는 우연히 true가 나올 수 있어 더 위험하다. 항상 equals.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
equals(Object) |
내용 동일 | "abc".equals("abc") |
true |
equalsIgnoreCase(String) |
대소문자 무시 | "ABC".equalsIgnoreCase("abc") |
true |
compareTo(String) |
사전순 (음수/0/양수) | "apple".compareTo("banana") |
-1 |
compareTo 결과값 |
첫 다른 문자의 코드 차이 | "a".compareTo("B") |
31 (97-66) |
compareToIgnoreCase(String) |
대소문자 무시 사전순 | "A".compareToIgnoreCase("a") |
0 |
contentEquals(CharSequence) |
StringBuilder 등과 내용 비교 | "abc".contentEquals(new StringBuilder("abc")) |
true |
Objects.equals(a, b) |
null-safe equals | Objects.equals(null, null) |
true |
regionMatches(ic, off, other, ooff, len) |
부분 영역 비교 | "Hello World".regionMatches(true, 6, "world", 0, 5) |
true |
String.CASE_INSENSITIVE_ORDER |
Comparator 상수 | Stream.of("b","A","c").sorted(String.CASE_INSENSITIVE_ORDER) |
[A, b, c] |
Comparator.nullsFirst(...) |
null 포함 정렬 | Comparator.nullsFirst(String::compareTo).compare(null, "a") |
-1 |
sorted()는 대문자가 소문자보다 앞에 온다("Z" < "a"). 사용자 표시용 정렬은 CASE_INSENSITIVE_ORDER나 Collator를 쓴다."상수".equals(변수) 순서로 쓰면 변수가 null이어도 NPE가 나지 않는다.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
contains(CharSequence) |
포함 여부 | "Hello, Java".contains("Java") |
true |
startsWith(String) |
접두사 | "Hello, Java".startsWith("Hel") |
true |
startsWith(String, offset) |
offset부터 접두사 | "Hello, Java".startsWith("Java", 7) |
true |
endsWith(String) |
접미사 | "Hello, Java".endsWith("va") |
true |
indexOf(char) |
첫 위치 | "Hello, Java".indexOf('l') |
2 |
indexOf(String) |
첫 위치 | "Hello, Java".indexOf("Java") |
7 |
indexOf(char, from) |
from 이후 첫 위치 | "Hello, Java".indexOf('l', 4) |
-1 |
lastIndexOf(char) |
마지막 위치 | "Hello, Java".lastIndexOf('l') |
3 |
| 없을 때 | -1 반환 (예외 없음) | "Hello".indexOf("x") |
-1 |
matches(regex) |
전체가 정규식과 일치 | "2024-01-15".matches("\\d{4}-\\d{2}-\\d{2}") |
true |
chars().filter().count() |
특정 문자 개수 | "hello".chars().filter(c -> c == 'l').count() |
2 |
indexOf를 반복하거나(스니펫 참고) chars()/정규식을 쓴다.matches는 부분 일치가 아니다. "abc1".matches("[a-z]+")는 false. 부분 검색은 Pattern.matcher().find().| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
substring(begin) |
begin부터 끝까지 | "Hello, Java".substring(7) |
"Java" |
substring(begin, end) |
[begin, end) | "Hello, Java".substring(0, 5) |
"Hello" |
substring(len) |
begin == length면 빈 문자열 | "abc".substring(3) |
"" |
subSequence(a, b) |
CharSequence 반환 | "Hello".subSequence(0, 5) |
"Hello" |
toUpperCase() / toLowerCase() |
대소문자 | "Hello, Java".toLowerCase() |
"hello, java" |
toUpperCase(Locale.ROOT) |
로케일 독립 변환(터키어 i 문제 회피) | "title".toUpperCase(Locale.ROOT) |
"TITLE" |
trim() |
U+0020 이하 문자 제거 | " hi ".trim() |
"hi" |
strip() |
유니코드 공백 제거 (권장) | " hi ".strip() |
"hi" |
stripLeading() |
앞만 | " hi ".stripLeading() |
"hi " |
stripTrailing() |
뒤만 | " hi ".stripTrailing() |
" hi" |
replace(char, char) |
문자 전부 치환 | "a-b-c".replace('-', '+') |
"a+b+c" |
replace(CharSequence, CharSequence) |
리터럴 문자열 전부 치환 | "aaa".replace("a", "bb") |
"bbbbbb" |
replaceAll(regex, repl) |
정규식 전부 치환 | "a1b22c333".replaceAll("\\d+", "#") |
"a#b#c#" |
replaceFirst(regex, repl) |
정규식 첫 개만 | "a1b22c333".replaceFirst("\\d+", "#") |
"a#b22c333" |
repeat(n) |
반복 | "ab".repeat(3) |
"ababab" |
concat(s) |
이어 붙이기 (+와 동일) |
"a".concat("b") |
"ab" |
lines() |
줄 단위 Stream (\n, \r\n 모두) |
"a\nb\r\nc".lines().toList() |
[a, b, c] |
indent(n) |
각 줄 n칸 들여쓰기(+개행 정규화) | "a\nb".indent(2) |
" a\n b\n" |
indent(-n) |
n칸 내어쓰기 | " a\n b".indent(-1) |
" a\n b\n" |
stripIndent() |
공통 들여쓰기 제거 | " a\n b".stripIndent() |
"a\n b" |
translateEscapes() |
\t 같은 이스케이프 문자열 해석 |
"a\\tb".translateEscapes() |
"a<TAB>b" |
transform(Function) |
파이프라인처럼 함수 적용 | "42".transform(Integer::parseInt) + 1 |
43 |
replace는 정규식이 아니다. "a.b.c".replace(".", "-")는 "a-b-c", replaceAll(".", "-")는 "-----"(모든 문자 매칭).trim()은 \u2003(EM SPACE) 같은 유니코드 공백을 못 지운다. JDK 11+에서는 strip()을 쓴다.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
split(regex) |
정규식 분할, 끝의 빈 요소 제거 | "a,b,,c,,".split(",") |
[a, b, , c] |
split(regex, -1) |
끝의 빈 요소 유지 | "a,b,,c,,".split(",", -1) |
[a, b, , c, , ] |
split(regex, n) |
최대 n개 | "a,b,c".split(",", 2) |
[a, b,c] |
split("\\.") |
메타문자는 이스케이프 | "a.b.c".split("\\.") |
[a, b, c] |
split("\\s+") |
연속 공백으로 분할 | "a b\tc".split("\\s+") |
[a, b, c] |
split("\\d") |
숫자로 분할 | "a1b2c3".split("\\d") |
[a, b, c] |
String.join(sep, elems...) |
가변 인자 결합 | String.join("-", "a", "b", "c") |
"a-b-c" |
String.join(sep, Iterable) |
컬렉션 결합 | String.join(", ", List.of("x", "y")) |
"x, y" |
Collectors.joining(sep, pre, suf) |
스트림 결합 | Stream.of("a","b").collect(joining(", ", "[", "]")) |
"[a, b]" |
new StringJoiner(sep, pre, suf) |
수동 결합 | new StringJoiner(", ", "[", "]").add("a").add("b") |
"[a, b]" |
StringJoiner.setEmptyValue(s) |
비었을 때 값 | new StringJoiner(",", "[", "]").setEmptyValue("EMPTY") |
"EMPTY" |
Pattern.splitAsStream(s) |
분할 결과를 Stream으로 | Pattern.compile(",").splitAsStream("a,b,c").toList() |
[a, b, c] |
split(",")를 쓰면 따옴표 안의 콤마가 깨진다. 스니펫 2 참고.split("|")은 |가 정규식 OR라서 문자 단위로 쪼개진다. split("\\|")로 써야 한다.| 지정자/메서드 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
String.format(fmt, args) |
정적 포맷 | String.format("%s님 %d세", "홍길동", 30) |
"홍길동님 30세" |
"fmt".formatted(args) |
인스턴스 포맷 (JDK 15+) | "%s님 %d세".formatted("홍길동", 30) |
"홍길동님 30세" |
%d |
정수 | "%d".formatted(42) |
"42" |
%5d |
폭 5 우측 정렬 | "[%5d]".formatted(42) |
"[ 42]" |
%-5d |
폭 5 좌측 정렬 | "[%-5d]".formatted(42) |
"[42 ]" |
%05d |
0 채움 | "[%05d]".formatted(42) |
"[00042]" |
%,d |
천 단위 콤마 | "%,d".formatted(1234567) |
"1,234,567" |
%+d / %(d |
부호 / 음수 괄호 | "%+d %(d".formatted(5, -5) |
"+5 (5)" |
%.2f |
소수 2자리(반올림 HALF_UP) | "%.2f".formatted(3.14159) |
"3.14" |
%8.2f |
폭 8, 소수 2자리 | "%8.2f|".formatted(3.14159) |
" 3.14|" |
%,.2f |
콤마 + 소수 | "%,.2f".formatted(1234567.891) |
"1,234,567.89" |
%e |
지수 표기 | "%e".formatted(12345.678) |
"1.234568e+04" |
%s |
문자열(toString) | "[%10s]".formatted("hi") |
"[ hi]" |
%-10s |
좌측 정렬 | "[%-10s]".formatted("hi") |
"[hi ]" |
%.3s |
앞 3글자만 | "%.3s".formatted("abcdef") |
"abc" |
%c |
문자 | "%c".formatted(65) |
"A" |
%b |
boolean (null → false, 그 외 → true) | "%b".formatted("x") |
"true" |
%x / %X / %o |
16진/대문자/8진 | "%x %X %o".formatted(255, 255, 8) |
"ff FF 10" |
%% |
퍼센트 리터럴 | "%d%%".formatted(50) |
"50%" |
%n |
플랫폼 개행 | "a%nb".formatted() |
"a\r\nb" (Windows) |
%2$s %1$s |
인자 순서 지정 | "%2$s %1$s".formatted("world", "hello") |
"hello world" |
%tY-%<tm-%<td |
날짜(<는 앞 인자 재사용) |
"%tY-%<tm-%<td".formatted(LocalDate.of(2024,3,5)) |
"2024-03-05" |
System.out.printf(fmt, args) |
출력과 포맷 동시 | System.out.printf("%5.1f%n", 3.14159) |
3.1 |
""" 텍스트 블록 |
여러 줄 문자열(JDK 15+) | 아래 코드 | 아래 |
\ (줄 끝) |
텍스트 블록 줄 이어붙이기 | """\n hello \\\n world""" |
"hello world" |
\s |
텍스트 블록 끝 공백 유지 | """\n trailing \\s\n end""" |
"trailing \nend" |
String json = """
{
"name": "Java",
"ver": 21
}
""";
// 출력: {\n "name": "Java",\n "ver": 21\n}\n
// 닫는 """의 위치가 공통 들여쓰기 기준. 마지막 줄바꿈이 포함된다(닫는 """가 다음 줄에 있으면).%d에 double을 넣으면 IllegalFormatConversionException. 타입을 맞춘다.%.2f는 HALF_UP 반올림이다. 금액은 BigDecimal.setScale 후 toPlainString()을 쓴다(05 참고).| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
Pattern.compile(regex) |
컴파일(재사용하려면 static final) | Pattern p = Pattern.compile("(\\d{3})-(\\d{4})") |
Pattern |
Pattern.compile(regex, flags) |
플래그 | 아래 코드 | true |
Pattern.matches(regex, input) |
1회성 전체 일치 | Pattern.matches("[a-z]+", "abc") |
true |
p.matcher(input) |
Matcher 생성 | Matcher m = p.matcher("tel 010-1234, 031-9999") |
Matcher |
m.matches() |
전체 일치 | Pattern.compile("[a-z]+").matcher("abc1").matches() |
false |
m.lookingAt() |
시작 부분 일치 | Pattern.compile("[a-z]+").matcher("abc1").lookingAt() |
true |
m.find() |
다음 부분 일치 찾기(반복 호출) | while (m.find()) list.add(m.group()) |
[010-1234, 031-9999] |
m.group() / m.group(n) |
전체 / n번 그룹 | m.group(1) + "," + m.group(2) |
"010,1234" |
m.group("name") |
이름 있는 그룹 | Pattern.compile("(?<y>\\d{4})-(?<m>\\d{2})") → m.group("y") |
"2024" |
m.groupCount() |
그룹 수 | m.groupCount() |
2 |
m.start() / m.end() |
일치 위치 | m.start() |
4 |
m.results() |
모든 일치를 Stream으로 (JDK 9+) | 아래 코드 | [1, 22, 333] |
m.replaceAll(repl) |
치환 | "2024-05-17".replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1") |
"17/05/2024" |
${name} 치환 |
이름 그룹 참조 | 아래 코드 | "17.05.2024" |
m.replaceAll(Function) |
함수로 치환 (JDK 9+) | 아래 코드 | "a2b44" |
m.appendReplacement/appendTail |
수동 치환 루프 | 아래 코드 | "a<1>b<2>" |
Pattern.quote(s) |
리터럴로 이스케이프 | Pattern.quote("a.b") |
"\Qa.b\E" |
Matcher.quoteReplacement(s) |
치환문의 $,\ 이스케이프 |
Matcher.quoteReplacement("$1") |
"\$1" |
p.asPredicate() |
부분 일치 Predicate | 아래 코드 | [a1, c2] |
p.asMatchPredicate() |
전체 일치 Predicate | .filter(Pattern.compile("[a-z]\\d").asMatchPredicate()) |
[a1, c2] |
p.splitAsStream(s) |
분할 스트림 | Pattern.compile(",").splitAsStream("a,b,c").toList() |
[a, b, c] |
p.pattern() |
원본 정규식 | p.pattern() |
"(\d{3})-(\d{4})" |
Matcher m = Pattern.compile("\\d").matcher("a1b2");
StringBuilder sb = new StringBuilder();
while (m.find()) m.appendReplacement(sb, "<" + m.group() + ">");
m.appendTail(sb);
// 출력: a<1>b<2>
// 플래그(대소문자 무시)
boolean ci = Pattern.compile("java", Pattern.CASE_INSENSITIVE)
.matcher("JAVA").matches(); // true
// 모든 일치를 Stream으로 수집(JDK 9+)
List<String> nums = Pattern.compile("\\d+")
.matcher("a1b22c333")
.results().map(MatchResult::group).toList();
// [1, 22, 333]
// 이름 있는 그룹을 ${name}으로 치환
String d = "2024-05-17".replaceAll(
"(?<y>\\d{4})-(?<m>\\d{2})-(?<d>\\d{2})",
"${d}.${m}.${y}"); // "17.05.2024"
// 함수로 치환: 숫자를 2배로(JDK 9+)
String r = Pattern.compile("\\d+").matcher("a1b22")
.replaceAll(mr ->
String.valueOf(Integer.parseInt(mr.group()) * 2));
// "a2b44"
// 부분 일치 Predicate로 필터링
List<String> has = Stream.of("a1", "bb", "c2")
.filter(Pattern.compile("\\d").asPredicate())
.toList(); // [a1, c2]주요 정규식 문법 요약 (자세한 치트표는 05 시간·기타 유틸 참고):
| 문법 | 의미 | 예제 | 결과 |
|---|---|---|---|
\d \w \s |
숫자 / 단어문자 / 공백 | "a 1".replaceAll("\\s", "_") |
"a_1" |
[가-힣] |
한글 음절 | "가".matches("[가-힣]") |
true |
.* vs .*? |
탐욕 / 비탐욕 | "<a><b>".replaceAll("<.*?>", "X") |
"XX" (탐욕은 "X") |
(?i) (?s) (?m) |
대소문자 무시 / DOTALL / 여러 줄 | "HELLO".matches("(?i)hello") |
true |
\b |
단어 경계 | "cat category".replaceAll("\\bcat\\b", "dog") |
"dog category" |
(?<=x) (?=x) |
후방/전방 탐색 | "price: $100".replaceAll("(?<=\\$)\\d+", "N") |
"price: $N" |
$0 $1 |
치환문에서 전체/그룹 참조 | "aa bb cd".replaceAll("(\\w)\\1", "<$0>") |
"<aa> <bb> cd" |
a|b |
OR | "cat".matches("cat|dog") |
true |
\는 \\로 써야 한다. "\\d+"가 정규식 \d+.String.matches/replaceAll/split은 매번 Pattern.compile을 한다. 루프 안에서는 static final Pattern으로 미리 컴파일한다.$가 리터럴로 들어가면 Matcher.quoteReplacement로 감싼다.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
Integer.parseInt(s) |
문자열 → int | Integer.parseInt("42") + 1 |
43 |
Integer.parseInt(s, radix) |
진법 지정 | Integer.parseInt("ff", 16) |
255 |
Integer.valueOf(s) |
문자열 → Integer(박싱) | Integer.valueOf("42") |
42 |
Long.parseLong(s) |
문자열 → long | Long.parseLong("9000000000") |
9000000000 |
Double.parseDouble(s) |
문자열 → double | Double.parseDouble("3.5") |
3.5 |
Boolean.parseBoolean(s) |
"true"(대소문자 무시)만 true |
Boolean.parseBoolean("yes") |
false |
Integer.decode(s) |
접두사로 진법 판단 | Integer.decode("0x1F") |
31 |
new BigDecimal(s) |
정확한 소수 | new BigDecimal("1.10").toPlainString() |
"1.10" |
Integer.toString(n, radix) |
int → 진법 문자열 | Integer.toString(255, 16) |
"ff" |
Integer.toBinaryString(n) |
2진 | Integer.toBinaryString(10) |
"1010" |
Integer.toHexString(n) |
16진 | Integer.toHexString(255) |
"ff" |
Integer.toOctalString(n) |
8진 | Integer.toOctalString(8) |
"10" |
String.valueOf(n) / n + "" |
int → 문자열 | 42 + "" |
"42" |
(int) ch |
char → 코드 | (int) 'A' |
65 |
(char) n |
코드 → char | (char) 66 |
'B' |
ch - '0' |
숫자 문자 → 값 | '7' - '0' |
7 |
Character.getNumericValue(ch) |
숫자 문자 → 값 (안전) | Character.getNumericValue('7') |
7 |
Character.digit(ch, radix) |
진법 자릿값 | Character.digit('f', 16) |
15 |
Character.forDigit(n, radix) |
자릿값 → 문자 | Character.forDigit(11, 16) |
'b' |
NumberFormatException |
잘못된 입력 | Integer.parseInt("12a") |
For input string: "12a" |
| 공백 포함 | 공백도 실패 | Integer.parseInt(" 12") |
For input string: " 12" |
| 빈 문자열 | 실패 | Integer.parseInt("") |
For input string: "" |
strip() 후 파싱하고, NumberFormatException을 반드시 처리한다.'a' + 'b'는 195(int 덧셈)다. 문자열로 잇고 싶으면 "" + 'a' + 'b'.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
getBytes(StandardCharsets.UTF_8) |
UTF-8 바이트 (한글 3바이트) | "한글".getBytes(UTF_8).length |
6 |
getBytes("EUC-KR") |
EUC-KR (한글 2바이트) | "한글".getBytes("EUC-KR").length |
4 |
getBytes(UTF_16) |
UTF-16 (BOM 2 + 2×2) | "한글".getBytes(UTF_16).length |
6 |
getBytes() (인자 없음) |
기본 charset (JDK 18+ UTF-8) | Charset.defaultCharset() |
UTF-8 |
new String(bytes, charset) |
바이트 → 문자열 | new String(bytes, UTF_8) |
"한글" |
| 인코딩 불일치 | 깨진 문자 | new String("한글".getBytes("EUC-KR"), UTF_8) |
"�ѱ�" |
HexFormat.of().formatHex(bytes) |
바이트 → 16진 문자열 (JDK 17+) | HexFormat.of().formatHex("한글".getBytes(UTF_8)) |
"ed959ceab880" |
HexFormat.of().parseHex(s) |
16진 → 바이트 | HexFormat.of().parseHex("01ff") |
[1, -1] |
Base64.getEncoder().encodeToString(b) |
Base64 인코딩 | Base64.getEncoder().encodeToString("hello".getBytes()) |
"aGVsbG8=" |
Base64.getDecoder().decode(s) |
Base64 디코딩 | new String(Base64.getDecoder().decode("aGVsbG8=")) |
"hello" |
Base64.getUrlEncoder().withoutPadding() |
URL-safe, = 없이 |
토큰 생성에 사용 | -_ 사용 |
URLEncoder.encode(s, UTF_8) |
URL 인코딩 (공백→+) |
URLEncoder.encode("한 글&a=b", UTF_8) |
"%ED%95%9C+%EA%B8%80%26a%3Db" |
URLDecoder.decode(s, UTF_8) |
URL 디코딩 | URLDecoder.decode("%ED%95%9C+%EA%B8%80", UTF_8) |
"한 글" |
getBytes()/new String(bytes)처럼 인자 없는 버전은 환경에 따라 결과가 달라진다.getBytes(charset).length로 잰다. "abc한글".getBytes(UTF_8).length는 9.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
new StringBuilder() |
기본 용량 16 | new StringBuilder().capacity() |
16 |
new StringBuilder(int) |
용량 지정 | new StringBuilder(100).capacity() |
100 |
new StringBuilder(String) |
초기값 (용량 = 길이+16) | new StringBuilder("abc").capacity() |
19 |
append(x) |
끝에 추가(모든 타입 오버로드, 체이닝) | new StringBuilder("abc").append("d").append(1).append(true) |
"abcd1true" |
appendCodePoint(cp) |
코드포인트 추가 | new StringBuilder().appendCodePoint(0x1F600) |
"😀" |
insert(idx, x) |
위치에 삽입 | new StringBuilder("abc").insert(1, "X") |
"aXbc" |
delete(start, end) |
[start, end) 삭제 | new StringBuilder("abcdef").delete(1, 3) |
"adef" |
deleteCharAt(idx) |
한 문자 삭제 | new StringBuilder("abc").deleteCharAt(0) |
"bc" |
replace(start, end, s) |
구간 치환 (정규식 아님) | new StringBuilder("abcdef").replace(1, 3, "XY") |
"aXYdef" |
reverse() |
뒤집기 | new StringBuilder("abc").reverse() |
"cba" |
setCharAt(idx, ch) |
문자 교체 (void) | sb.setCharAt(0, 'Z') |
"Zbc" |
setLength(n) |
길이 자르기/늘리기 | sb.setLength(3) (from "abcdef") |
"abc" |
setLength(0) |
비우기(재사용 관용구) | sb.setLength(0) |
"" |
length() |
현재 길이 | new StringBuilder("abc").length() |
3 |
capacity() |
내부 버퍼 크기 | new StringBuilder("abc").capacity() |
19 |
ensureCapacity(n) |
미리 확보 | sb.ensureCapacity(1000) |
void |
trimToSize() |
버퍼를 길이에 맞춤 | sb.trimToSize() |
void |
charAt(idx) |
문자 조회 | new StringBuilder("abc").charAt(1) |
'b' |
indexOf(s) / lastIndexOf(s) |
검색 | new StringBuilder("abcabc").lastIndexOf("c") |
5 |
substring(a, b) |
String 추출 | new StringBuilder("abcdef").substring(2, 4) |
"cd" |
isEmpty() |
비었는지 (JDK 15+) | new StringBuilder().isEmpty() |
true |
chars() |
IntStream | new StringBuilder("ab").chars().sum() |
195 |
compareTo(sb) |
사전순 비교 (JDK 11+) | new StringBuilder("a").compareTo(new StringBuilder("b")) |
-1 |
toString() |
String으로 확정 | sb.toString() |
String |
equals(o) |
참조 비교 (내용 비교 아님!) | new StringBuilder("a").equals(new StringBuilder("a")) |
false |
StringBuilder는 스레드 안전하지 않다. 여러 스레드가 공유하면 StringBuffer(동기화) 또는 스레드별 인스턴스.+=로 문자열을 누적하면 O(n²). 반복 결합은 무조건 StringBuilder.sb.toString().equals(...) 또는 "a".contentEquals(sb).| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
Character.isDigit(ch) |
숫자 | Character.isDigit('7') |
true |
Character.isLetter(ch) |
문자(한글 포함) | Character.isLetter('가') |
true |
Character.isLetterOrDigit(ch) |
문자 또는 숫자 | Character.isLetterOrDigit('_') |
false |
Character.isAlphabetic(cp) |
알파벳 속성 | Character.isAlphabetic('a') |
true |
Character.isUpperCase(ch) |
대문자 | Character.isUpperCase('A') |
true |
Character.isLowerCase(ch) |
소문자 | Character.isLowerCase('a') |
true |
Character.isWhitespace(ch) |
공백(탭/개행 포함) | Character.isWhitespace('\t') |
true |
Character.isSpaceChar(ch) |
유니코드 공백 문자 | Character.isSpaceChar(' ') |
true |
Character.toUpperCase(ch) |
대문자로 | Character.toUpperCase('a') |
'A' |
Character.toLowerCase(ch) |
소문자로 | Character.toLowerCase('A') |
'a' |
Character.toString(cp) |
코드포인트 → 문자열 | Character.toString(65) |
"A" |
Character.toChars(cp) |
코드포인트 → char[] | new String(Character.toChars(0x1F600)) |
"😀" |
Character.isSurrogate(ch) |
서로게이트 쌍 여부 | Character.isSurrogate("😀".charAt(0)) |
true |
Character.getType(ch) |
유니코드 카테고리 | Character.getType('가') == Character.OTHER_LETTER |
true |
Character.UnicodeBlock.of(ch) |
블록 | Character.UnicodeBlock.of('가') |
HANGUL_SYLLABLES |
Character.compare(a, b) |
비교 | Character.compare('a', 'b') |
-1 |
Character.isJavaIdentifierStart(ch) |
식별자 시작 가능 | Character.isJavaIdentifierStart(' |
true |
Character.MAX_VALUE |
최대 char 값 | (int) Character.MAX_VALUE |
65535 |
이름은 가운데를 *로, 전화번호는 중간 자리를, 카드는 가운데 8자리를 가린다. 전화번호는 하이픈 유무에 관계없이 처리되도록 -?를 쓴다. 이메일은 앞 2글자만 남기고 @ 앞까지 마스킹한다(후방·전방 탐색 활용).
static String maskName(String name) {
if (name == null || name.length() < 2) return name;
if (name.length() == 2) return name.charAt(0) + "*";
return name.charAt(0) + "*".repeat(name.length() - 2) + name.charAt(name.length() - 1);
}
static String maskPhone(String phone) {
return phone.replaceAll("(\\d{3})-?(\\d{3,4})-?(\\d{4})", "$1-****-$3");
}
static String maskCard(String card) {
return card.replaceAll("(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})", "$1-****-****-$4");
}
static String maskEmail(String email) {
return email.replaceAll("(?<=.{2}).(?=[^@]*@)", "*");
}
maskName("홍길동"); maskName("김철"); maskName("남궁민수");
// 출력: 홍*동 김* 남**수
maskPhone("010-1234-5678"); maskPhone("01012345678");
// 출력: 010-****-5678 010-****-5678
maskCard("1234-5678-9012-3456");
// 출력: 1234-****-****-3456
maskEmail("hong.gildong@example.com");
// 출력: ho**********@example.comsplit(",")는 "Kim, Chul"처럼 따옴표 안의 콤마를 처리하지 못한다. 상태(따옴표 안/밖)를 추적하는 한 번의 순회로 처리하고, ""는 따옴표 하나로 해석한다. 실제 서비스에서는 OpenCSV 같은 라이브러리를 쓰되, 원리는 이렇다.
static List<String> parseCsvLine(String line) {
List<String> out = new ArrayList<>();
StringBuilder cur = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (inQuotes) {
if (c == '"') {
if (i + 1 < line.length() && line.charAt(i + 1) == '"') { cur.append('"'); i++; }
else inQuotes = false;
} else cur.append(c);
} else {
if (c == '"') inQuotes = true;
else if (c == ',') { out.add(cur.toString()); cur.setLength(0); }
else cur.append(c);
}
}
out.add(cur.toString());
return out;
}
parseCsvLine("1,\"Kim, Chul\",\"say \"\"hi\"\"\",,30");
// 출력: [1, Kim, Chul, say "hi", , 30]DB 컬럼(user_first_name)과 자바 필드(userFirstName) 사이 변환. 카멜→스네이크는 소문자/숫자 뒤의 대문자 앞에 _를 넣고, HTTPServer처럼 대문자 연속인 경우도 처리하기 위해 두 번째 치환을 추가했다.
static String camelToSnake(String s) {
return s.replaceAll("([a-z0-9])([A-Z])", "$1_$2")
.replaceAll("([A-Z])([A-Z][a-z])", "$1_$2")
.toLowerCase();
}
static String snakeToCamel(String s) {
StringBuilder sb = new StringBuilder();
boolean upper = false;
for (char c : s.toLowerCase().toCharArray()) {
if (c == '_') { upper = true; continue; }
sb.append(upper ? Character.toUpperCase(c) : c);
upper = false;
}
return sb.toString();
}
camelToSnake("userFirstName"); camelToSnake("HTTPServerURL"); camelToSnake("id");
// 출력: user_first_name http_server_url id
snakeToCamel("user_first_name"); snakeToCamel("ID");
// 출력: userFirstName ida.equals(b)는 a가 null이면 NPE. 세 가지 관용구: 상수를 앞에 두기, Objects.equals, Objects.toString(x, default). Optional.ofNullable(...).map(...).orElse(...)는 체인이 길 때 유용하다.
String status = null;
"ACTIVE".equals(status); // 출력: false (NPE 없음)
Objects.equals(status, "ACTIVE"); // 출력: false
Objects.equals(null, null); // 출력: true
Objects.toString(status, "-"); // 출력: -
Objects.requireNonNullElse(status, "N/A"); // 출력: N/A
Optional.ofNullable(status).map(String::strip).filter(s -> !s.isEmpty()).orElse("(empty)");
// 출력: (empty)
// 널 안전 isBlank
static boolean isBlank(String s) { return s == null || s.isBlank(); }주문번호 000042, 고정폭 리포트, 전문(전문 통신) 필드 채우기에 쓴다. String.format은 자르지 않으므로 최대 길이가 있으면 substring으로 먼저 자른다. 한글은 화면 폭이 2칸이지만 length()는 1이라 콘솔 정렬이 어긋난다(바이트 기반 패딩이 필요하면 getBytes 길이로 계산).
static String padLeft(String s, int width, char pad) {
return s.length() >= width ? s : String.valueOf(pad).repeat(width - s.length()) + s;
}
static String padRight(String s, int width, char pad) {
return s.length() >= width ? s : s + String.valueOf(pad).repeat(width - s.length());
}
static String fixed(String s, int width) { // 넘치면 자르고, 모자라면 공백 채움
if (s.length() > width) return s.substring(0, width);
return String.format("%-" + width + "s", s);
}
padLeft("42", 6, '0'); // 출력: 000042
padRight("ab", 5, '.'); // 출력: ab...
padLeft("toolong", 3, ' '); // 출력: toolong (자르지 않음)
fixed("한글", 6); // 출력: [한글 ]
fixed("abcdefgh", 6); // 출력: [abcdef]
String.format("%05d", 42); // 출력: 00042isNumeric, capitalize, 단어 뒤집기, 출현 횟수, 숫자만 남기기. 각각 정규식/indexOf 루프/Collections.reverse를 쓴다.
static boolean isNumeric(String s) { return s != null && s.matches("-?\\d+(\\.\\d+)?"); }
static String capitalize(String s) { return s.isEmpty() ? s : Character.toUpperCase(s.charAt(0)) + s.substring(1); }
static String reverseWords(String s) {
List<String> w = Arrays.asList(s.split(" "));
Collections.reverse(w);
return String.join(" ", w);
}
static int countOccur(String s, String sub) {
int count = 0, idx = 0;
while ((idx = s.indexOf(sub, idx)) != -1) { count++; idx += sub.length(); }
return count;
}
isNumeric("123"); isNumeric("12.3"); isNumeric("-5"); isNumeric(""); isNumeric("1e3");
// 출력: true true true false false
capitalize("hello"); // 출력: Hello
reverseWords("hello big world"); // 출력: world big hello
countOccur("banana", "an"); // 출력: 2
"010-1234-5678".replaceAll("[^0-9]", ""); // 출력: 01012345678
new StringBuilder("abc").reverse(); // 출력: cba검증 정규식은 static final Pattern으로 한 번만 컴파일한다. matcher(...).matches()가 String.matches보다 루프에서 훨씬 빠르다.
private static final Pattern PHONE = Pattern.compile("^01[016789]-?\\d{3,4}-?\\d{4}$");
private static final Pattern EMAIL = Pattern.compile("^[\\w.+-]+@[\\w-]+(\\.[\\w-]+)+$");
private static final Pattern KOREAN_NAME = Pattern.compile("^[가-힣]{2,5}$");
PHONE.matcher("010-1234-5678").matches(); // 출력: true
EMAIL.matcher("[email protected]").matches(); // 출력: true
KOREAN_NAME.matcher("홍gil").matches(); // 출력: false
// 텍스트에서 모든 전화번호 추출
Pattern.compile("01[016789]-\\d{3,4}-\\d{4}").matcher("A 010-1111-2222, B 011-333-4444")
.results().map(MatchResult::group).toList();
// 출력: [010-1111-2222, 011-333-4444]==로 문자열 비교String h = "hel";
("hel" + "lo") == "hello"; // true — 컴파일 타임 상수라 Pool 공유
(h + "lo") == "hello"; // false — 런타임 결합은 새 객체
(h + "lo").equals("hello"); // true ← 항상 이렇게| 잘못 | 올바름 |
|---|---|
s.toUpperCase(); (s는 그대로) |
s = s.toUpperCase(); |
s.replace("a", "b"); |
String r = s.replace("a", "b"); |
s.strip(); |
s = s.strip(); |
replace와 replaceAll 혼동, split 메타문자"a.b.c".replaceAll(".", "-"); // "-----" (.은 모든 문자)
"a.b.c".replace(".", "-"); // "a-b-c" (리터럴)
"a.b.c".split("."); // [] (빈 배열!)
"a.b.c".split("\\."); // [a, b, c]
"a|b".split("|"); // [a, |, b]
"a|b".split("\\|"); // [a, b]split이 끝의 빈 문자열을 버림"a,b,,".split(",").length; // 2 — 뒤의 빈 칸 2개가 사라짐
"a,b,,".split(",", -1).length; // 4 — CSV 컬럼 수를 유지하려면 limit -1+로 문자열 누적String s = "";
for (int i = 0; i < 100_000; i++) s += i; // 매번 새 String 생성, O(n²)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100_000; i++) sb.append(i); // O(n)
String s = sb.toString();char 덧셈과 문자열 결합 순서'a' + 'b' // 195 (int 덧셈)
"" + 'a' + 'b' // "ab"
1 + 2 + "3" + 4 + 5 // "3345" (왼쪽부터: 3 → "33" → "334" → "3345")
"a" + null // "anull" (NPE 아님)StringBuilder.equals로 내용 비교new StringBuilder("a").equals(new StringBuilder("a")); // false (참조 비교)
new StringBuilder("a").toString().equals("a"); // true
"a".contentEquals(new StringBuilder("a")); // truebyte[] b = "한글".getBytes(); // 환경 의존 (JDK 18+ 기본 UTF-8이지만 명시 권장)
byte[] b = "한글".getBytes(StandardCharsets.UTF_8); // 항상 6바이트
new String(b, StandardCharsets.UTF_8); // "한글"
new String("한글".getBytes("EUC-KR"), StandardCharsets.UTF_8); // "�ѱ�" 깨짐matches를 부분 일치로 착각"abc1".matches("[a-z]+"); // false (전체 일치)
Pattern.compile("[a-z]+").matcher("abc1").find(); // true (부분 일치)
Pattern.compile("[a-z]+").matcher("abc1").lookingAt(); // true (앞부분 일치)== vs equals, StringBuilder 내부, Wrapper 캐시char와 int 변환, 리터럴transform, Pattern.asPredicate, 메서드 참조chars(), lines(), Collectors.joiningString.format과 DecimalFormat 비교