본문 바로가기
공식 문서/JDK

[JDK 15] Text Blocks

by lms0806 2024. 8. 4.
728x90
반응형

JAVA 공식 문서를 한글로 번역한 내용에 대하여 작성하였습니다.

JDK15에서 String 관련 새로운 기능이 추가되었습니다.

해당 기능은 JDK12에서 처음으로 등장했지만 릴리즈되지 않았고, JDK13, JDK14에서 미리볼 수 있는 방법으로 등장했었습니다.

이후 JDK15를 통해 릴리즈가 되었습니다.

목표

  • 여러 줄의 소스 코드에 걸쳐 있는 문자열을 쉽게 표현할 수 있도록 함
  • JAVA 프로그램의 문자열 가독성 향상
  • 문자열 리터럴과 동일한 방식으로 조작도리 수 있도록 규정하여 문자열 리터럴에서의 마이그레이션을 지원

동기부여

JAVA에서 HTML, XML, SQL 또는 JSON의 스니펫 문자열 리터럴에 임베드하려면 일반적으로 스니펫이 포함된 코드가 컴파일되기 전에 이스케이프 및 연결을 사용하여 상당한 편집이 사용됩니다. 이러한 스니펫은 읽기 어렵고 유지 관리가 힘듭니다.

 

문자열은 소스 파일의 한 줄에 표시할 수 있을 만큼 작고 쉽게 이스케이프할 수 있을 만큼 간단해야 합니다.

문자열 리터럴보다 문자열을 더 문자 그대로 표시하는 언어적 매커니즘을 여러 줄에 걸쳐, 그리고 이스케이프의 시각적 혼란 없이 갖는 것이 광범위한 JAVA 프로그램의 가독성과 쓰기성을 모두 개선할 것이다.

개발자가 줄바꿈이나 나타나는 위치와 관련된 문제로 텍스트 "블록"의 왼쪽과 오른쪽에 나타나는 공백의 양을 정확하게 제어할 수 있다면 도움이 될 것이다.

HTML 예제

  • 1차원
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";
  • 2차원
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

설명

텍스트 블록은 JAVA 언어의 새로운 종류의 리터럴입니다. 문자열 리터럴이 나타날 수 있는 모든 곳에서 문자열을 나타내는 데 사용할 수 있지만, 더 큰 표현력과 덜 우발적인 복잡성을 제공합니다.

텍스트 블록은 세개의 큰 따옴표(""") 문자 뒤에 0개 이상의 공백 문자가 이어지고 줄 종료 문자가 이어지는 시퀀스입니다.

닫는 구분기호 또한 세개의 큰 따옴표(""") 문자의 시퀀스입니다. 내용을 닫는 구분 기호의 첫 번째 큰따옴표 앞의 마지막 문자에서 끝납니다.

 

텍스트 블록에서 \"을 사용하는 것은 허용되지만 필수는 아니고 권장하지도 않습니다. 구분 기호(""")은 문자가 이스케이프되지 않은 것처럼 보이도록 하기 위하여 선택하였고, 텍스트 블록과 문자열 리터럴을 시각적으로 구분하기 위해서도 선택하였습니다. 텍스트 블록에서 \n를 사용하는 것은 허용되지만 필수 또는 권장되지는 않습니다.

"""
line1
line2
line3
"""
"line 1\nline 2\nline 3\n"
"line 1\n" +
"line 2\n" +
"line 3\n"

다음 3개의 문자열 리터럴은 모두 동일한 결과를 가져옵니다.

텍스트 블록은 빈 문자열을 나타낼 수 있지만 이는 두 줄의 소스코드가 필요하기 때문에 권장하지 않습니다.

String empty = """
""";

다음은 잘못 구성된 텍스트 블록들 입니다.

String a = """""";
String b = """ """;
String c = """
            ";
String d = """
            abc \ def
            """;

컴파일 타임 처리

텍스트 블록은 문자열 리터럴과 마찬가지 유형의 상수 표현식 String 입니다. 그러나 문자열 리터럴과 달리 텍스트 블록의 내용은 java 컴파일러에서 3가지 단계로 처리됩니다.

  1. 콘텐츠의 줄 종료 문자는 LF(\u000A) 로 변환됩니다. 이 변환의 목적은 java 소스코드를 플랫폼 간에 이동할 때 최소한의 원칙을 따르는 것입니다.
  2. java 소스 코드의 들여쓰기에 맞게 콘텐츠 주위에 생긴 빈 공간이 제거됩니다.
  3. 콘텐츠의 이스케이프 시퀀스는 해석됩니다. 해석을 마지막 단계로 수행한다는 것은 개발자가 \n 이전 단계에서 수정되거나 삭제되지 않고도 이스케이프 시퀀스를 작성할 수 있다는 것을 의미합니다.

처리된 콘텐츠는 문자열 리터럴의 문자와 마찬가지로 상수 풀의 CONSTANT_String_info 항목으로 클래스 파일에 기록됩니다. 클래스 파일은 CONSTANT_String_info 항목이 텍스트 블록 또는 문자열 리터럴에서 파생되었는지 여부를 기록하지 않습니다.

 

런타임 시 텍스트 블록은 문자열 리터럴과 마찬가지로 String 인스턴스로 평가됩니다. 처리된 내용이 동일한 두 개의 텍스트 블록은 문자열 리터럴과 마찬가지로 동일한 String 인스턴스를 참조합니다.

컴파일 타임 처리

1. Line terminators

Line terminators은 JAVA 컴파일러에 의해 CR(\u000D) 및 CRLF(\u000D\u000A)에서 LF(\u000A)로 정규화됩니다. 이렇게 하면 소스코드가 플랫폼 인코딩(javac-encoding)으로 변환된 경우에도 콘텐츠에서 파생된 문자열이 플랫폼 전체에서 동일하게 처리됩니다.

ex) Unix 플랫폼(LF)에서 생성된 Java 소스코드를 Windows 플랫폼(CRLF)에서 편집한 경우 정규화를 사용하지 않으면 내용이 각 문자마다 한 글자씩 길어집니다. LF를 Line terminators으로 사용하는 알고리즘은 실패할 수 있으며, String::equals를 사용하여 문자열 동일성을 확인하는 데 필요한 모든 테스트도 실패합니다.

이스케이프 시퀀스 \n(LF), \f(FF), \r(CR)은 정규화 중에 해석되지 않습니다.

2. 공백

텍스트 블록은 연결된 문자열 리터럴보다는 읽기 쉬웠지만, 텍스트 블록의 내용에 대한 명확한 해석에는 포함된 문자열들을 들여쓰기 위해 추가된 공백이 포함되어 여는 구분 기호와 깔끔하게 정렬됩니다.

String html = """
..............<html>
..............    <body>
..............        <p>Hello, world</p>
..............    </body>
..............</html>
..............""";

여는 구분 기호는 일반적으로 텍스트 블록을 사용하는 문이나 표현식과 같은 줄에 나타나도록 배치되므로 시각화된 14개의 공백이 각 줄을 시작한다는 사실에는 실질적인 의미가 없습니다. 콘텐츠에 이러한 공백을 포함하면 텍스트 블록이 연결된 문자열 리터럴로 표시된 문자열과 다른 문자열을 나타냄을 의미합니다.


또한 닫는 구분 기호는 일반적으로 콘텐츠와 정렬되도록 배치되며, 이는 시각화된 14개의 공간이 중요하지 않습니다.

Java 컴파일러는 개발자가 의도한 결과를 얻기 위해 부수적인 공백을 제거하여 콘텐츠를 처리합니다. 그런 다음 String::indent를 사용하여 원하는 경우 들여쓰기를 추가로 조작할 수 있습니다.

 

다시 들여쓰기 알고리즘은 line terminators가 LF로 정규화된 텍스트 블록의 내용을 사용합니다. 줄 중 가장 왼쪽 위치에 하나 이상의 공백이 아닌 문자가 있을 때까지 내용의 각 줄에서 동일한 양의 공백을 제거합니다.

여는 """문자의 위치는 알고리즘에 영향을 주지 않지만, 닫는 """ 문자의 위치는 자체 줄에 배치되는 경우 영향을 미칩니다.

 

알고리즘은 다음과 같습니다.

  1. 모든 LF에서 텍스트 블록의 내용을 분할하여 개별 줄 목록을 생성합니다. LF였던 내용의 모든 줄은 개별 줄 목록에서 빈 줄이 됩니다.
  2. 개별 줄 목록의 비어있지 않은 모든 줄을 결정 줄 집합에 추가합니다. (비어 있거나, 완전히 공백으로 구성된 줄은 들여쓰기에 눈에 띄는 영향을 미치지 않습니다.)
  3. 개별 줄 목록의 마지막 줄(즉, 닫는 구분 기호가 있는 줄)이 비어 있으면 해당 줄을 결정 줄 집합에 추가합니다. (닫는 구분 기호의 들여쓰기는 내용 전체의 들여쓰기에 영향을 주어야 합니다.)
  4. 각 줄의 선행 공백 문자 수를 세고, 최소 개수를 취합하여 결정 줄 집합의 공통 공백 접두사를 계산합니다.
  5. 개별 줄 목록의 비어 있지 않은 각 줄에서 공통 공백 접두사를 제거합니다.
  6. 5단계의 수정된 개별 줄 목록에 있는 모든 줄에서 모든 후행 공백을 제거합니다. 해당 단계에서는 수정된 목록의 전체 공백 줄이 비어 있도록 축소하지만 삭제되지는 않습니다.
  7. LF를 줄 사이의 구분 기호로 사용하여 6단계에서의 수정된 개별 줄 목록의 모든 줄을 결합하여 결과 문자열을 구성합니다. 6단계 목록의 마지막 줄이 비어 있으면 이전 줄에서 결합된 LF가 결과 문자열의 마지막 문자가 됩니다.

이스케이스 시퀀스인 \b(백스페이스), \t(탭), \s(스페이스)는 알고리즘에 의해 해석되지 않습니다. 이스케이프 처리는 나중에 발생합니다.

개발자는 새로운 인스터스 메소드인 String::stripIndent를 통해 액세스할 수 있습니다.

중요한 후행 정책

일반적으로 두 가지 방법으로 텍스트 블록의 형식을 지정합니다.

  1. 여는 구분 기호의 첫 번째 " 아래에 나타나는 콘텐츠의 왼쪽 가장자리를 배치합니다.
  2. 여는 구분 기호 바로 아래에 나타나도록 닫는 구분 기호를 자체 줄에 배치합니다.
    결과 문자열에는 줄 시작 부분에 공백이 없으며 닫는 구분 기호의 후행 공백 줄이 포함되지 않습니다.

그러나 후행 공백 줄은 결정 줄로 간주뫼드로 이를 왼쪽으로 이동하면 일반적인 공백 접두사가 줄어들어 모든 줄의 시작 부분에서 제거되는 공백의 양이 줄어드는 효과가 있습니다.
극단적인 경우, 닫는 구분 기호가 왼쪽으로 완전히 이동하면 공통 공백 접두사가 0으로 줄어들고 공백 제거가 효과적으로 선택 해제됩니다.

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
""";
728x90
반응형

댓글