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)
}
この長さのフィールドは u2
(2バイト)なので、最大値は 65,535 です。
文字列リテラルは修正UTF-8で格納されているので、'A'
(= UTF-8で1バイト)であれば 65,535 文字まではOKのように見えます。
OpenJDK コンパイラの場合
では、実際に OpenJDK コンパイラは、文字列リテラルの長さをどこまで許容しているのでしょうか。
検証してもよくわからなかったので、 StackOverflow で聞きました。
その結果、以下の両方の条件を満たす場合 はコンパイルが通ることがわかりました。
- 文字数は、65,534文字まで
- 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 さんです。お楽しみに。
-
あまりに長いと、Eclipse が重くなりますが。 ↩