문자열 포매팅 개념 설명
문자열 포매팅은 문자열 내에 특정 값을 삽입하거나 형식을 지정하는 방법입니다. 실제 데이터를 문자열 템플릿에 끼워 넣어 원하는 형태의 문자열을 만드는 과정입니다.
쉬운 설명
기본 포맷(틀)이 정해진 양식이 있고 비어있는 칸에 스티커를 붙여 넣는 것처럼 특정 값을 넣는 방식
- 편지지의 to __
- 주차권의 차량번호 : __ 입차시간 : __
- 학생 정보의 __ 학년 __ 반 __ 번
// 문자열 포매팅 예시
// 편지지 비유
String letter = "친애하는 %s님께".formatted(name);
// 주차권 비유
String parking = String.format("차량번호: %s, 입차시간: %s", carNumber, time);
// 학생 정보 비유
String student = String.format("%d학년 %d반 %d번 %s", grade, class, number, name);
역할
- 동적 데이터 삽입 : 문자열의 특정 지점에 새로운 데이터를 넣을 수 있다.
- 데이터 형식 지정 : 데이터를 넣을 때 데이터의 타입을 지정할 수 있다.
- 문자열 조합의 가독성 향상 : 단순 문자열 연결(+) 사용보다 가독성이 좋음
- 다국어 지원 용이성
//문자열 조합의 가독성 향상
1. 단순 문자열 연결(+) 사용시
// 1. 보기 어려운 방식
String message = "안녕하세요 " + name + "님, 현재 잔액은 " + balance + "원이고, " +
"등급은 " + grade + "입니다.";
// 2. 더 보기 어려운 복잡한 예시
String sql = "SELECT * FROM users WHERE age > " + age +
" AND city = '" + city + "' AND grade = '" +
grade + "' ORDER BY " + orderBy;
2. 포매팅 사용시
// 1. 깔끔한 포매팅
String message = String.format(
"안녕하세요 %s님, 현재 잔액은 %d원이고, 등급은 %s입니다.",
name, balance, grade
);
// 2. SQL 쿼리도 깔끔하게
String sql = String.format(
"SELECT * FROM users WHERE age > %d AND city = '%s' AND grade = '%s' ORDER BY %s",
age, city, grade, orderBy
);
사용 이유와 목적
- 코드의 가독성 향상
- 유지보수 용이성
- 다양한 데이터 타입 처리
- 일관된 출력 형식 유지
자주 하는 실수
- 포맷 지정자와 인자 개수 불일치
- 잘못된 포맷 지정자 사용
- 특수문자 이스케이프 처리 누락
- 타입 불일치
포매팅 방식 비교
방식 | 문법 | 장점 | 단점 |
---|---|---|---|
printf 스타일 | String.format("%s", value) | 익숙함, 다양한 형식 지원 | 타입 안정성 낮음 |
MessageFormat | MessageFormat.format("{0}", value) | 순서 변경 가능 | 제한된 형식 |
String.format() | String.format("%s", value) | 유연성 | 복잡한 구문 |
문자열 템플릿 | ${value} |
가독성 좋음 | Java 15 이상 필요 |
실무 활용 예시
// 로그 메시지
logger.info("User {} logged in at {}", username, timestamp);
// API 응답 메시지
String response = String.format("{'status': %d, 'message': '%s'}", code, message);
// 이메일 템플릿
String email = """
Dear %s,
Your order #%d has been confirmed.
Total amount: $%.2f
""".formatted(name, orderId, amount);
// 다국어 메시지
String welcome = MessageFormat.format(
resourceBundle.getString("welcome"), username);
// SQL 쿼리
String query = String.format(
"SELECT * FROM users WHERE age > %d AND city = '%s'",
age, city);
이러한 포매팅은 실무에서 로깅, 메시지 생성, 보고서 작성, UI 텍스트 등 다양한 상황에서 활용됩니다.
자바의 문자열 포매팅 종류
printf 스타일 포매팅 (%)
printf는 "print formatted"의 줄임말로, 출력할 형식을 미리 지정하고, 거기에 맞춰 데이터를 출력하는 메서드
//기본 구조
System.out.printf("형식지정자를 포함한 문자열", 값1, 값2, ...);
주요 포맷 지정자:
- %s: 문자열
- %d: 정수
- %f: 실수
- %b: boolean
- %c: 문자
- %x: 16진수
- %o: 8진수
- %n: 줄바꿈
//실제 예시
String name = "Kim"; // 문자열
int age = 25; // 정수
double height = 175.5; // 실수
char grade = 'A'; // 문자
boolean isStudent = true; // boolean
System.out.printf("이름: %s\n", name); // 이름: Kim
System.out.printf("나이: %d살\n", age); // 나이: 25살
System.out.printf("키: %f cm\n", height); // 키: 175.500000 cm
System.out.printf("학점: %c\n", grade); // 학점: A
System.out.printf("학생여부: %b\n", isStudent); // 학생여부: true
//자주하는 실수
// 잘못된 예시
System.out.printf("나이: %s", 25); // 정수에 %s 사용
System.out.printf("이름: %d", "Kim"); // 문자열에 %d 사용
// 올바른 예시
System.out.printf("나이: %d", 25); // 정수는 %d
System.out.printf("이름: %s", "Kim"); // 문자열은 %s
//실무에서 자주 사용하는 패턴
// 금액 표시
int price = 1000000;
System.out.printf("가격: %,d원", price); // 가격: 1,000,000원
// 퍼센트 표시
double percent = 0.845;
System.out.printf("달성률: %.1f%%", percent * 100); // 달성률: 84.5%
// 날짜 형식
System.out.printf("%1$tY년 %1$tm월 %1$td일", new Date());
MessageFormat
MessageFormat은 Java에서 제공하는 또 다른 문자열 포매팅 방식입니다. 주로 순서 기반의 포매팅이 필요할 때 사용됩니다.
//1. 기본 사용법
// 기본 형태
String message = MessageFormat.format("이름: {0}, 나이: {1}", "홍길동", 25);
// 결과: 이름: 홍길동, 나이: 25
// {0}, {1} 등은 순서를 나타내는 인덱스입니다
//홍길동이 {0}번 25가 {1}번
//2. printf와의 차이점
// printf 방식
String msg1 = String.format("이름: %s, 나이: %d", "홍길동", 25);
// MessageFormat 방식
String msg2 = MessageFormat.format("이름: {0}, 나이: {1}", "홍길동", 25);
//3. MessageFormat의 장점
// 1. 같은 값을 여러 번 사용 가능
String msg = MessageFormat.format("제가 {0}입니다. {0}은 {1}살입니다.", "홍길동", 25);
// 결과: 제가 홍길동입니다. 홍길동은 25살입니다.
// 2. 순서 변경 가능
String msg = MessageFormat.format("나이: {1}, 이름: {0}", "홍길동", 25);
// 결과: 나이: 25, 이름: 홍길동
// 3. 날짜, 숫자 형식화 지원
MessageFormat mf = new MessageFormat("날짜: {0,date,yyyy-MM-dd}");
String result = mf.format(new Object[]{new Date()});
- 순서 기반 인덱스 사용
- 인자 재사용 가능
- 날짜, 숫자 형식화 지원
//MessageFormat와 printf의 차이점
// MessageFormat - 인자 재사용 가능
String name = "홍길동";
String msg = MessageFormat.format(
"안녕하세요 {0}님! {0}님의 등급은 VIP입니다.",
name
);
// 결과: 안녕하세요 홍길동님! 홍길동님의 등급은 VIP입니다.
// printf - 인자 재사용 불가능
String msg2 = String.format(
"안녕하세요 %s님! %s님의 등급은 VIP입니다.",
name, name // 같은 값을 두 번 써야 함
);
특징 | printf | MessageFormat |
---|---|---|
문법 | %s, %d 등 | {0}, {1} 등 |
재사용 | 불가능 | 가능 |
순서변경 | 불가능 | 가능 |
형식화 | 제한적 | 다양함 |
사용성 | 간단함 | 더 유연함 |
String.format()
String.format()과 printf는 내부적으로 같은 포매팅 방식을 사용하지만, 목적과 동작이 다릅니다
- printf 스타일과 동일한 문법
- 문자열 반환
//1. 주요 차이점
// printf: 콘솔에 출력
System.out.printf("이름: %s", "홍길동");
// 화면에 출력됨
// String.format: 문자열 반환
String message = String.format("이름: %s", "홍길동");
// 문자열이 반환됨 (출력 안됨)
//2. 용도 비교
// 1. printf - 바로 출력이 필요할 때
System.out.printf("이름: %s, 나이: %d\n", "홍길동", 25);
// 바로 콘솔에 출력됨
// 2. String.format - 문자열을 변수에 저장하거나 다른 곳에서 사용할 때
String userInfo = String.format("이름: %s, 나이: %d", "홍길동", 25);
System.out.println(userInfo); // 나중에 출력
sendEmail(userInfo); // 이메일로 전송
saveToDatabase(userInfo); // DB에 저장
//3. 실무 활용 예시
// printf - 로그나 디버그 출력에 적합
System.out.printf("DEBUG: 처리 중... %d%%\n", progress);
// String.format - 데이터 가공이나 저장에 적합
String log = String.format("[%tF %tT] %s", now, now, message);
logger.info(log);
String query = String.format(
"SELECT * FROM users WHERE age > %d", age);
database.execute(query);
문자열 연결 (+)
문자열을 +를 통해서 하나로 합치는 방식
String message = "Name: " + name + ", Age: " + age;
- 단순하지만 가독성 낮음
- 성능 이슈 가능성
//1. 자바 vs 자바스크립트 비교
// 자바
String name = "홍길동";
int age = 25;
// 1. + 연산자 사용
String message = "이름: " + name + ", 나이: " + age;
// 자바스크립트
const name = "홍길동";
const age = 25;
// 1. + 연산자 사용
const message1 = "이름: " + name + ", 나이: " + age;
// 2. 템플릿 리터럴 사용 (더 편리)
const message2 = `이름: ${name}, 나이: ${age}`;
자바스크립트의 템플릿 리터럴처럼 편리한 문법은 Java 15부터 Text Blocks으로 제공됩니다
// Java 15+ Text Blocks
String message = """
안녕하세요 %s님
현재 잔액은 %d원 입니다.
""".formatted(name, balance);
//2. 자바의 문자열 연결 예시
// 1. 단순 연결
String result = "Hello" + " " + "World";
// 2. 변수와 연결
String name = "홍길동";
String greeting = "안녕하세요 " + name + "님";
// 3. 숫자와 연결
int age = 25;
String info = "나이: " + age + "살"; // 숫자가 자동으로 문자열로 변환
// 4. 여러 줄 연결
String message = "안녕하세요\n" +
"반갑습니다\n" +
"또 만나요";
//3. 주의사항
// 1. 성능 문제 (많은 + 연산은 비효율적)
// 나쁜 예
String result = "";
for(int i = 0; i < 1000; i++) {
result = result + i; // 매번 새로운 문자열 객체 생성
}
// 좋은 예
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
// 2. null 처리 주의
String name = null;
String message = "Hello " + name; // "Hello null" 출력
//4. 다른 방식과의 비교
String name = "홍길동";
int age = 25;
// 1. + 연산자 (단순하지만 가독성 낮음)
String msg1 = "이름: " + name + ", 나이: " + age;
// 2. String.format (깔끔하지만 약간 복잡)
String msg2 = String.format("이름: %s, 나이: %d", name, age);
// 3. MessageFormat (재사용성 높음)
String msg3 = MessageFormat.format("이름: {0}, 나이: {1}", name, age);
//5. 실무에서 자주 사용하는 경우
// 1. 간단한 메시지 조합
String welcomeMsg = "안녕하세요 " + userName + "님";
// 2. 경로 조합
String filePath = baseDir + "/" + fileName;
// 3. URL 생성
String url = baseUrl + "/api/" + endpoint;
// 4. 로그 메시지
String log = "[" + new Date() + "] " + logMessage;
StringBuilder/StringBuffer
StringBuilder와 StringBuffer는 문자열을 효율적으로 처리하기 위한 클래스입니다. String은 불변(immutable)이라 문자열 연산 시 매번 새로운 객체를 생성하지만, StringBuilder/StringBuffer는 가변(mutable)으로 동작하여 문자열 연산 시 기존 객체를 재사용합니다.
- String: 한 번 만든 종이는 수정할 수 없어서 새 종이를 만들어야 함
- StringBuilder/Buffer: 지우개로 지우고 다시 쓸 수 있는 화이트보드
StringBuilder의 실제 동작 방식
//1. 초기화 단계
StringBuilder logBuilder = new StringBuilder();
내부적으로 기본 16개 문자를 저장할 수 있는 char[] 배열이 생성됩니다.
이 배열은 문자들을 저장할 버퍼 역할을 합니다.
//2. append() 연산 동작
logBuilder.append("Time: ")
"Time: " 문자열의 길이를 확인
현재 버퍼의 남은 공간 확인
공간이 부족하면 버퍼 크기를 늘림 (기존 크기 * 2 + 2)
문자들을 내부 char[] 배열에 순차적으로 복사
//3. 연쇄 append() 호출
.append(timestamp).append(" User: ").append(userId)
각 append()는 StringBuilder 자신을 반환(this)
이를 통해 메소드 체이닝(method chaining) 가능
모든 문자가 하나의 버퍼에 순차적으로 추가
예시로 보는 내부 동작
// 초기 상태
char[] buffer = new char[16]; // [_, _, _, _, _, _, ...]
// "Time: " 추가 후
buffer = ['T','i','m','e',':',' ', _, _, ...]
// timestamp "2023-12-25" 추가 후
buffer = ['T','i','m','e',':',' ','2','0','2','3','-','1','2','-','2','5']
// 버퍼 크기 확장 후 " User: " 추가
buffer = ['T',...,'5',' ','U','s','e','r',':',' ', _, ...]
// userId "john123" 추가 후 최종
buffer = ['T',...,' ','j','o','h','n','1','2','3']
StringBuffer는 StringBuilder와 거의 동일하게 작동하지만, 가장 큰 차이점은 thread-safe하다는 것입니다.
Thread(스레드)란?
프로그램 내에서 실행되는 작업의 가장 작은 단위
하나의 프로그램이 여러 작업을 동시에 수행할 수 있게 해줌
//1. StringBuffer의 주요 특징
// StringBuffer의 메서드들은 synchronized 키워드가 붙어있음
public synchronized StringBuffer append(String str) {
// ...
return this;
}
//2. StringBuilder와 StringBuffer 비교
// StringBuffer (Thread-safe)
StringBuffer buffer = new StringBuffer();
// 여러 스레드가 동시에 접근해도 안전
synchronized void append() {
buffer.append("text");
}
// StringBuilder (Not Thread-safe)
StringBuilder builder = new StringBuilder();
// 여러 스레드가 동시에 접근하면 문제 발생 가능
void append() {
builder.append("text");
}
//3. 실제 사용 예시
// 멀티스레드 환경에서 안전하게 사용
public class ThreadSafeCounter {
private StringBuffer sharedBuffer = new StringBuffer();
public void addLog(String log) {
// 여러 스레드가 동시에 호출해도 안전
sharedBuffer.append(log).append("\n");
}
}
// 단일 스레드 환경에서는 StringBuilder 사용이 더 효율적
public class SingleThreadProcessor {
private StringBuilder localBuilder = new StringBuilder();
public void processData(String data) {
localBuilder.append(data);
}
}
//4. 성능 차이
// StringBuffer (더 느림)
StringBuffer buffer = new StringBuffer();
for(int i = 0; i < 1000000; i++) {
buffer.append("a"); // 동기화 오버헤드 발생
}
// StringBuilder (더 빠름)
StringBuilder builder = new StringBuilder();
for(int i = 0; i < 1000000; i++) {
builder.append("a"); // 동기화 없음
}
실제 사용 예시
// 1. 대량 로그 생성
StringBuilder logBuilder = new StringBuilder();
logBuilder.append("Time: ").append(timestamp)
.append(" User: ").append(userId);
// 2. HTML/XML 생성
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append("<div>")
.append(content)
.append("</div>");
// 3. 동적 쿼리 생성
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("SELECT * FROM users")
.append(" WHERE age > 20");
// 4. 대용량 텍스트 처리
StringBuilder textBuilder = new StringBuilder();
while(reader.hasNext()) {
textBuilder.append(reader.nextLine());
}
// 5. JSON 문자열 생성
StringBuilder jsonBuilder = new StringBuilder();
jsonBuilder.append("{")
.append("\"name\":\"").append(name).append("\",")
.append("\"age\":").append(age)
.append("}");
- 대량의 문자열 조작에 효율적
- 스레드 안전성(StringBuffer)
텍스트 블록 (Java 15+)
String message = """
Name: %s
Age: %d
""".formatted(name, age);
- 여러 줄 문자열에 효과적
- 가독성 우수
String.join()
String message = String.join(", ",
"Name: " + name,
"Age: " + age);
- 컬렉션 요소 결합에 유용
각 방식의 특징 비교
포매팅 방식 | 가독성 | 성능 | 유연성 | 타입 안전성 |
---|---|---|---|---|
printf 스타일 | 중간 | 좋음 | 높음 | 낮음 |
MessageFormat | 좋음 | 중간 | 중간 | 중간 |
String.format | 중간 | 좋음 | 높음 | 낮음 |
문자열 연결 | 낮음 | 낮음 | 높음 | 높음 |
StringBuilder | 낮음 | 매우 좋음 | 높음 | 높음 |
텍스트 블록 | 매우 좋음 | 좋음 | 높음 | 낮음 |
실무 활용 시나리오
로깅
logger.debug("User {} performed action {} at {}",
userId, action, timestamp);
SQL 쿼리
String query = String.format(
"SELECT * FROM %s WHERE id = %d",
tableName, id);
JSON 생성
String json = """
{
"name": "%s",
"age": %d,
"email": "%s"
}
""".formatted(name, age, email);
이메일 템플릿
String email = MessageFormat.format(
emailTemplate,
customerName,
orderNumber,
deliveryDate);
보고서 생성
StringBuilder report = new StringBuilder()
.append("Report for ").append(date).append("\n")
.append("Total Sales: ").append(sales).append("\n")
.append("Profit: ").append(profit);
각 상황에 맞는 적절한 포매팅 방식을 선택하는 것이 중요합니다.
'Java > 자바 학습' 카테고리의 다른 글
ArrayList와 LinkedList의 주요 차이점 (0) | 2025.05.10 |
---|---|
자바의 배열 (0) | 2025.05.09 |
자바의 String은 클래스다. (0) | 2025.05.09 |
문자 자료형과 메서드 (0) | 2025.05.08 |
자바 제네릭 (1) | 2025.05.07 |