拡張関数 use とは?
use 関数の概要
Loan パターンを実現するための拡張関数で、kotlin.io パッケージに含まれています。
inline fun <T : Closeable, R> T.use(block: (T) -> R): R (source)
※ use - stdlib - Kotlin Programming Language
// ブロックを抜けた時点で close メソッドが呼び出される(例外が発生したとしても)
FileInputStream("sample.txt").use {
while (true) {
val content = it.read()
if (content == -1) {
break;
}
print(content as? Char)
}
}
Loan パターンとは?
リソース(ファイルやコネクション)を表現するオブジェクトで、リソースの使用後に close メソッドを半自動的に呼び出すことで、リソースの解放もれを防ぐコーディングパターンを指します。
Java では、バージョン 1.7 より言語仕様の一部(try-with-resources 文)として組み込まれおり、Closeable もしくは AutoCloseable インタフェースを実装したクラスのオブジェクトに対して、これを適用することが出来ます。
// fstream は try 節を抜けた時点で close メソッドが呼び出される(例外が発生したとしても)
try (FileInputStream fstream = new FileInputStream("sample.txt")) {
while (true) {
int content = fstream.read();
if (content == -1) {
break;
}
System.out.print((char) content);
}
}
※ The try-with-resources Statement (The Java™ Tutorials > Essential Classes > Exceptions)
※ Closeable (Java Platform SE 8 )
※ AutoCloseable (Java Platform SE 8 )
拡張関数とは?
拡張関数とは、Kotlin において定められている関数仕様の一つです。
Kotlin では特定のクラス(レシーバと呼ばれる)に対して外側からメソッドを定義するようなことが出来ます。
※ Extensions - Kotlin Programming Language
use 関数では、Closeable インタフェースを実装したクラスに対して、パラメータとして渡されたブロックの実行と、ブロック内の処理完了に付随して close 関数を呼び出すという処理を実装しています。
※ kotlin/ReadWrite.kt at v1.0.5 · JetBrains/kotlin
どこにハマったか?
事の経緯
あるコネクションクラス(java.sql.Connection)のオブジェクトに対して、use 関数を用いて Loan パターンを適用する実装を行いましたが、コンパイルエラーになりました。
※ Connection (Java Platform SE 8 )
何が問題かというと、use 関数は Closeable インタフェースの実装をレシーバに要求しますが、java.sql.Connection は AutoCloseable インタフェースのみを実装したクラスで、Closeable は実装していません。
よって、クラスは use 関数の拡張が及ぶところではなく、java.sql.Connection オブジェクトに対して use 関数を適用することは出来ないというわけです。
※ ただし一番の問題は、そもそも Closeable と AutoCloseable の区別がついていなかった私にあるということは言うまでもありません。
どうして AutoCloseable に対応していないのか?
Java の try-with-resources 文は、Closeable インタフェースと AutoCloseable インタフェースの両方に対応している点を考えると、これは不便に感じてしまいますね。
これの理由についでですが、Kotlin は 1.0.5 の時点で JDK の最小対象バージョンを 1.6 と定めていますが、AutoCloseable インタフェースは(try-with-resources 文とともに)JDK 1.7 より追加された関数であるため、このギャップにより Kotlin は実装を見送っているようです。
※ JDK7/8 features in Kotlin 1.0 - Libraries - Kotlin Discussions
対処法は?
将来的に use 関数を Closable だけでなく、AutoClosable にも対応することが決まっております。
※ Kotlin 1.1 にて実装予定。
と言っても、おうおうにして待てないという話になるわけですが、
先行対応として JetBrains の開発チームが拡張ライブラリをリリースしています。
kotlinx.support
Provides extension and top-level functions to use JDK7/JDK8 features in Kotlin 1.0
※ Github: Kotlin/kotlinx.support
なんと言っても、kotlinx.support というライブラリ名がしびれますね!
このライブラリは、JDK 1.7, 1.8 の追加仕様に対応するという名目のライブラリで、JDK 1.7 への追従として、AutoClosable をレシーバとする use 関数が提供されています。
import kotlinx.support.jdk7.use
import kotlinx.support.jdk7.suppressed
import java.sql.DriverManager
...
try {
DriverManager.getConnection("localhost:3306").use {
...
}
} catch {
...
//「抑制された例外」のチェックもお忘れなく
assert(e.suppressed.isEmpty());
}
まとめ
- kotlinx.support を入れることで、AutoClosable でも Loan パターンを適用可能。
- JetBrains の開発チーム グッジョブ!
- ググってみると意外と解決策が見つかるもん。