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

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

```
String str = "AAAAAAAA…（略）…AAAA";
```

:::note info
今回は「**文字列リテラル**（ソースコード上の " " で囲われた文字列）」の話です。
:::

# Java 言語仕様

まず、Java 言語仕様を確認しました。しかし、文字列リテラルの最大長については触れられていませんでした。
[Java Language Specification - Chapter 3. Lexical Structure - 3.10.5. String Literals](https://docs.oracle.com/javase/specs/jls/se21/html/jls-3.html#jls-3.10.5)

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

# Java 仮想マシン仕様

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

文字列リテラルは、クラスファイルの定数プールに `CONSTANT_Utf8_info` 構造体として格納されるのですが、この構造体には**UTF-8のバイト配列とその長さ**が含まれています。
```c
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](https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.4.7)

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

# OpenJDK コンパイラの場合

では、実際に OpenJDK コンパイラは、文字列リテラルの長さをどこまで許容しているのでしょうか。
検証してもよくわからなかったので、 [StackOverflow で聞きました。](https://stackoverflow.com/a/77440237/1932017)

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

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

---
例えば、下記の `A`（UTF-8で1バイトの文字）が **65,534 個の場合はコンパイルができます。**
一方、65,535 個だと1の条件に引っかかって、**コンパイルエラー「定数文字列が長すぎます」** になります。
```java
String ok = "AAAAAAAAAA…（略）…AAAA";   // 65534個なら OK
String ng = "AAAAAAAAAA…（略）…AAAAA";  // 65535個なら NG
```

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

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

まとめると、このようになります。
（[今回のコードは Gist に置いておきました](https://gist.github.com/YujiSoftware/0bf430e50688b4b32f95f59539fafb25)）

|文字列リテラル|文字数|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|

:::note warn
前者のエラー「定数文字列が長すぎます」になるのが、コンパイラのこの部分です。
[jdk/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java at 9320ef9b29927b8fff52055d7a7a89db9b15b95b · openjdk/jdk](https://github.com/openjdk/jdk/blob/9320ef9b29927b8fff52055d7a7a89db9b15b95b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java#L534-L542)
```java
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です。

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

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


# Eclipse JDT コンパイラの場合

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

[^eclipse]: あまりに長いと、Eclipse が重くなりますが。

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

# まとめ

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


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