9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Scala2.13のUsingを試してみた(ローンパターン)

Last updated at Posted at 2019-09-23

#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(【リソース】)(【処理内容】)の形で記述します。

sample.txt
hello
world
Main.scala
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の任意のサブクラスを指定可能です。

Main.scala
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オブジェクトを使い、下記のように記述します。

sample.txt
hello
world
sample2.txt
using
sample
Main.scala
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

Main.scala
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

失敗した場合

Main.scala
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までお願いします。

  1. ここではgetLines.toListと記述していますが、このtoListを入れなかった場合、戻り値はTry[Iterator[String]]となります。これでもコンパイルは通りますが、実行すると要素の読み取りが既にリソースが閉じられた後になってしまい、例外が発生してしまいます。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?