【Jython】AutoCloseable / Closeable 実装オブジェクトに with 文を使うには

# `try-with-resources`文と`with`文
Java には[`try-with-resources`文](https://docs.oracle.com/javase/jp/7/technotes/guides/language/try-with-resources.html)があり、これを、[インターフェース`AutoCloseable`](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/AutoCloseable.html)又は[インタフェース`Closeable`](https://docs.oracle.com/javase/jp/10/docs/api/java/io/Closeable.html)を実装したクラスをインスタンス化したオブジェクト（以下、`AutoCloseable`/` Closeable`実装オブジェクト）が使用すると、ブロック終了時又は例外発生時に於いて、自動的にそのオブジェクトの`close()`メソッドが呼び出され、クローズ処理が為される。

次に示すJavaソースコードファイル`Main.java`には、2 つの`try-with-resources`文があり、変数`bw`及び変数`br`に、`AutoCloseable`/` Closeable`実装オブジェクトの参照が格納されている。

```java:Main.java
// -*- coding: utf-8; -*-

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

class Main {
    public static void main(String[] args) {
        final File file = new File("iroha.txt");
        // ファイル書き込み
        try (final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
            bw.write("いろはにほへと　ちりぬるを");
            bw.newLine();
            bw.write("わかよたれそ　つねならむ");
            bw.newLine();
            bw.write("うゐのおくやま　けふこえて");
            bw.newLine();
            bw.write("あさきゆめみし　ゑひもせす");
            bw.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // ファイル読み込み
        try (final BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

```

次に、Java コンパイラのバージョン確認、ソースコードのコンパイル、及び実行例を示す。

```shell-session
$ javac -version
javac 17.0.1

$ javac -encoding UTF-8 Main.java

$ java Main
いろはにほへと　ちりぬるを
わかよたれそ　つねならむ
うゐのおくやま　けふこえて
あさきゆめみし　ゑひもせす

```

多くのJVM言語は、Java の`try-with-resources`文に相当する機能を備えている。例えば、Kotlin の`use`メソッド[^1]、[Jython](https://www.jython.org/) (Javaで記述されたPython処理系) の[`with`文](https://docs.python.org/ja/3/reference/compound_stmts.html#with)が挙げられる。

もし、`Main.java`を Jython スクリプトに移植しようとするなら、だいたい次のようなものを想像するだろう。

```python
# -*- coding: utf-8; -*-

from java.io import BufferedReader, BufferedWriter, File, FileInputStream, FileOutputStream, InputStreamReader, OutputStreamWriter
from java.nio.charset import StandardCharsets

file = File(u"iroha.txt")
# ファイル書き込み
with BufferedWriter(OutputStreamWriter(FileOutputStream(file), StandardCharsets.UTF_8)) as bw:
    bw.write(u"いろはにほへと　ちりぬるを")
    bw.newLine()
    bw.write(u"わかよたれそ　つねならむ")
    bw.newLine()
    bw.write(u"うゐのおくやま　けふこえて")
    bw.newLine()
    bw.write(u"あさきゆめみし　ゑひもせす")
    bw.newLine()
# ファイル読み込み
with BufferedReader(InputStreamReader(FileInputStream(file), StandardCharsets.UTF_8)) as br:
    line = br.readLine()
    while line:
        print(line)
        line = br.readLine()

```

しかし、上記スクリプトは動作せず、次のエラー文を印字する。

```
AttributeError: 'java.io.BufferedWriter' object has no attribute '__exit__'
```

Jython は、他のJVM言語とは異なり、`AutoCloseable`/` Closeable`実装オブジェクトであろうと関係なく、上記エラー文の指摘にもある実装要件を満たしていなければ、`with`文を使うことはできない。

しかしながら、`AutoCloseable`/`Closeable`実装オブジェクトに`with`文を使う方法はある。

# `AutoCloseable`/`Closeable`実装オブジェクトに`with`文を使うには

とっても簡単である。標準ライブラリの[`contextlib.closing`関数](https://docs.python.org/ja/3/library/contextlib.html#contextlib.closing)を作用させれば良い。具体的には、上記スクリプトを次のように修正する。

```diff_python
# -*- coding: utf-8; -*-

+ from contextlib import closing
from java.io import BufferedReader, BufferedWriter, File, FileInputStream, FileOutputStream, InputStreamReader, OutputStreamWriter
from java.nio.charset import StandardCharsets

file = File(u"iroha.txt")
# ファイル書き込み
- with BufferedWriter(OutputStreamWriter(FileOutputStream(file), StandardCharsets.UTF_8)) as bw:
+ with closing(BufferedWriter(OutputStreamWriter(FileOutputStream(file), StandardCharsets.UTF_8))) as bw:
    bw.write(u"いろはにほへと　ちりぬるを")
    bw.newLine()
    bw.write(u"わかよたれそ　つねならむ")
    bw.newLine()
    bw.write(u"うゐのおくやま　けふこえて")
    bw.newLine()
    bw.write(u"あさきゆめみし　ゑひもせす")
    bw.newLine()
# ファイル読み込み
- with BufferedReader(InputStreamReader(FileInputStream(file), StandardCharsets.UTF_8)) as br:
+ with closing(BufferedReader(InputStreamReader(FileInputStream(file), StandardCharsets.UTF_8))) as br:
    line = br.readLine()
    while line:
        print(line)
        line = br.readLine()

```

修正済み Jython スクリプトファイル`Main.py`を、次に示す。

```python:Main.py
# -*- coding: utf-8; -*-

from contextlib import closing
from java.io import BufferedReader, BufferedWriter, File, FileInputStream, FileOutputStream, InputStreamReader, OutputStreamWriter
from java.nio.charset import StandardCharsets

file = File(u"iroha.txt")
# ファイル書き込み
with closing(BufferedWriter(OutputStreamWriter(FileOutputStream(file), StandardCharsets.UTF_8))) as bw:
    bw.write(u"いろはにほへと　ちりぬるを")
    bw.newLine()
    bw.write(u"わかよたれそ　つねならむ")
    bw.newLine()
    bw.write(u"うゐのおくやま　けふこえて")
    bw.newLine()
    bw.write(u"あさきゆめみし　ゑひもせす")
    bw.newLine()
# ファイル読み込み
with closing(BufferedReader(InputStreamReader(FileInputStream(file), StandardCharsets.UTF_8))) as br:
    line = br.readLine()
    while line:
        print(line)
        line = br.readLine()

```

次に、Jython のバージョン確認、及び実行例を示す。

```shell-session
$ jython --version
Jython 2.7.2

$ jython Main.py
いろはにほへと　ちりぬるを
わかよたれそ　つねならむ
うゐのおくやま　けふこえて
あさきゆめみし　ゑひもせす
```

# 参考
- [`try-with-resources`文](https://docs.oracle.com/javase/jp/7/technotes/guides/language/try-with-resources.html)
- [インタフェース`AutoCloseable`](https://docs.oracle.com/javase/jp/10/docs/api/java/lang/AutoCloseable.html)
- [インタフェース`Closeable`](https://docs.oracle.com/javase/jp/10/docs/api/java/io/Closeable.html)
- [Jython](https://www.jython.org/)
- [`with`文](https://docs.python.org/ja/3/reference/compound_stmts.html#with)
- [`contextlib.closing`関数](https://docs.python.org/ja/3/library/contextlib.html#contextlib.closing)

[^1]: 但し、**`Closeable`未実装の**`AutoCloseable`実装オブジェクトを`use`メソッドに対応させるには、`kotlin-stdlib-jdk7.jar`をクラスパスに追加しなければならない。