[CS] 자바의 정석 독서 #16 - String 클래스 파헤치기

2025. 11. 18. 07:32·언어공부/Java | Kotlin

왜 200쪽짜리 자몽살구클럽은 2시간만에 읽고 150쪽짜리 java.lang 패키지와 유용한 클래스는 며칠동안 읽고있지...?

 

https://dev-dx2d2y-log.tistory.com/122

 

[CS] 자바의 정석 독서 #15 - java.lang 패키지와 Object 클래스

java.lang 패키지 알아보기java.lang 패키지는 출력문을 나타내는 System 클래스, Object 클래스, String 클래스 등, 자바 프로그래밍에서 가장 기본이 되는 클래스들을 포함하는 패키지이다. 그래서 import

dev-dx2d2y-log.tistory.com

저번에는 Object 클래스 내부에서 equals, hashCode, toString, clone, getClass 메서드에 대해서 알아봤다.

오늘은 String 클래스를 알아볼 예정이다.


String

C에서는 문자열을 활용하려면 char 배열을 사용해야했지만 자바는 문자열을 위한 클래스인 String 클래스를 지원한다. 편하다.


String 클래스의 특징

- 불변클래스

    @Stable
    private final byte[] value;

어쨌든 String도 내부적으로는 byte 배열을 사용하는데 이 내부적 byte가 불변으로 선언되어있다. 

 

원래 JDK9 이전까지는 char[]를 써왔는데, 효율적 메모리 사용을 위해 byte[]로 바뀌었다. 이를 '컴팩트 문자열'이라고 칭한다.

자바는 JDK18 이전까지 UTF-16을 통해 인코딩을 진행해왔는데 (이후 UTF-8로 변경) UTF-16에서는 모든 문자열이 2 바이트로 저장되기 때문에 영어나 숫자 등의 1바이트 문자도 2바이트로 저장되는 문제가 있었다.

 

그래서 char[]이 아니라 byte[]를 사용하여 문자열의 모든 문자가 Latin-1로 인코딩되어 1바이트로 표현 가능한 경우, 1바이트로 저장되도록 변경한 것이다.

 

https://velog.io/@roro/Java-%EC%9E%90%EB%B0%94-String-byte-Compact-Strings

 

[Java] - 자바 String byte[], Compact Strings

Java 8 기준으로 쓰여진 책이나 인터넷에 보면 String에 대해 아래와 같이 나와있다. > String은 인스턴스 생성 시 입력받은 문자열을 내부 인스턴스변수인 문자형 배열(char[]) 로 저장한다. 본인도 예

velog.io

좀 더 자세한 설명은 이 글을 보면 좋을듯하다.

 

static final boolean COMPACT_STRINGS;

static {
    COMPACT_STRINGS = true;
}
    
public String(String original) {
    this.value = original.value;
    this.coder = original.coder;
    this.hash = original.hash;
}

if (COMPACT_STRINGS) {
        byte[] val = StringLatin1.toBytes(codePoints, offset, count);
        if (val != null) {
            this.coder = LATIN1;
            this.value = val;
            return;
        }
 }

생성자를 통해 문자열을 생성한 경우 value(위에 나온 바이트배열), hash(해시코드)와 함께 coder 변수도 받아오도록하고, coder라는 변수도 받아오게되는데 컴팩트문자열이 true로 설정된 경우 (LATIN-1로 인코딩된 경우) coder의 값이 LATIN1로 설정되게 된다.

 

private static byte[] encode8859_1(byte coder, byte[] val, boolean doReplace) {
        if (coder == LATIN1) {
            return Arrays.copyOf(val, val.length);
        }
        int len = val.length >> 1;
        byte[] dst = new byte[len];
        int dp = 0;
        int sp = 0;
        int sl = len;
        while (sp < sl) {
            int ret = StringCoding.implEncodeISOArray(val, sp, dst, dp, len);
            sp = sp + ret;
            dp = dp + ret;
            if (ret != len) {
                if (!doReplace) {
                    throwUnmappable(sp);
                }
                char c = StringUTF16.getChar(val, sp++);
                if (Character.isHighSurrogate(c) && sp < sl &&
                        Character.isLowSurrogate(StringUTF16.getChar(val, sp))) {
                    sp++;
                }
                dst[dp++] = '?';
                len = sl - sp;
            }
        }
        if (dp == dst.length) {
            return dst;
        }
        return Arrays.copyOf(dst, dp);
    }

 

그리고 나중에 LATIN-1로의 인코딩이 필요한 경우 encode8859_1 메서드가 불려지게 되는데, coder 값이 앞서 LATIN1로 설정된 경우에는 그냥 배열복사로 마무리되고, 그렇지 않은 경우(UTF-16으로 설정됐다던가) while 루프를 돌며 2바이트 배열을 1바이트 배열로 바꿔서 반환하게 된다.

 

즉, 입력은 UTF-16이나 UTF-8로 입력했어도 출력 시 LATIN-1로 인코딩된 값을 원할 때 저 코드가 호출된다. 인코딩 방식을 바꿀 때 필요하기 때문에 문자열에 저장된 coder 값을 검사하고 LATIN1일 경우에는 이미 1바이트 배열로 저장되었으므로 그대로 배열복사하고 반환, LATIN1이 아닐 경우에는 2바이트 배열을 1바이트 배열로 바꿔 반환하게 된다.

그래서 한글 등 2바이트 배열이 반드시 필요한 배열을 encode8859_1 메서드를 호출하여 인코딩하면 문자가 깨지게된다.

 

그리고 저 코드에서 밑으로 내려가면 UTF-8 인코딩과 관련된 코드들이 있다. 

 

    public String(String original) {
        this.value = original.value;
        this.coder = original.coder;
        this.hash = original.hash;
    }

암튼 문자열을 저장하면 이 value 값에 문자열이 저장된다.

 

그렇기 때문에 한 번 생성된 String 클래스는 문자열을 읽어올 수만 있으며, 변경할 수 없다. 어떤식으로든 String 변수에 저장된 내요잉 바뀌면 새 객체가 생성된다.


- 문자열의 비교

String s1 = "abc";
String s2 = "abc";

System.out.println(s1 == s2);			//true
System.out.println(s1.equals(s2));		//true

흔히 String을 선언할 때 문자열 리터럴을 지정하는데, 이 경우 기존에 존재하는 문자열을 재사용하게된다.

그래서 위의 경우에는 s1과 s2가 같은 String 객체를 가리키게 된다.

 

String s1 = new String("abc");
String s2 = new String("abc");

System.out.println(s1 == s2);			//false
System.out.println(s1.equals(s2));		//true

이처럼 생성자를 통해 String 객체를 선언할 경우에는 같은 문자열이더라도 서로 다른 주소에서 생성된다.

다만 String 클래스는 equals() 메서드를 재정의했으므로 equals 메서드를 사용할 경우 내용이 같다면 true를 반환한다.

 

문자열 리터럴을 사용할 경우 클래스 파일의 상수영역에 단 한 번만 저장되고, 같은 문자열 리터럴을 사용하는 변수들끼리는 이 문자열 리터럴을 공유한다. 불변객체기 때문에 큰 상관이 없다.


String의 생성자와 메서드

생성자는 주로 String 클래스를 넣거나, char 배열을 넣거나, StringBuffer(뒤에서 나온다)를 넣어 String Buffer가 갖고 있는 문자열가 같은 내용의 String 클래스를 매개변수로 주어 String 객체를 형성할 수 있다.

 

메서드는 유용하기도한데 너무 많아서 자바의 정석 ch09 p.499를 참고해아할 듯하다.

이 게시글에 적기도 양이 많고 다른 게시글에 따로 작성하기도 양이 많다.


생성자는 문자열(String), 문자배열(char []), 스트링버퍼(StringBuffer) 를 받아 생성할 수 있다.


어느정도 쓰일 것 같은 메서드들은..

 

int compareTo(String str)

문자열과 매개변수의 문자열 str을 비교한다. 문자열이 사전순으로 이전이면 음수, 이후면 양수

 

boolean contains(CharSequence s)

문자열에 대해 지정된 문자열(s)이 있는지 확인다.

 

int indexOf(int ch / int ch, int pos / String str)

문자열에서 문자 / 문자열이 있는지 검사한다. 매개변수가 int로 선언되었지만 문자를 줘야한다. pos가 붙으면 문자열에서 그 번호부터 인덱스를 조사한다.

 

String str = "1,2,3";

String[] strArr = str.split(",");	//[ "1", "2", "3" ]
String[] strArr2 = str.split(",", 2) //[ "1", "2,3" ]

String[] split(String regex / String regex, int limit)

문자열 regex를 기준으로 나누어 문자 배열에 나눈다. limit도 같이 선언되면 limit 수만큼 문자열을 나눈다.

 

String toString()

문자열을 반환한다. 이전에 Object 클래스에서 한 번 해봤다.

 

String valueOf (boolean b / char c / int i / long l / float f / double d / Object o)

지정된 값을 문자열로 반환한다. 참조변수는 toString()을 호출한 결과를 반환한다.

숫자에 그대로 빈 문자열 ""을 더해주는 방법도 있는데, 빈 문자열을 더하는 것이 편할지라도 성능은 valueOf가 더 좋다.

 

문자열을 다시 Intger, Double 등으로 다시 변환할 경우에도 valueOf를 사용하면 된다. 바꾸려는 타입의 클래스에 저장된 valueOf()를 사용하면 된다. Integer.valueOf, Double.valueOf 등.. Integer.valueOf()를 사용하면 내부적으로는 parseInt가 호출되는데, 이름의 통일성을 위해 valueOf()가 나중에 추가되었다.

 

String nums = "1,2,3";
String[] arr = nums.split(",");

String str = String.join("&", arr);
System.out.println(str);		//1&2&3
StringJoiner sj = new StringJoiner("&");
String[] strArr = {"123", "456", "789"};

for (String str : strArr) {
	sj.add(str);
}

System.out.println(sj.toString());

join() / StringJoiner

여러 문자열 사이에 구분자를 넣는다. 이 때 여러 문자열은 배열에 선언되어있고 이 배열의 문자열을 주어진 구분자를 통해 결합한다. StringJoiner는 인자로 문자열 배열을 전해주면 주어진 구분자로 배열의 요소들을 합친다.

 

2025.11.19 수정

StringBuffer의 생성자 관련된 내용을 StringBuffer의 메서드와 관련된 내용과 합쳐 새 포스트로 이동시켰다.

링크 > https://dev-dx2d2y-log.tistory.com/125

 

[CS] 자바의 정석 독서 #17 - StringBuffer, StringBuilder

어제는 String에 대해서 알아봤는데,String은 불변객체기 때문에 값을 수정하거나 새로운 값을 저장할 때마다 새로운 String 객체가 생성된다. 간단한 프로젝트나 현재의 JVM환경에서는 크게 못 느낄

dev-dx2d2y-log.tistory.com

 

'언어공부 > Java | Kotlin' 카테고리의 다른 글

[CS] 자바의정석 독서 #18 - 컬렉션은 무엇인가?  (1) 2025.11.20
[CS] 자바의 정석 독서 #17 - StringBuffer, StringBuilder  (0) 2025.11.19
[CS] 자바의 정석 독서 #14 - 예외처리  (0) 2025.11.11
[CS] 이펙티브 자바 독서 #3 - 아이템85. 직렬화를 피하라!  (0) 2025.10.23
[CS] 스레드란 무엇일까?  (0) 2025.10.15
'언어공부/Java | Kotlin' 카테고리의 다른 글
  • [CS] 자바의정석 독서 #18 - 컬렉션은 무엇인가?
  • [CS] 자바의 정석 독서 #17 - StringBuffer, StringBuilder
  • [CS] 자바의 정석 독서 #14 - 예외처리
  • [CS] 이펙티브 자바 독서 #3 - 아이템85. 직렬화를 피하라!
Radiata
Radiata
개발을 합니다.
  • Radiata
    DDD
    Radiata
  • 전체
    오늘
    어제
    • 분류 전체보기 (211)
      • 신년사 (3)
        • 2025년 (2)
        • 2026년 (1)
      • CS (59)
        • JVM (12)
        • 백엔드 (20)
        • 언어구현 (1)
        • 객체지향 (1)
        • 논리회로 (5)
        • 컴퓨터구조 (9)
        • 데이터베이스 (1)
        • 컴퓨터 네트워크 (10)
      • 언어공부 (64)
        • Java | Kotlin (48)
        • JavaScript | TypeScript (9)
        • C | C++ (6)
      • 개인 프로젝트 (11)
        • [2025] Happy2SendingMails (3)
        • [2026] 골든리포트! (8)
        • [2026] 순수자바로 개발하기 (0)
        • 기타 이것저것 (0)
      • 팀 프로젝트 (29)
        • [2025][GDG]홍대 맛집 아카이빙 프로젝트 (29)
      • 알고리즘 (13)
        • 백준풀이기록 (11)
      • 놀이터 (0)
      • 에러 수정일지 (2)
      • 고찰 (24)
        • CEOS 23기 회고록 (2)
  • 블로그 메뉴

    • CS
    • 언어공부
    • 개인 프로젝트
    • 팀 프로젝트
    • 알고리즘
    • 고찰
    • 신년사
    • 컬러잇 개발블로그
  • 링크

    • 컬러잇 개발블로그
  • 공지사항

  • 인기 글

  • 태그

    144
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Radiata
[CS] 자바의 정석 독서 #16 - String 클래스 파헤치기
상단으로

티스토리툴바