String 클래스

public final class String {
    
    //문자열 보관
    private final char[] value; // 자바 9 이전
    private final byte[] value; // 자바 9 이후
    
    //여러 메서드
    public String concat(String str) {...}
    public int length() {...}
    ...
}

String 클래스는 직접 다루기 불편한 char[]을 내부에 감추고 개발자가 편리하게 문자열을 다룰 수 있도록 다양한 기능을 제공한다.

  • char은 2바이트(16비트) 유니코드 문자 UTF-16을 저장한다.

  • 모든 문자열은 무조건 2바이트 단위로 저장되기 때문에, 영문자나 숫자같이 1바이트로도 충분한 문자들도 2바이트 공간을 낭비하게 된다.

Java 9 이후 : byte[] + coder 기반

private final byte[] value;
private final byte coder; //0이면 LATIN1, 1이면 UTF16
  • 문자열이 라틴 문자 범위면 byte[] 하나만 사용한다. 이를 Compact Strings 라고 한다.

String 클래스 주요 메소드

  • length() : 문자열의 길이를 반환한다.

  • charAt(int index) : 특정 인덱스의 문자를 반환한다.

  • substring(int beginIndex, int endIndex) : 문자열의 부분 문자열을 반환한다.

  • indexOf(String str) : 특정 문자열이 시작되는 인덱스를 반환한다.

  • toLowerCase(), toUpperCase() : 문자열을 소문자 또는 대문자로 변환한다.

  • trim() : 문자열 양 끝의 공백을 제거한다.

  • concat(String str): 문자열을 더한다.

    • 하지만 a+b와 같은 연산을 편의로 제공한다.

String 클래스를 비교할 때는 == 비교가 아닌 항상 eqauls()(동등성) 비교를 해야한다.

  • 동일성(Identity) : == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인

  • 동등성(Equality) : equals() 메서드를 사용하여 두 객체가 논리적으로 같은지 확인

String이 불변으로 설계된 이유

  • String은 자바 내부에서 문자열 풀을 통해 최적화를 한다.

  • 문자열 풀은 자바 9 이후로 Heap 영역에 존재한다. 이전엔 메소드 영역

  • 문자열 풀에서 문자를 찾고 재사용할 때는 해시 알고리즘을 사용한다.

  • 문자열 풀에 있는 String 인스턴스의 값이 중간에 변경되면 같은 문자열을 참고하는 다른 변수의 값도 함께 변경된다. str3이 변경되면 str4이 참조하고 있는 것도 변경되기 때문에 불변 객체로 설계해 새로운 인스턴스를 만들어 반환한다.

불변인 String 클래스의 단점은 문자를 더하거나 변경할 때 마다 계속해서 새로운 객체를 생성한다는 점이다.

  • 실제로는 문자열을 다룰 때 자바가 내부에서 StringBuilder를 사용하도록 하거나 , 문자열 리터럴을 더하는 부분을 합쳐 문자열 리터럴이 하나만 생성되게 하는 등 최적화 하지만 특정 상황에선 이러한 최적화가 적용되지 않을 수 있다.

StringBuilder

문자열을 합칠 때 대부분의 경우 최적화가 되므로 + 연산을 사용하면 된다.

ㅡString Builder는 내부에 가변 byte[]를 가진다.

StringBuilder를 직접 사용하는 것이 더 좋은 경우

  • 반복문에서 반복해서 문자를 연결할 때

  • 조건문을 통해 동적으로 문자열을 조합할 때

  • 복잡한 문자열의 특정 부분을 변경해야 할 때

  • 매우 긴 대용량 문자열을 다룰 때

StringBuilder vs StringBuffer

  • StringBuffer는 내부에 동기화가 되어 있어, 멀티 스레드 상황에 안전하다. 하지만 동기화 오버헤드로 인해 성능이 느리다.

StringBuilder와 메서드 체인(Chain)

StringBuilder 에서 문자열을 변경하는 대부분의 메서드도 메서드 체이닝 기법을 제공하기 위해 자기 자신을 반환 한다. 예) insert() , delete() , reverse() 앞서 StringBuilder 를 사용한 코드는 다음과 같이 개선할 수 있다.

StringBuilder 는 메서드 체이닝 기법을 제공한다. StringBuilder 의 append() 메서드를 보면 자기 자신의 참조값을 반환한다.

String의 해시 캐싱(hash caching)은 hashCode() 메서드의 결과값을 한 번 계산해 저장해두고, 이후 반복 호출 시 다시 계산하지 않도록 하는 성능 최적화 기법이다. 이 매커니즘은 String이 불변이기 때문에 가능하다.

🎯 왜 캐싱하는가?

이유
설명

✅ 성능 개선

해시 값은 Map, Set 등에서 자주 사용되므로, 매번 계산하지 않음

✅ 가능 이유

String은 immutable(불변) → 내용이 바뀌지 않기 때문

✅ GC 영향 없음

int 타입으로 저장되며, 추가 객체 할당 없음

String 이 key로 사용될 때 해시 값을 통해 해시 테이블의 버킷 값 결정함.

항목
내용

캐싱 위치

private int hash 필드

최초 계산 시점

hashCode() 최초 호출 시

재사용 여부

이후 모든 호출에서 동일한 값 사용

기반 구조

Java 8: char[], Java 9+: byte[] + coder

장점

해시 테이블 기반 자료구조 사용 시 성능 대폭 향상

Last updated