문자열은 잘 다루기 어렵습니다. toUpperCase와 toLowerCase를 보다가, 문득 이런 질문을 하게 되었습니다.

 

 제 생각은 항상 같다. 였습니다. 어디까지나 내부 클래스들을 뜯기 전까지는요. 내부 클래스를 보고 공식 문서를 보니, 아니라는 것을 알기까지 그리 오랜 시간이 걸리지 않았습니다. 항상 맞다. 혹은 틀리다는 너무 극단적인 케이스이기도 하고요.

 


  이럴수가. 제가 알고 있었던 것이 사실이 아니였다니. 사실, 저는 heroes를 toUpperCase로 바꿔버리면 HEROES가 되고, giant를 대문자로 바꾸면 GIANT로 바뀌고, toLowerCases는 역방향으로 바뀔 거니, 길이가 항상 같을 거라고 생각했습니다.

 

 프로그램 1을 보겠습니다. 0x130이라는 값을 가지는 어떤 문자 하나를 ori에 넣었습니다. 그리고 그것을 바탕으로 heroes라는 문자열 하나를 생성하였습니다. 다음에 giants는 히어로즈에 있는 것들을 소문자화 한 결과를 저장합니다. 그 다음에 8번째 줄은 각각의 길이를 출력합니다. 상식선에서 생각을 한다면, 1, 1이 나올 겁니다.

 

 

  그런데, 상식 바깥의 일입니다. 1, 2가 나온 것입니다. 이건 어떻게 된 일일까요? jdk11+28로 돌린 결과입니다.

 

 

 제 로컬 환경에서 다시 실행해 보겠습니다. java -version을 치면 jdk 버전은 1.8.0_265가 뜹니다.

 

 

 디버그를 해 보면, 중간에 srcChar == '\u0130'인 부분이 있습니다. 여기에 걸리므로, 2616번째 줄을 수행합니다. 로케일은 디폴트일 거고. 일단 이 안으로 들어가 보겠습니다.

 

 

  그러면 왠 lookupTable 메소드를 호출합니다. 심상치 않아 보입니다.

 

 

 이 메서드 안으로 들어오면, entryTable을 돌리게 되는데요. src.codePointAt(index)는, 스트링의 index번째의 코드 포인트를 의미합니다. 그러면 이 엔트리 테이블에 어떤 것이 들어가 있는지를 파악해야 하는데요. 직관적으로, 저 클래스에 공통적으로 들어가 있는 테이블일 거 같지 않나요?

 

 

 실제로 ConditionalSpecialCasing 클래스의 Entry를 보면 사전 식으로 무언가가 들어감을 알 수 있습니다. 이 중에는 Locale 센시티브한 것들도 있고, 그렇지 않은 것도 있습니다. 0x130이 소문자로 변환디는 경우, 0x0069, 0x0307 배열로 변환이 되고, 0x03A3은 0x03C2로 변환된다. 정도를 볼 수 있었습니다. 조금 더 읽어보면, 특수한 케이스에 대해서는 또 다르게 변환을 한다는 것을 볼 수 있습니다.

 

 

 이게 정확히 무엇인지는 잘 모르겠습니다만, 그 상황은 '로케일' 때문이다. 정도만 눈치 채셔도 나쁘지 않을 듯 싶습니다.

 

 

 이러한 정보들은 entryTable에 로케일에 따라서 어떻게 변환할 것인지가 들어가 있습니다. 그러면, 거기에서 꺼내오기만 하면 될 겁니다. 이 과정에서 상황에 따라서, 2개의 크기를 가지는 배열이 뽑아집니다. 여기까지 정리해 보면 특정 상황에서,  0x0130이라는 문자 하나를 소문자로, Lowercase로 변환한 결과가, [0x0069, 0x0307] 이였다 입니다.

 

 

 따라서, 어떠한 문자열에다가 toLowerCase를 한 결과의 length와 원본의 length 값이 다를 수도 있다는 것은 참입니다. 당연하게도. 스페셜 케이스 클래스를 잘 읽어보면, 로케일에 따라서 변환 결과가 다르게 나올 수도 있다는 것 또한 눈치채실 수 있습니다.

 

 


 그러면 역방향은 어떨까요? 아래 코드를 디버깅 해 보겠습니다.

 

 크게 어렵지는 않습니다. 다만, 0x00df라는 값을 ori에 넣고, toLowerCase대신에 toUpperCase를 썼을 뿐입니다.

 

 

  CharacterDataLatin1의 toUpperCaseEx의 234번째 줄에 걸려버립니다. 이는, 해당 맵에서의 오버 플로우 때문에 그런 것으로 보입니다.

 

 그러면 toUpperCase 메소드를 계속 수행해 보겠습니다. upperChar가 Error인 상태였고, locale이 특정 값이 아니라면, 2786번째 줄을 수행하게 될 겁니다. 이 안으로 들어가 봅시다.

 

 

 계속 들어가다 보면, ch가 0x00DF일 때, upperMap을 sharpsMap으로 셋팅하고, 이 값을 리턴한다고 되어 있습니다. 문자 하나가 들어왔는데, S가 2개. 로케일에 따라서는 toUpperCase 또한 길이가 달라질 수 있습니다.

 

 

 결론. 로케일 설정에 따라서 대문자나 소문자로 변환하기 전의 문자열 길이와, 그 후의 길이가 다를 수도 있다. 입니다. 이에 대한 것은, 공식 문서에도 언급이 되어 있으니, 한 번 정도는 읽어보시는 게 좋겠습니다.