LoginSignup
8
1

Java の文字列リテラルの最大長は?

Last updated at Posted at 2023-12-13

Java の文字列リテラルは、最長で何文字まで許容されるのでしょうか。
具体的には、以下のように AAA… と文字を並べて何文字までならコンパイルができるのか、調べてみました。

String str = "AAAAAAAA…(略)…AAAA";

今回は「文字列リテラル(ソースコード上の " " で囲われた文字列)」の話です。

Java 言語仕様

まず、Java 言語仕様を確認しました。しかし、文字列リテラルの最大長については触れられていませんでした。
Java Language Specification - Chapter 3. Lexical Structure - 3.10.5. String Literals

つまり、仕様未定義のようです。
そのため、文字列リテラルの長さをどこまで許容するのかは、 コンパイラの実装依存になるようです。

Java 仮想マシン仕様

一方で、Java 仮想マシン仕様には長さに関する項目があります。

文字列リテラルは、クラスファイルの定数プールに CONSTANT_Utf8_info 構造体として格納されるのですが、この構造体にはUTF-8のバイト配列とその長さが含まれています。

CONSTANT_Utf8_info {
    u1 tag;            // タグ (0x1)
    u2 length;         // バイト配列の長さ
    u1 bytes[length];  // バイト配列(中身は修正UTF-8)
}

Java Virtual Machine Specification - Chapter 4. The class File Format - 4.4.7. The CONSTANT_Utf8_info Structure

この長さのフィールドは u2(2バイト)なので、最大値は 65,535 です。
文字列リテラルは修正UTF-8で格納されているので、'A' (= UTF-8で1バイト)であれば 65,535 文字まではOKのように見えます。

OpenJDK コンパイラの場合

では、実際に OpenJDK コンパイラは、文字列リテラルの長さをどこまで許容しているのでしょうか。
検証してもよくわからなかったので、 StackOverflow で聞きました。

その結果、以下の両方の条件を満たす場合 はコンパイルが通ることがわかりました。

  1. 文字数は、65,534文字まで
  2. UTF-8 で、65,535バイトまで

例えば、下記の A(UTF-8で1バイトの文字)が 65,534 個の場合はコンパイルができます。
一方、65,535 個だと1の条件に引っかかって、コンパイルエラー「定数文字列が長すぎます」 になります。

String ok = "AAAAAAAAAA…(略)…AAAA";   // 65534個なら OK
String ng = "AAAAAAAAAA…(略)…AAAAA";  // 65535個なら NG

また、1文字目が ©(UTF-8で2バイトの文字)、2文字目以降が A 66,533個だと、1と2の両方を満たすのでコンパイルができます。
さらに、2文字目の A© に置き換えると2の条件に引っかかって、別のコンパイルエラー「文字列"©©AAAAAAAAAAAAAAAAAA..."のUTF8表現が、定数プールに対して長すぎます」 になります。

String ok = "©AAAAAAAAA…(略)…AAAA";  // © が1個 + A が65534個なら OK
String ng = "©©AAAAAAAA…(略)…AAAA";  // © が2個 + A が65533個なら NG

まとめると、このようになります。
今回のコードは Gist に置いておきました

文字列リテラル 文字数 UTF-8 結果
A が 65,534個 65,534文字 65,534バイト OK
A が 65,535個 65,535文字 65,535バイト NG
© + A が65,533個 65,534文字 65,535バイト OK
©© + A が65,532個 65,534文字 65,536バイト NG

前者のエラー「定数文字列が長すぎます」になるのが、コンパイラのこの部分です。
jdk/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java at 9320ef9b29927b8fff52055d7a7a89db9b15b95b · openjdk/jdk

private void checkStringConstant(DiagnosticPosition pos, Object constValue) {
    if (nerrs != 0 || // only complain about a long string once
        constValue == null ||
        !(constValue instanceof String str) ||
        str.length() < PoolWriter.MAX_STRING_LENGTH)
        return;
    log.error(pos, Errors.LimitString);
    nerrs++;
}

なぜか、文字数定数プールの文字列の長さの上限(UTF-8 で 65,535 バイト) を比較しています。

回避策は簡単です。
2つの変数に分けて格納して、+ 演算子でつなげればOKです。

String str1 = "AAAAAAAAAA…(略)…AAAA";
String str2 = "AAAAAAAAAA…(略)…AAAA";
String str = str1 + str2;    // 文字列の長さは Integer.MAX_VALUE まで OK 

変数に格納せずに + 演算子で文字列リテラルをつなげるのは駄目です。コンパイル段階で一つの文字列リテラルになってしまい、結局コンパイルエラーになります。

Eclipse JDT コンパイラの場合

eclipse 内蔵のコンパイラだと、何文字でもOKです。
OpenJDK コンパイラがNGになったパターンでも、そのままコンパイルが通ります。1

コンパイラが、定数プールに収まるように複数の文字列リテラルに分割して、実行時に StringBuilder で結合して一つの文字列にする、という形にコンパイルしてくれるためです。

まとめ

もともと、これは JJUG CCC 2023 Fall のバイナリビューアを使ってクラスファイルを読んでみよう!というセッションの資料に「文字列リテラルの長さは、UTF-8 で最大65,535バイト」って書くのにあたって検証したときに気づいたことでした。
もしよければ、この資料も見てもらえると参考になるかもしれません。

Java Advent Calendar 2023、明日は @earu さんです。お楽しみに。

  1. あまりに長いと、Eclipse が重くなりますが。

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1