オーバーライドできるtry-catch-finally

注意

タイトルは嘘です。
正確にはtry-catch-finallyらしきもの。

概要

try-catch-finallyをメソッドでラップし、拡張可能にする。
メソッドチェインで本物に似せる。

動機

printStackTraceなど、お決まりのtry-catch-finallyを書くのが面倒。

基礎知識

必要な知識はこのへん。

  • メソッド呼び出しの引数には遅延評価できるブロックが渡せる
  • 関数の中身は呼び出されるまで評価されない
  • メソッドチェインがドットなしで書ける

方針

Try.apply, Catch, Finally呼び出しで渡されるブロックを関数にしてTryインスタンスに貯めておく。
Finallyの最後にexecuteでまとめて実行する。
拡張できるように、各関数呼び出しの部分はexecTry, execCatch, execFinallyとメソッドにしておく。
Scalaのtryは式で値を返すが、面倒なのでunitに限定する。

package mytry;

object TryMain {
  def Try = new Try()
  
  def main(args : Array[String]) : Unit = {
    Try {
      println("try")
      (null : String).toInt
    } Catch {
      case e => println("catch: " + e)
    } Finally {
      println("finally")
    }

    println("normally finished!")
  }
}

class Try {
  //variables to hold blocks
  var tryPart : Unit => Unit = { _ => () }
  var catchPart : PartialFunction[Throwable, Unit] = { case e => throw e }
  var finallyPart : Unit => Unit = { _ => () }
  
  //set tryPart
  def apply(tryPart : => Unit) : Try = {
    this.tryPart = _ => tryPart
    this
  }
  
  //set catchPart
  def Catch(catchPart : PartialFunction[Throwable, Unit]) : Try = {
    this.catchPart = catchPart
    this
  }
  
  //set finallyPart and execute
  def Finally(finallyPart : => Unit) : Unit = {
    this.finallyPart = _ => finallyPart
    execute()
  }
  
  //execute real try-catch-finally
  def execute() =
    try {
      execTry()
    } catch {
      case e : Throwable => execCatch(e)
    } finally {
      execFinally()
    }
  
  def execTry() = tryPart.apply()
  
  //execute catchPart if the exception can be caught
  def execCatch(e : Throwable) = 
    if(catchPart.isDefinedAt(e)){
      catchPart.apply(e)
    } else {
      throw e
    }
  
  def execFinally() = finallyPart.apply()
}

実行結果

try
catch: java.lang.NumberFormatException: null
finally
normally finished!

try -> catch -> finallyと、期待通りの挙動をして正常終了していることがわかる。
ちなみに、(null : String).toIntはNullPointerExceptionではなくNumberFormatExceptionが投げられる。

拡張

execTry, execCatch, execFinallyという拡張できるメソッドを用意したので
オーバーライドしてみる。ブロック呼び出しの前後にprintを挟む。

package mytry;

object TraceTryMain {
  def TraceTry = new TraceTry()
  
  def main(args : Array[String]) : Unit = {
    TraceTry {
      println("try")
      (null : String).toInt
    } Catch {
      case e => println("catch: " + e)
    } Finally {
      println("finally")
    }

    println("normally finished!")
  }
}

class TraceTry extends Try {
  //override execs
  override def execTry() {
    println("tryPart start")
    super.execTry()
    println("tryPart end")
  }
  
  override def execCatch(e : Throwable) {
    println("catchPart start")
    super.execCatch(e)
    println("catchPart end")
  }
  
  override def execFinally() {
    println("finallyPart start")
    super.execFinally()
    println("finallyPart end")
  }
}

実行結果

tryPart start
try
catchPart start
catch: java.lang.NumberFormatException: null
catchPart end
finallyPart start
finally
finallyPart end
normally finished!

"tryPart end"のみ、例外発生により実行されていないことがわかる。