Post

[JAVA] String 클래스

[JAVA] String 클래스

자바에서 문자열

문자 하나: char

1
char c = 'A';

자바에서 문자를 다루는 가장 기본적인 타입.

1
2
char[] arr = {'h', 'e', 'l', 'l', 'o'};
System.out.println(arr); // hello
  • 여러 문자를 다루려면 char[] 을 사용한다.
  • 하지만 char[] 는 직접다루기 불편하기에 자바에서는 String 클래스를 제공한다.

문자열: String

1
2
String str1 = "hello";             //리터럴 방식
String str2 = new String("hello"); // 객체 생성 방식
  • 문자열 리터럴은 문자열 풀에 저장되어 중복 방지 및 성능 최적화가 된다.
  • 생성자 방식은 매번 새로운 객체가 생성된다.

String 클래스는 참조형

  • String 은 클래스 즉 참조형
  • 내부적으로 문자 배열(char[] 또는 Java 9 이후 byte[])을 사용해 문자열 저장
  • 문자열 더하기는 concat() 또는 + 연산자 사용 가능
1
2
3
4
5
String a = "hello";
String b = " java";
System.out.println(a.concat(b)); // hello java
System.out.println(a + b);       // hello java
//+ 연산자는 자바가 내부적으로 StringBuilder 를 사용해 최적화함

문자열 비교 == vs equals()

1
2
3
4
5
6
7
8
9
10
11
String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1 == s2);       // false: 참조값 비교
System.out.println(s1.equals(s2));  // true: 문자열 내용 비교

String s3 = "hello";
String s4 = "hello";

System.out.println(s3 == s4);       // true: 문자열 풀 덕분에 같은 참조
System.out.println(s3.equals(s4));  // true
  • 문자열 비교는 항상 equals() 를 사용해야 안전
  • 메서드로 전달된 문자열이 리터럴인지 new 인지 알 수 없기 때문에 == 사용 금지

String 클래스 - 불변 객체

불변 객체란?

  • 생성 이후 내부 값이 변경되지 않는 객체
  • String 은 대표적인 불변 객체
1
2
3
String str = "hello";
str.concat(" java");
System.out.println("str = " + str); // 출력: hello

concat() 은 새로운 문자열을 반환하지만 str 변수에 할당하지 않았기 때문에 원본은 변하지 않는다.

String이 불변하게 설계된 이유

  • 문자열 풀에서 같은 문자열을 여러 객체가 공유하기 때문에
  • 만약 하나의 값을 변경하면 다른 모든 참조도 영향을 받게되기 때문 → 사이드 이펙트
1
2
3
String a = "hello";
String b = "hello";
// 만약 b가 내부 값을 바꾸면? → a 도 영향을 받게 됨 (위험!)

이런 문제를 방지하기 위해 String은 불변으로 설계되었다.

String 주요 메서드

문자열 정보 확인

1
2
3
4
5
String str = "Hello, Java!";
str.length();      // 12
str.isEmpty();     // false
str.isBlank();     // false (Java 11 이상)
str.charAt(7);     // 'J'

문자열 비교

1
2
3
4
5
str1.equals(str2);               // 값 비교
str1.equalsIgnoreCase(str2);    // 대소문자 무시
str1.compareTo(str3);           // 사전순 비교
str1.startsWith("Hello");       // true
str1.endsWith("Java!");         // true

문자열 검색

1
2
3
str.contains("Java");           // true
str.indexOf("Java");            // 첫 등장 위치
str.lastIndexOf("Java");        // 마지막 등장 위치

이 외에도 substring, replace, split, join, format, matches 등

문자열 조작 및 변환

부분 문자열 추출: substring()

1
2
3
String str = "Hello, Java!";
str.substring(7);        // "Java!"
str.substring(7, 12);    // "Java!"

문자열 연결: concat()

1
str.concat("!!!");       // "Hello, Java!!!!"

문자열 치환: replace(), replaceFirst(), replaceAll()

1
2
str.replace("Java", "World");         // 모든 Java → World
str.replaceFirst("Java", "World");    // 첫 번째 Java → World

대소문자 변환: toLowerCase(), toUpperCase()

1
2
str.toLowerCase(); // 소문자
str.toUpperCase(); // 대문자

공백 제거: trim(), strip(), stripLeading(), stripTrailing()

1
2
3
4
str.trim();          // 양쪽 공백 제거
str.strip();         // 양쪽 유니코드 공백 제거 (Java 11+)
str.stripLeading();  // 앞 공백 제거
str.stripTrailing(); // 뒤 공백 제거

문자열 분할 및 결합

분할: split()

1
2
String str = "Apple,Banana,Orange";
String[] arr = str.split(",");

결합: String.join()

1
2
String.join("-", "A", "B", "C"); // "A-B-C"
String.join("-", arr);           // "Apple-Banana-Orange"

기타 유틸리티

문자열 변환: valueOf()

1
2
3
String.valueOf(100);      // "100"
String.valueOf(true);     // "true"
String.valueOf(obj);      // obj.toString() 값

참고: “” + x 로도 문자열 변환 가능

문자 배열 변환: toCharArray()

1
char[] chars = str.toCharArray();

문자열 포맷: String.format(), System.out.printf()

1
2
String.format("숫자: %d, 문자열: %s", 10, "hello");
// 출력: 숫자: 10, 문자열: hello
포맷 코드 설명
%d 정수 (int)
%s 문자열 (String)
%b boolean 값
%f 실수 (float)

정규식 매칭: matches()

1
str.matches("Hello, (Java!|World!)"); // true

  • 자바 String 클래스는 기능이 매우 풍부하기 때문에 자주 쓰는 메서드 위주로 익히고, 필요할 땐 API 문서로 확인하는 것이 효율적
  • 문자열 변환이 많거나 반복되는 조작이 필요할 땐 StringBuilder, StringBuffer 활용도 고려
  • Java 21 String 클래스 공식 API 문서
  • Java 8 String 클래스 API

StringBuilder

불변인 String의 단점

  • String은 불변객체이기 때문에 문자열을 변경할 때마다 새로운 객체가 생성된다.
  • 예: "A" + "B" + "C" + "D"new String("A") → "AB" → "ABC" → "ABCD"
  • 중간에 생성된 문자열들은 사용되지도 않고 버려짐 → 메모리 낭비, GC 부하
1
String str = "A" + "B" + "C" + "D"; // 실제론 여러 객체 생성됨

StringBuilder(가변 문자열) 등장

  • StringBuilder가변(mutable) 객체
  • 내부 문자열을 직접 수정하므로 새로운 객체를 만들지 않음
  • 문자열 변경이 빈번한 상황에서 훨씬 빠르고 효율적
1
2
3
StringBuilder sb = new StringBuilder();
sb.append("A").append("B").append("C");
System.out.println(sb.toString()); // ABC

StringBuilder는 쓰레드 안전하지 않음. 멀티쓰레드 환경에서는 StringBuffer 사용

StringBuilder 주요 메서드

메서드 설명
append(str) 문자열 추가
insert(idx, str) 특정 위치에 삽입
delete(start, end) 특정 범위 삭제
reverse() 문자열 뒤집기
toString() 최종 문자열 반환 (String)
1
2
3
4
5
StringBuilder sb = new StringBuilder("ABCD");
sb.insert(4, "Java");          // ABCDJava
sb.delete(4, 8);               // ABCD
sb.reverse();                  // DCBA
String result = sb.toString(); // DCBA

성능 비교: 반복문에서 문자열 더하기

String 사용 시 (비효율적)

1
2
3
4
String result = "";
for (int i = 0; i < 100000; i++) {
    result += "Hello ";
}
  • 문자열을 더할 때마다 새로운 객체 생성
  • 실행 시간: 2.5초 (Mac 기준)

StringBuilder 사용 시

1
2
3
4
5
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    sb.append("Hello ");
}
String result = sb.toString();
  • 문자열을 한 객체에서 수정
  • 실행 시간: 3ms

자바의 문자열 최적화

  • 자바는 문자열 리터럴을 컴파일 시 자동으로 합침 (리터럴 최적화)

    1
    
      String s = "Hello, " + "World!"; // → "Hello, World!"
    
  • 변수는 컴파일 시 최적화 불가 → 내부적으로 StringBuilder 사용

    1
    2
    
      String result = str1 + str2;
      // 내부적으로 → new StringBuilder().append(str1).append(str2).toString();
    

언제 StringBuilder를 직접 써야 할까?

상황 설명
반복문에서 문자열 결합 루프마다 문자열 생성 방지
조건문 등 동적 조합 if, switch 등에서 조합이 자주 발생할 때
긴 문자열 예: HTML 생성, 로그 조립 등
문자열 일부 변경 중간 삽입, 삭제, 교체

StringBuilder vs StringBuffer

클래스 쓰레드 안전성 속도
StringBuilder 비동기 → 빠름 빠름
StringBuffer 동기화 → 안전 느림

멀티쓰레드 환경에선 StringBuffer 사용. 단일 스레드에선 StringBuilder가 성능 우위


  • String은 불변이라 작은 작업에도 객체를 많이 생성
  • 자주 변경되는 문자열 작업엔 StringBuilder를 사용해야 성능이 훨씬 좋다
  • 자바는 자동으로 최적화하는 경우도 있지만, 반복문 안에선 직접 StringBuilder 사용 권장

StringBuilder와 메서드 체인(Chain)

StringBuilder 는 메서드 체이닝 기법을 제공한다

메서드 체이닝이란?

  • 메서드 호출 결과로 자기 자신(this)을 반환하면, 그 반환값을 이어서 다시 메서드 호출 가능
  • 즉, .method1().method2().method3() 처럼 연속 호출되는 구조

기본 구조

1
2
3
4
5
6
7
8
9
10
11
12
public class ValueAdder {
    private int value;

    public ValueAdder add(int addValue) {
        value += addValue;
        return this; // 자신의 참조값 반환
    }

    public int getValue() {
        return value;
    }
}

일반 호출 vs 체이닝 호출

일반 방식

1
2
3
4
5
ValueAdder adder = new ValueAdder();
adder.add(1);
adder.add(2);
adder.add(3);
System.out.println(adder.getValue()); // 6

체이닝 방식

1
2
3
ValueAdder adder = new ValueAdder();
int result = adder.add(1).add(2).add(3).getValue();
System.out.println(result); // 6

변수 선언 없이 바로 연결 가능 → 코드가 간결해짐


StringBuilder와 메서드 체이닝

자바의 StringBuilder도 체이닝을 지원하는 대표적인 클래스

1
2
3
4
5
6
7
8
9
10
11
String result = new StringBuilder()
    .append("A")
    .append("B")
    .append("C")
    .append("D")
    .insert(4, "Java")
    .delete(4, 8)
    .reverse()
    .toString();

System.out.println(result); // DCBA

append(), insert(), delete() 등 거의 모든 메서드가 this를 반환함

→ 한 줄로 복잡한 문자열 처리 가능


체이닝이 가능한 이유

  • 메서드가 return this; 를 하고 있기 때문
  • 즉, 반환값이 자기 자신이기 때문에 . 으로 계속 이어질 수 있음

코드가 간결해지고 가독성이 좋아지지만, 너무 길거나 조건문이 섞이면 오히려 가독성이 저하된다. 메서드 체이닝은 클래스 작성자는 조금 번거롭지만, 사용자는 훨씬 편리해진다.



출처: 김영한의 실전 자바 - 중급 1편

This post is licensed under CC BY 4.0 by the author.

Trending Tags