BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Java 注目の機能: テキストブロック

Java 注目の機能: テキストブロック

ブックマーク

キーポイント

  • Java SE 13 (Sept 2019) introduced text blocks as a preview feature, aimed at reducing the pain of declaring and using multi-line string literals in Java. It was subsequently refined in a second preview, with minor changes, and is scheduled to become a permanent feature of the Java Language in Java SE 15(Sept 2020).
  • String literals in Java programs are not limited to short strings like "yes" and "no"; they often correspond to entire "programs" in structured languages such as HTML, SQL, XML, JSON, or even Java. 
  • Text blocks are string literals that can comprise multiple lines of text and uses triple quotes (""") as its opening and closing delimiter.
  • A text block can be thought of as a two dimensional block of text embedded in a Java program.

  • Being able to preserve the two-dimensional structure of that embedded program, without having to muck it up with escape characters and other linguistic intrusions, is less error-prone and results in more readable programs.

原文(投稿日:2020/05/01)へのリンク

プレビュー機能

Javaプラットフォームの世界的展開と高い互換性を考えると、言語機能の設計ミスのコストは非常に高くなります。言語の設計ミスの文脈では、互換性への責任は、その機能を削除したり、大幅に変更したりすることが非常に困難であることを意味するだけはありません。既存の機能は、将来の機能ができることを制約します。今日の輝かしい新機能は明日の互換性の制約です。

言語機能の究極の証明の場は、実際に使用されていることです。実際のコードベースで実際に試した開発者からのフィードバックは、機能が意図した通りに動作していることを確認するために不可欠です。Java が複数年に渡ってリリースサイクルを持っていた頃は、実験やフィードバックのための時間が十分にありました。新しい急速なリリースの流れの下で実験とフィードバックのための十分な時間を確保するために、新しい言語機能は、プラットフォームの一部であるプレビューの1回または複数回の繰り返しを経ることになります。しかし、個別に認可を得なければならず、まだ恒久的ではありません。開発者からのフィードバックに基づいて調整する必要がある場合には、そのようにします。これは、ミッションクリティカルなコードを壊すことなく実現できます。

QCon New YorkでのJava Futuresでは、Java言語アーキテクトのBrian Goetz氏が、Java言語のいくつかの最近の機能と将来の機能を弾丸ツアーで紹介しました。この記事では、彼はテキストブロックに飛び込んでいます。

Java SE 13(2019年9月)で テキストブロックプレビュー機能として導入しました。これは、Javaで複数行の文字列リテラルを使用して、宣言の苦痛を軽減することを目的としています。

その後、2回目のプレビューでは細かい変更が加えられ、洗練されました。 Java SE 15 (Sept 2020) では、Java言語の恒久的な機能となる予定がされています。

テキストブロックは、複数行のテキストを構成できる文字列リテラルです。テキストブロックは次のようになります。

String address = """
                 25 Main Street
                 Anytown, USA, 12345
                 """;

この単純な例では、変数の address は2行の文字列を含み、各行の後に行の終端があります。テキストブロックがなければ、書かなければならないことがありました。

String address = "25 Main Street\n" +
                 "Anytown, USA, 12345\n";
あるいは

String address = "25 Main Street\nAnytown, USA, 12345\n";

すべてのJava開発者がすでに知っているように、これらの代替案は書くのが面倒です。しかし、より重要なのは、それらはまた、より多くのエラーの傾向があり(\nを忘れやすくそれに気付かない)、より読みにくいです(言語構文が文字列の内容と混ざっているため)。テキストブロックは通常、エスケープ文字やその他の言語的な中断がないので、言語が邪魔にならないので、読み手は文字列の内容を見やすくなります。

文字列リテラルで最も一般的にエスケープされた文字は改行(\n)です。テキストブロックは複数行の文字列を直接表現できるようにすることで、これらの必要性を排除しています。改行の次に、最も一般的にエスケープされる文字は、ダブルクオート (\") です。それは文字列リテラルの区切り文字と競合するため、エスケープされなければなりません。テキストブロックは、これらも不要になります。なぜなら、シングルクォートはトリプルクォートのテキストブロックの区切り文字と競合しないからです。

なんで変な名前なの?

この機能は「複数行文字列リテラル」と呼ばれていたのではないかと思う人もいるかもしれません(そして、おそらく、多くの人がそう呼ぶでしょう)。 しかし、私たちはテキストブロックという別の名前を選びました。それは、テキストブロックが単に無関係な行の集まりではないという事実を強調するためです。というよりも、それはJavaプログラムに埋め込まれた二次元のテキストブロックと考えた方が良いでしょう。「二次元」の意味を説明するために、もう少し構造的な例を見てみましょう。それはテキストブロックがXMLのスニペットの例です。(他の何らかの「言語」の「プログラム」のスニペットである文字列についても、同様の考慮事項が適用されます。SQL、HTML、JSON、あるいはJavaのように、Javaプログラムにリテラルとして埋め込まれているものもあります)。

void m() {
    System.out.println("""
                       <person>
                           <firstName>Bob</firstName>
                           <lastName>Jones</lastName>
                       </person>
                       """);
}

その作者は何を期待して出力しているのでしょうか?私たちは彼らの心を読むことはできませんが、XMLブロックが21個の空白でインデントされるべきであるという意図があったとは思えません。この21個の空白は、テキストブロックを周囲のコードと並べるためだけに存在している可能性の方がはるかに高いです。一方で、出力の2行目が1行目よりも4つ多くの空白をインデントするのは、ほぼ間違いなく作者の意図です。さらに、たとえ作者がインデントの空白を正確に21個にしたいと思っていたとしても、プログラムが修正され、周囲のコードのインデントが変更された場合はどうなるのでしょうか?ソースコードが再フォーマットされたからといって、出力のインデントを変更したくはありません。また、テキストブロックが周囲のコードに対して「場違い」に見えるようにする必要もありません。なぜなら、理にかなった形で並んでないためです。

この例から、プログラムソースに埋め込まれた複数行のテキストのブロックの自然なインデントは、ブロックの行間に望む相対的なインデントと、ブロックと周囲のコード間の相対的なインデントの両方に由来することがわかります。私たちは文字列リテラルが私たちのコードとぴったりと合うようにしたいです(そうしないと場違いな感じになってしまうからです)。そして、私たちは文字列リテラルの行が行間の相対的なインデントを反映するようにしたいです。しかし、これら2つの私たちが付随する必須と呼べるインデントのソースは、プログラムのソースの表現の中で必然的に混在しています。(従来の文字列リテラルにはこの問題はありません。なぜなら、それらは行をまたぐことができないので、リテラルの先頭に余分な空白を入れて物事を並べようとする誘惑がないからです)。

この問題を解決する一つの方法は、Kotlin の trimIndent メソッドのような、複数行の文字列リテラルに適用できるライブラリのメソッドを使うことです。Javaは確かにそのようなメソッドを提供しています。それは String::stripIndent です。しかし、これは一般的な問題なので、Javaはさらに進んで、コンパイル時に付随するインデントを自動的に除去しています。

付随的なインデントと必須のインデントを分離するために、私たちはスニペット全体を含むXMLスニペットの周りに可能な限り小さな四角形を描き、この四角形の内容を2次元のテキストブロックとして扱うことを想像できます。この「魔法の四角形」はテキストブロックの内容であり、ブロックの行間の相対的なインデントを反映しますが、プログラムのインデントの仕方による影響であるインデントは無視されます。

この「魔法の四角形」の比喩は、テキストブロックがどのように機能するかを動機づけるのに役立つかもしれません。しかし、詳細はもう少し微妙です。なぜなら、インデントが付随的とみなされるか必須とみなされるかをより細かく制御したいと思うかもしれないからです。付随的なインデントと必須なインデントのバランスは、内容に対する後端の区切り文字の位置を使って調整できます。

詳細はこちら

テキストブロックは、その開閉の区切り文字として3つのダブルクオート ( """ ) を使用します。そして、開始の区切り文字のある行の残りは空白でなければなりません。テキストブロックの内容は、 次の行から始まり、 区切り文字を閉じるまで続きます。ブロックの内容のコンパイル時の処理には3つのフェーズがあります。

  • ラインターミネータは正規化されています。全てのラインターミネータをLF(\u000A)文字に置き換えています。これにより、テキストブロックの値が、コードが最後に編集されたプラットフォームの改行の規則の影響を受けないようにします。(Windows は CR + LF を使って行を終了させます。Unix システムでは LF のみを使います。他にもいろいろあります。)
  • 各行の先頭にある付随的な空白、および末尾にあるすべての空白は削除されます。付随的な空白は、以下のように決定されます。
    • 前のステップの結果の空白でないすべての行と、空白であっても最後の行(終了の区切り文字を含む行)である判断する行のセットを計算します。
    • すべての判断する行の共通の空白プレフィックスを計算します。
    • 各判断する行から共通の空白のプレフィックスを削除します。
  • コンテンツ内のエスケープシーケンスが解釈されます。テキストブロックは、文字列や文字リテラルと同じエスケープシーケンスのセットを使用します。最後にこれらを実行すると、\n, \t, \s, \<eol>のようなエスケープは空白処理に影響を与えないことを意味します。(JEP368の一部として、2つのエスケープシーケンスが追加されました。明示的な空白のための\sと、続けることを指示する\<eol>です。)

XML の例では、最初と最後の行からすべての空白が削除されます。そして、真ん中の2行は4つの空白でインデントされます。なぜなら、この例では5つの判断する行があるからです。XML コードを含む 4 行と終了の区切り文字を含む行を指定します。そして、内容の最初の行と同じくらいの空白ですべての行がインデントされています。多くの場合、このインデントは期待通りのものです。ですが、時には、先頭のインデントをすべて取り除きたくない場合もあります。例えば、ブロック全体を4つの空白でインデントしたい場合は、区切り文字を左に4つの空白だけ移動させることでできます。

void m() {
    System.out.println("""
                       <person>
                           <firstName>Bob</firstName>
                           <lastName>Jones</lastName>
                       </person>
                   """);
}

なぜなら、最後の一行は判断する行になっています。共通の空白のプレフィックスは、ブロックの最後の行の終了の区切り文字の前の空白の量になりました。そして、これが各行から削除される量です。ブロック全体を4つインデントしたままにします。インデントをプログラムで管理することもできます。これはインスタンスメソッド (String::indent) を使います。これは複数行の文字列 (テキストブロックから来ているかどうかに関わらず) を受け取り、各行を一定の空白数だけインデントします。

void m() {
    System.out.println("""
                       <person>
                           <firstName>Bob</firstName>
                           <lastName>Jones</lastName>
                       </person>
                       """.indent(4));
}

極端な場合には、空白を除去したくない場合には、終了区切り文字を左余白に移動させることができます。

void m() {
    System.out.println("""
                       <person>
                           <firstName>Bob</firstName>
                           <lastName>Jones</lastName>
                       </person>
""");
}

別の方法として、テキストブロックの本文全体を余白へ移動することでも同じ効果を得ることができます。

void m() {
    System.out.println("""
<person>
    <firstName>Bob</firstName>
    <lastName>Jones</lastName>
</person>
""");
}

これらのルールは、最初はやや複雑に聞こえるかもしれません。しかし、このルールは、周囲のプログラムに対してテキストブロックを相対的にインデントできるようにしたいという、さまざまな競合する懸念事項のバランスをとるために選択されましたが、可変量の偶発的な先頭の空白を発生させないようにします。また、デフォルトのアルゴリズムが望むものでない場合に、空白の除去を調整したり、手を引いたりするための簡単な方法を提供しています。

埋め込み式

Javaの文字列リテラルは、他のいくつかの言語がそうであるように、式の補間をサポートしていません。テキストブロックもそうではありません。(今後のある時点でこの機能を検討する可能性がある範囲では、テキストブロックに特化した機能ではないでしょう。ですが、文字列リテラルにも同じように適用されます。)。 歴史的に、パラメータ化された文字列式は、通常の文字列連結(+)で構築されていました。Java 5では、String::format が追加され、"printf"スタイルの文字列フォーマットをサポートするようになりました。

空白を取り巻くグローバルな解析のため、テキストブロックを文字列連結で結合する際にインデントを正しく行うのは難しい場合があります。しかし、テキストブロックは通常の文字列として評価されます。そのため、文字列式をパラメータ化するためにString::formatを使用できます。さらに、新しいString::formattedメソッドを使用できます。これはString::formatのインスタンス版です。

String person = """
                <person>
                    <firstName>%s</firstName>
                    <lastName>%s</lastName>
                </person>
                """.formatted(first, last));

(残念ながら、このメソッドもformatと呼べませんでした。 なぜなら、static メソッドとインスタンスメソッドを同じ名前とパラメータリストでオーバーロードできないからです。

前例と歴史

文字列リテラルはある意味で「ささいな」機能です。ですが、これらは小さな苛立ちが積み重なるほど頻繁に使用されます。ですから、複数行の文字列がないことが、近年のJavaに対する最も一般的な不満の一つになっているのは当然のことでしょう。また、他の多くの言語では、異なるユースケースをサポートするために複数の形式の文字列リテラルが存在します。

意外かもしれないのは、このような特徴が一般的な言語では様々な方法で表現されていることです。「私たちは複数行の文字列が欲しい」と言うのは簡単です。ですが、他の言語を調査してみると、構文も目標も驚くほど多様なアプローチがあります。(もちろん、「正しい」やり方については、開発者の意見も比較的幅広い) 2 つの言語が同じということはありませんが、幅広い言語に共通する機能のほとんどは ( for ループなど) 一般的には、言語が選ぶいくつかの共通のアプローチがあります。 ある機能の15の言語で15の異なる解釈を見つけるのは珍しいことです。しかし、複数行の文字列リテラルや生の文字列リテラルに関しては、まさにそれが判明しました。

次の表は、さまざまな言語での文字列リテラルのオプション(の一部)を示しています。それぞれにおいて、...は文字列リテラルの内容とみなされます。それはエスケープシーケンスや埋め込み補間のために処理されている場合とそうでない場合があります。 xxx は、文字列の内容と衝突しないことが保証された、ユーザーが選択したノンスを表します。そして、### 記号の可変数を表しています(0であっても構いません)。

言語 構文 注意事項
Bash '...' [span]
Bash $'...' [esc] [span]
Bash "..." [esc] [interp] [span]
C "..." [esc]
C++ "..." [esc]
C++ R"xxx(...)xxx" [span] [delim]
C# "..." [esc]
C# $"..." [esc] [interp]
C# @"..."  
Dart '...' [esc] [interp]
Dart "..." [esc] [interp]
Dart '''...''' [esc] [interp] [span]
Dart """...""" [esc] [interp] [span]
Dart r'...' [prefix]
Go "..." [esc]
Go ... [span]
Groovy '...' [esc]
Groovy "..." [esc] [interp]
Groovy '''...''' [esc] [span]
Groovy """...""" [esc] [interp] [span]
Haskell "..." [esc]
Java "..." [esc]
Javascript '...' [esc] [span]
Javascript "..." [esc] [span]
Javascript ... [esc] [interp] [span]
Kotlin "..." [esc] [interp]
Kotlin """...""" [interp] [span]
Perl '...'  
Perl "..." [esc] [interp]
Perl <<'xxx' [here]
Perl <<"xxx" [esc] [interp] [here]
Perl q{...} [span]
Perl qq{...} [esc] [interp] [span]
Python '...' [esc]
Python "..." [esc]
Python '''...''' [esc] [span]
Python """...""" [esc] [span]
Python r'...' [esc] [prefix]
Python f'...' [esc] [interp] [prefix]
Ruby '...' [span]
Ruby "..." [esc] [interp] [span]
Ruby %q{...} [span] [delim]
Ruby %Q{...} [esc] [interp] [span] [delim]
Ruby <<-xxx [here] [interp]
Ruby <<~xxx [here] [interp] [strip]
Rust "..." [esc] [span]
Rust r##"..."## [span] [delim]
Scala "..." [esc]
Scala """...""" [span]
Scala s"..." [esc] [interp]
Scala f"..." [esc] [interp]
Scala raw"..." [interp]
Swift ##"..."## [esc] [interp] [delim]
Swift ##"""..."""## [esc] [interp] [delim] [span]

凡例:

  • esc.いくつかのエスケープシーケンス処理では、エスケープは通常Cスタイル(例えば、\n)から派生します。
  • interp.変数や任意の式の補間をいくつかサポートしています。
  • span.複数行の文字列は、複数のソース行にまたがるだけで表現できます。
  • here.「ヒアドキュメント」は、文字列リテラルの本文として扱われます。ここで、ユーザが選択したノンスのみを含む行までは、次の行になります。
  • prefix.接頭辞の形式は、他のすべての文字列リテラル形式で有効です。簡潔にするために省略しました。
  • delim.区切り文字はある程度カスタマイズ可能です。ノンスを包含するか判断します(C++)。様々な数の# 文字(Rust、Swift)。または括弧を他のマッチした括弧に置き換えられます(Ruby)。
  • strip.付随するインデントのある程度の除去がサポートされています。

この表は、文字列リテラルへのアプローチの多様性を示すものではありますが、実際には表面を掻きむしっているだけです。言語が文字列リテラルをどのように解釈しているのか、その機微の多様性は、このような単純な形で捉えるにはあまりにも多様であるからです。ほとんどの言語では C に触発されたエスケープ言語が使われていますが、サポートしているエスケープの種類は様々です。それは、それらがユニコードエスケープ(例えば、\unnnn)をサポートしているかどうか、そして、完全なエスケープ言語をサポートしていない形式が、区切り文字のためのエスケープのいくつかの限定的な形式をまだサポートしているかどうかです。(文字列の最後に2つの引用符を使う代わりに埋め込み引用符を使うなど) この表では、簡潔さのために他の形式(C++で文字エンコーディングを制御するためのさまざまな接頭辞など)をいくつか省略しています。

言語間の最も明白な違いの軸は、区切り文字の選択であり、異なる区切り文字がどのように文字列リテラルの異なる形式を示すかということです。(エスケープの有無、単行または複数行、補間の有無、文字エンコーディングの選択など) しかし、行間を読むと、これらの構文的な選択が言語デザインに関する哲学的な違いを反映していることがよくわかります。これは、シンプルさ、表現力、ユーザーの利便性など、様々な目的のバランスをどうとるかです。

当然のことながら、スクリプト言語(bash、Perl、Ruby、Python)は「ユーザーの選択」を最優先にしており、非直交的な方法で変化する可能性のあるリテラルの多くの形式を備えています。(同じものを複数の表現方法で表現することが多い) しかし、一般的に、言語はユーザーに文字列リテラルについて考えるように促したり、どのように多くの形式を公開したり、それらの形式がどのように直交しているかという点で、あらゆる言語が存在しています。また、複数の線にまたがる文字列についての哲学もいくつか見られます。一部(JavascriptやGoなど)では、ラインターミネータをただの文字として扱っています。これにより、すべての形式の文字列リテラルが複数行にまたがることを可能にします。(C++など)一部では、これらを 「生の」文字列の特殊なケースとして扱うものもあります。(Kotlinなど)一部は文字列を 「単純」と「複雑」に分け、複数行の文字列を「複雑」のバケツに入れています。といった単純な分類にも逆らうほど多くの選択肢を提供しています。同様に、「生の文字列」の解釈も様々です。本当の「生の性質」は、ユーザーが制御可能な何らかの形式の区切り文字を必要とします(C++、Swift、Rustのように)。他の人は文字列を 「生」と呼んでいますが、それでも区切り文字を閉じる(固定された)区切り文字のために何らかの形でエスケープを予約しています。

様々なアプローチや意見がありますが、プリンシプルデザインと表現力の両立という観点からは、今回の調査では明確な「勝者」が存在します。それはSwiftです。エスケープ、補間、本当の生の性質を単一の柔軟なメカニズムでサポートしています (単一行と複数行のそれぞれの両方の形)。 あとになっての判断の恩恵を受けていたのだから、グループ内の新しい言語が一番きれいな話をしていてもおかしくはないです。そして、これは他の人の成功や失敗から学ぶことができました。(ここでの重要な技術革新は、エスケープ区切り文字が文字列の区切り文字に合わせて差があれば変化することです。それは「調理された」と「生」モードの間で選択する必要性を回避します。文字列リテラルのすべての形式でエスケープ言語を共有しています。「後になってみれば明らか」と高く評価されるようなアプローチであることがわかります)。 Javaは既存の言語の制約のためにSwiftのアプローチを卸して採用することができませんでした。Javaのアプローチは、Swiftコミュニティが行った良い仕事からできる限り多くのインスピレーションを得ました。そして、これは将来的に良くする余地を残しています。

危うく道に迷いそうになりました。

テキストブロックはこの機能の最初のイテレーションではありません。最初のイテレーションは 生の文字列リテラルでした。Rust の生の文字列のように、可変サイズの区切り文字(何文字ものバッククォート文字)を使用ます。それは内容を全く解釈しませんでした。この提案は、完全に設計・試作された後に取り下げられました。これは十分でしたが、あまりにも「側面に釘付け」になっていると判断されました。その方法は伝統的な文字列リテラルとの共通点が少なすぎたため、将来的に機能を拡張しようと思っても、それらを一緒に拡張する道はありませんでした。(急速にリリースされる周期のために、この機能は6ヶ月遅れるだけでした。これにより、機能が大幅に改善されました。)

JEP 326のアプローチに対する大きな反論の一つは、生の文字列が従来の文字列リテラルとはあらゆる方法で異なる働きをするということです。これは異なる区切り文字、可変区切り文字と固定区切り文字、単一行と複数行、エスケープと非エスケープ、などです。常に、誰かがいくつかの異なる組み合わせの選択肢を望んでいるでしょう。また、より多くの異なる形態を求める声があがるでしょう。これは Bash が行った道を案内するようです。その上で、「付随するインデント」の問題には何も対応していませんでした。これは明らかにJavaプログラムの脆さの原因になりそうでした。この経験から学んだことは、テキストブロックは従来の文字列リテラル(区切り文字構文、エスケープ言語)とより多くを共有しており、文字列が一次元の文字列の連続か、二次元のテキストブロックであるかという一つの重要な側面だけが異なります。

スタイルガイダンス

Oracle の Java チームの Jim Laskey 氏と Stuart Marks 氏は、テキストブロックの詳細と推奨スタイルの概要をまとめたプログラマーズガイドを発表しました。

コードの分かりやすさを向上させる場合は、テキストブロックを使用します。連結、エスケープされた改行、エスケープされた引用符区切り文字は、文字列リテラルの内容を難読化します。 テキストブロックは「邪魔にならないように」することで、内容がより明確になります。ですが、従来の文字列リテラルよりも構文的に重くなります。余分なコストの割に利益がある場合に使用してください。文字列が一行に収まり、エスケープされた改行がない場合は、伝統的な文字列リテラルにこだわるのがベストでしょう。

複雑な式の中でのインラインテキストブロックは避けます。テキストブロックは文字列を値とする式であるため、文字列が期待される場所であればどこでも使用することができます。複雑な式の中にテキストブロックを入れ子にするのは必ずしも最善ではありません。別の変数に格納した方が良い場合もあります。次の例では、テキストブロックがコードを読むときの流れを分断しています。これは、読者に精神的にギアを切り替えることを強制します。

String poem = new String(Files.readAllBytes(Paths.get("jabberwocky.txt")));
String middleVerses = Pattern.compile("\\n\\n")
                             .splitAsStream(poem)
                             .match(verse -> !"""
                                   'Twas brillig, and the slithy toves
                                   Did gyre and gimble in the wabe;
                                   All mimsy were the borogoves,
                                   And the mome raths outgrabe.
                                   """.equals(verse))
                             .collect(Collectors.joining("\n\n"));

テキストブロックを独自の変数に格納すると、読み手は計算の流れについていきやすくなります。

String firstLastVerse = """
    'Twas brillig, and the slithy toves
    Did gyre and gimble in the wabe;
    All mimsy were the borogoves,
    And the mome raths outgrabe.
    """;
String poem = new String(Files.readAllBytes(Paths.get("jabberwocky.txt")));
String middleVerses = Pattern.compile("\\n\\n")
                             .splitAsStream(poem)
                             .match(verse -> !firstLastVerse.equals(verse))
                             .collect(Collectors.joining("\n\n"));

テキストブロックのインデントに空白やタブが混ざらないようにします。付随するインデントを除去するアルゴリズムは、共通の空白の接頭辞を計算します。そのため、行が空白とタブの組み合わせで一貫してインデントされている場合は、これでも動作します。しかし、これは明らかに脆くてエラーが出やすいです。混ぜるのは避けた方がいいです。どちらか一方を使用します。

隣接するJavaコードにテキストブロックを調整します。付随する空白は自動的に除去されるので、これを利用してコードを読みやすくする必要があります。以下の様に書きたくなるかもしれませんが

void printPoem() {
    String poem = """
'Twas brillig, and the slithy toves
Did gyre and gimble in the wabe;
All mimsy were the borogoves,
And the mome raths outgrabe.
""";
    System.out.print(poem);

なぜなら、文字列の先頭にインデントを入れたくないからです。しかし、書くべきことがほとんどです。

void printPoem() {
    String poem = """
        'Twas brillig, and the slithy toves
        Did gyre and gimble in the wabe;
        All mimsy were the borogoves,
        And the mome raths outgrabe.
        """;
    System.out.print(poem);
}

なぜなら、これは読者の認知的な負荷を軽減するからです。

冒頭の区切り文字で文章を並べる義務を感じないようにしましょう。テキストブロックの内容を開始の区切り文字と並べることができます。

String poem = """
              'Twas brillig, and the slithy toves
              Did gyre and gimble in the wabe;
              All mimsy were the borogoves,
              And the mome raths outgrabe.
              """;

これは魅力的に見えるかもしれません。しかし、行が長い場合や区切り文字が左余白から始まる場合は面倒です。なぜなら、テキストが右マージンにずっと張り付いてしまうからです。しかし、この形式のインデントは必須ではありません。一貫してインデントを行う限り、どれだけ連続してインデントを使用しても構いません。

String poem = """
    'Twas brillig, and the slithy toves
    Did gyre and gimble in the wabe;
    All mimsy were the borogoves,
    And the mome raths outgrabe.
    """;

テキストブロックにトリプルクォートが埋め込まれている場合は、最初のクォートだけをエスケープしてください。すべての引用符をエスケープすることは可能ですが、それは必要ではなく、読みやすさを不必要に阻害します。最初の引用符だけをエスケープすることが必要です。

String code = """
    String source = \"""
        String message = "Hello, World!";
        System.out.println(message);
        \""";
    """;

非常に長い線を \ で分割してみてください。テキストブロックに加えて、2つの新しいエスケープシーケンス、\s(空白リテラルのため)と\<newline>(継続行を指示)を取得しました。 非常に長い行を持つリテラルがある場合は、ソースコードに改行を入れるために、\<newline>を使用することができます。これは文字列のコンパイル時のエスケープ処理の間に削除されます。

要約

Java プログラムの文字列リテラルは、「yes」「no」 のような短い文字列に限定されません。それらは、HTML、SQL、XML、JSON、あるいは Java のような構造化言語の「プログラム」全体に対応していることが多いのです。エスケープ文字やその他の言語的な侵入でプログラムを混乱させることなく、埋め込まれたプログラムの二次元構造を保持することができれば、エラーが発生しにくくなり、より読みやすいプログラムになります。

著者について

Brian Goetz 氏はオラクルのJava 言語アーキテクトで、JSR-335(Javaプログラミング言語用の Lambda 式)の仕様をリードしていました。 ベストセラー『Java Concurrency in Practice』の著者であり、Jimmy Carter 氏が大統領だった頃からプログラミングに魅了されてきました。

この記事に星をつける

おすすめ度
スタイル

こんにちは

コメントするには InfoQアカウントの登録 または が必要です。InfoQ に登録するとさまざまなことができます。

アカウント登録をしてInfoQをお楽しみください。

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

コミュニティコメント

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

BT