Scalaアプリケーションで、Java niotry-with-resource構文を使用してファイルから行を読み取ろうとしています。
Scalaバージョン2.11.8
Javaバージョン1.8
try(Stream<String> stream = Files.lines(Paths.get("somefile.txt"))){
stream.forEach(System.out::println); // will do business process here
}catch (IOException e) {
e.printStackTrace(); // will handle failure case here
}
しかし、コンパイラは次のようなエラーをスローします
◾見つかりません:バリューストリーム
◾キャッチなしで試すか、最終的には体をブロックに入れるのと同じです。例外は処理されません。
何が問題なのかわからない。 Java NIOを使用するのは初めてなので、どんな助けでも大歓迎です。
Scala 2.13を使用している場合は、 オブジェクトの使用 を使用する必要があります。
古いバージョンのscalaでは、javas try-with-resourcesコンストラクトは直接サポートされていませんが、ローンパターンを適用することで、独自のサポートを非常に簡単に構築できます。以下は単純ですが最適ではない例であり、理解しやすいものです。より正確な解決策は、この回答の後半に記載されています。
import Java.lang.AutoCloseable
def autoClose[A <: AutoCloseable,B](
closeable: A)(fun: (A) ⇒ B): B = {
try {
fun(closeable)
} finally {
closeable.close()
}
}
これは再利用可能なメソッドを定義し、Javaのtry-with-resource構造とほとんど同じように機能します。それは2つのパラメータを取ることによって機能します。 1つ目は、Autoclosableインスタンスのサブクラスを取得し、2つ目は、パラメーターと同じAutoclosable型を取得する関数を取得します。関数パラメーターの戻り値の型は、メソッドの戻り値の型として使用されます。次に、メソッドはtry内で関数を実行し、finallyブロックでautoclosebleを閉じます。
このように使用できます(ここでは、ストリームでfindAny()の結果を取得するために使用されます。
val result: Optional[String] = autoClose(Files.lines(Paths.get("somefile.txt"))) { stream ⇒
stream.findAny()
}
例外をキャッチしたい場合は、2つの選択肢があります。
Stream.findAny()呼び出しの周りにtry/catchブロックを追加します。
または、autoCloseメソッドのtryブロックにcatchブロックを追加します。これは、catchブロック内のロジックが、autoCloseが呼び出されるすべての場所から使用できる場合にのみ実行する必要があることに注意してください。
Vitalii Vitrenkoが指摘しているように、クライアントによって提供された関数とAutoCloseableのcloseメソッドの両方が例外をスローした場合、このメソッドはcloseメソッドからの例外を飲み込むことに注意してください。 Javaのtry-with-resourcesがこれを処理します。これをもう少し複雑にすることで、autoCloseに処理させることができます。
def autoClose[A <: AutoCloseable,B](
closeable: A)(fun: (A) ⇒ B): B = {
var t: Throwable = null
try {
fun(closeable)
} catch {
case funT: Throwable ⇒
t = funT
throw t
} finally {
if (t != null) {
try {
closeable.close()
} catch {
case closeT: Throwable ⇒
t.addSuppressed(closeT)
throw t
}
} else {
closeable.close()
}
}
}
これは、クライアント関数がスローする可能性のある例外を格納し、closeメソッドの潜在的な例外を抑制された例外として追加することで機能します。これは、try-with-resourceが実際に実行しているとOracleが説明している方法に非常に近いものです。 http://www.Oracle.com/technetwork/articles/Java/trywithresources-401775.html
ただし、これはScalaであり、多くの人がより機能的な方法でプログラミングすることを好みます。より機能的な方法では、メソッドは例外をスローするのではなく、Tryを返す必要があります。これにより、例外をスローすることによる副作用が回避され、応答が処理する必要のある障害である可能性があることがクライアントに明確になります(Stasの回答で指摘されています)。機能的な実装では、varを使用しないようにしたいので、単純な試みは次のようになります。
// Warning this implementation is not 100% safe, see below
def autoCloseTry[A <: AutoCloseable,B](
closeable: A)(fun: (A) ⇒ B): Try[B] = {
Try(fun(closeable)).transform(
result ⇒ {
closeable.close()
Success(result)
},
funT ⇒ {
Try(closeable.close()).transform(
_ ⇒ Failure(funT),
closeT ⇒ {
funT.addSuppressed(closeT)
Failure(funT)
}
)
}
)
}
これは、次のように呼び出すことができます。
val myTry = autoCloseTry(closeable) { resource ⇒
//doSomethingWithTheResource
33
}
myTry match {
case Success(result) ⇒ doSomethingWithTheResult(result)
case Failure(t) ⇒ handleMyExceptions(t)
}
または、myTryで.getを呼び出して結果を返すか、例外をスローすることもできます。
ただし、Kolmarがコメントで指摘しているように、returnステートメントがscalaでどのように機能するかにより、この実装には欠陥があります。次のことを考慮してください。
class MyClass extends AutoCloseable {
override def close(): Unit = println("Closing!")
}
def foo: Try[Int] = {
autoCloseTry(new MyClass) { _ => return Success(0) }
}
println(foo)
これでClosing!が出力されると予想されますが、そうではありません。ここでの問題は、関数本体内の明示的なreturnステートメントです。これにより、メソッドはautoCloseTryメソッドのロジックをスキップするため、リソースを閉じずにSuccess(0)を返すだけです。
この問題を修正するために、2つのソリューションを組み合わせて作成できます。1つはTryを返す機能APIを備えていますが、try/finallyブロックに基づく従来の実装を使用しています。
def autoCloseTry[A <: AutoCloseable,B](
closeable: A)(fun: (A) ⇒ B): Try[B] = {
var t: Throwable = null
try {
Success(fun(closeable))
} catch {
case funT: Throwable ⇒
t = funT
Failure(t)
} finally {
if (t != null) {
try {
closeable.close()
} catch {
case closeT: Throwable ⇒
t.addSuppressed(closeT)
Failure(t)
}
} else {
closeable.close()
}
}
}
これで問題が解決し、最初の試行と同じように使用できます。ただし、これは少しエラーが発生しやすいことを示しており、実装の誤りがこの回答に推奨バージョンとしてかなり前から含まれています。したがって、多くのライブラリが必要にならないようにする場合を除いて、ライブラリからこの機能を使用することを適切に検討する必要があります。もう1つ、1つを指し示す答えがあると思いますが、この問題をさまざまな方法で解決する乗算ライブラリがあると思います。
あるいは、ChoppyのTryCloseモナドを使用して、ScalaのTryと同様の構成可能な方法で理解のためにこれを行うことができます。
val ds = new JdbcDataSource()
val output = for {
conn <- TryClose(ds.getConnection())
ps <- TryClose(conn.prepareStatement("select * from MyTable"))
rs <- TryClose.wrap(ps.executeQuery())
} yield wrap(extractResult(rs))
ストリームでそれを行う方法は次のとおりです。
val output = for {
stream <- TryClose(Files.lines(Paths.get("somefile.txt")))
} yield wrap(stream.findAny())
あなたはすでに答えのアプローチの1つで言及されています:
def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): B = {
try
code(resource)
finally
resource.close()
}
しかし、私は以下がはるかにエレガントだと思います:
def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): Try[B] = {
val tryResult = Try {code(resource)}
resource.close()
tryResult
}
最後の1つは、制御フローの処理が簡単なIMHOです。