#Scalaのリソース解放処理
##~ 2.12
2.12までのScalaには、Javaのtry-with-resourcesやC#のusingのような、外部リソースを自動的に解放するための関数・構文がありませんでした。
そのため、都度finalyを書きたくなければ、以下のように自動解放処理を自前で実装する必要がありました(ローンパターン)。
Scala using(ローンパターン)
Scalaで一番よく使うローンパターン
Loanパターンをモナドfor式で使えるようにしてみたよ
##2.13 ~
Scala 2.13.0からは標準ライブラリにscala.util.Using
オブジェクトが追加されたことで、上記のような実装を追加せずともローンパターンが使えるようになりました。
scala.util.Using
#scala.util.Usingを使ってみる
##a. 単一リソースの場合
Using(【リソース】)(【処理内容】)
の形で記述します。
hello
world
import scala.io.Source
import scala.util.Using
object Main extends App {
Using(Source.fromFile("sample.txt")) { source => // ここのsourceはSource.fromFile("sample.txt")の戻り値
for (line <- source.getLines) println(line)
}
}
実行結果
hello
world
上記ではscala.io.Source
を使用しましたが、リソースはjava.lang.AutoCloseable
の任意のサブクラスを指定可能です。
import java.io.{BufferedReader, FileReader}
import scala.util.Using
object Main extends App {
// AutoCloseableのサブクラスなら何でも可
Using(new BufferedReader(new FileReader("sample.txt"))) { br =>
var line = ""
while ({ line = br.readLine(); line ne null }) {
println(line)
}
}
}
##b. 複数リソースの場合
Using.Manager
オブジェクトを使い、下記のように記述します。
hello
world
using
sample
import java.io.{BufferedReader, FileReader}
import scala.util.Using
object Main extends App {
Using.Manager { use =>
// 引数use(実体はUsing.Managerクラスのインスタンス)を介すことでリソースを複数指定可能
val br1 = use(new BufferedReader(new FileReader("sample.txt")))
val br2 = use(new BufferedReader(new FileReader("sample2.txt")))
var line1 = ""
var line2 = ""
while ({
line1 = br1.readLine()
line2 = br2.readLine()
(line1 ne null) && (line2 ne null)}) {
println(line1)
println(line2)
}
}
}
実行結果
hello
using
world
sample
###※複数リソース使用時の注意点
Using.Manager
で複数リソースを管理する場合、必ず対象のリソースにuse
関数を適用させるか、今までどおり自前で解放処理を記述してください。
以下のようにuse
なしでもコンパイルは通りますが、リソースが自動で解放されないためリソースリークの原因になりえます。
Using.Manager { use =>
val br1 = use(new BufferedReader(new FileReader("sample.txt")))
// 自動リソース解放対象にならない(コンパイルは通る)
val br2 = new BufferedReader(new FileReader("sample2.txt"))
...
##戻り値(Try[A])の使用
Using
およびUsing.Manager
は戻り値としてTry[A]
を返すため、下記のように変数に代入することも可能です。1
import scala.io.Source
import scala.util.{Failure, Success, Try, Using}
object Main extends App {
val result: Try[List[String]] = Using(Source.fromFile("sample.txt"))(_.getLines.toList)
result match {
case Success(xs) => xs.foreach(println)
case Failure(_) => println("failure")
}
}
実行結果
hello
world
失敗した場合
import scala.io.Source
import scala.util.{Failure, Success, Try, Using}
object Main extends App {
val result: Try[List[String]] = Using(Source.fromFile("sample.txt")) { source =>
throw new Exception // 強制的に失敗させる
source.getLines.toList
}
result match {
case Success(xs) => xs.foreach(println)
case Failure(_) => println("failure")
}
}
実行結果
failure
#まとめ
Scalaにusing相当の機能がないことは前から言われていたので、標準で追加されて安心しました。
今後は特に理由がなければ、自前で実装せずにこちらを使っていけばいいのではないかと思います。
最後までお読み頂きありがとうございました。
質問や不備についてはコメント欄かTwitterまでお願いします。
-
ここでは
getLines.toList
と記述していますが、このtoList
を入れなかった場合、戻り値はTry[Iterator[String]]
となります。これでもコンパイルは通りますが、実行すると要素の読み取りが既にリソースが閉じられた後になってしまい、例外が発生してしまいます。 ↩