ふと、この記事にあるようなこと、Scalaだったらどう書くかな?と思ってまとめてみました。途中で力尽きたので、まずは前半戦まで!
あの処理、Scalaでどう書く?
基本的にScalaの標準ライブラリでやる方法を紹介し、Javaの標準ライブラリを使う必要があるところはその旨書いてあります。
また、調べていくとScalaとJavaの標準ライブラリではできないことも有ったので、そこには「標準ライブラリではできません」と書いておきます。標準ライブラリ以外でやれる方法が有ったら、ぜひ教えてください。
なお、標準ライブラリはScala 2.12、Java 11の機能に基づいています。
標準出力・標準エラー出力
どちらもScalaのscala.Console
オブジェクトのprintln
メソッドを使います(パッケージ名のscalaは省略可なので、以降は省略します)。
println("HELLO") // 標準出力にメッセージ Console.err.println("ERROR!") // 標準エラー出力にメッセージ
単にprintln
と書いた場合は、Console.out.println
が呼び出されます。
なお、標準ライブラリにはログ出力ライブラリは用意されていません。
ファイル関係
基本的にScalaの標準ライブラリでは、ファイル関係のサポートがほぼ無いので、基本的にJavaの標準ライブラリを使います。
パスの操作
Java 7から追加されたjava.nio.file.Paths
クラスを使います。
import java.nio.file.Paths val p = Paths.get("/foo/bar/baz.txt") // OSごとの実装を持つPathインタフェースが返される p.getFileName // ファイル名「baz.txt」を保持するPathが返る p.getParent // 親ディレクトリ「/foo/bar/」を保持するPathが返る val dirpath = Paths.get("/foo/bar") dirpath.resolve("baz.txt") // パスの連結 「/foo/bar/baz.txt」を保持するPath…引数は文字列でもPathでもOK
Path
は、toString
で文字列に戻せます。また、Java6以前に作られたライブラリはjava.io.File
クラスを要求するものがありますが、toFile
でjava.io.File
に変換できます。
import java.nio.file.Paths val fullpath = Paths.get("/foo/bar/baz.txt") fullpath.toString // 文字列として「/foo/bar/baz.txt」が返る fullpath.toFile // java.io.Fileでラップされた「/foo/bar/baz.txt」が返る
チルダや環境変数が含まれるパスを扱う
ScalaやJavaでチルダや環境変数をパスに展開してくれる標準ライブラリは無いようです。
実現しているライブラリがあればぜひ教えてください。
ファイルの読み書き
テキストデータの読み込みにはScalaのscala.io.Source
オブジェクトのfromFile
メソッドが便利です。
val source = io.Source.fromFile("foo.txt", "utf-8") val lines = source.lines lines.foreach(println) // 行単位で標準出力へ source.close
fromFile
メソッドは文字列でのパス名以外にもjava.io.File
クラスのオブジェクトも指定可能です。また、scala.io.Source
はデフォルトで文字コードとして"UTF-8"が使われるようになっていますが、明示的に指定した方が分かりやすいでしょう。
反対に書き込みについては、専用のライブラリは(なぜか)用意されていないので、Java 7で導入されたFiles.newBufferedReader
メソッドを使います。ただし、Scalaにはtry-with-resource
構文が無いので自前でクローズします。
import java.nio.file.Paths import java.nio.file.Files import java.nio.charset.StandardCharsets val path = Paths.get("foo.txt") val writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8) try { writer.append("内容") writer.newLine } finally { writer.close }
構造が簡単なうちは自前でclose
メソッドを呼び出すようなコードを書いていても大丈夫だと思いますが、コードの構造が大規模になってくるとclose
の呼び出しも複雑化してきます。このような場合、Javaであればtry-with-resource
構文がありますし、C#にはusing
構文があります。Scalaでも構文は用意されていませんが、コーディングテクニックとしての「ローンパターン(loan pattern)」と呼ばれるものがあります。ここでは説明は割愛しますが、調べてみると色々な書き方が有るようなので、試してみましょう。
行数を数える(wc -l)
特に専用の構文やライブラリが用意されているわけではないですが、先ほどのio.Source
を使うとシンプルに書けます。
val source = io.Source.fromFile("foo.txt") source.getLines.length
ファイルの列挙
Java7で導入されたFiles.newDirectoryStream
を使います。
import java.nio.file.Paths import java.nio.file.Files val files = Files.newDirectoryStream(Paths.get("."), "*.txt") try { files.forEach(println) } finally { files.close }
ファイルの情報(存在確認・作成日時)
java.nio.file.Files
のメソッド群を使います。
import java.nio.file.Paths import java.nio.file.Files val f = Paths.get("foo.txt") Files.exists(f) // 存在確認 Files.isRegularFile(f) // ファイル? Files.isDirectory(f) // ディレクトリ? Files.getLastModifiedTime(f) // 更新日時…戻り値の型がjava.nio.file.attribute.FileTimeであることに注意!
その他、ファイルシステム固有の属性(作成日時、アクセス日時)は、Files.getAttribute
メソッドを使います。戻り値がobject
型になってしまうので、キャストが必要な点が要注意です。
val createTime = Files.getAttribute(Paths.get("foo.txt"), "unix:creationTime").asInstanceOf[java.nio.file.attribute.FileTime] val accessTime = Files.getAttribute(Paths.get("foo.txt"), "unix:lastAccessTime").asInstanceOf[java.nio.file.attribute.FileTime]
コピー・移動・削除
こちらもjava.nio.file.Files
のメソッド群を使います。
import java.nio.file.Paths import java.nio.file.Files Files.copy(Paths.get("foo.txt"), Paths.get("bar.txt")) // コピー Files.delete(Paths.get("bar.txt")) // 削除 Files.move(Paths.get("foo.txt"), Paths.get("bar.txt")) // 移動
コピーや、移動はオプションとしてStandardCopyOption
が用意する定数を指定することができます。例えば、StandardCopyOption.REPLACE_EXISTING
を指定すると、すでにファイルが有っても上書きします。
import java.nio.file.Paths import java.nio.file.Files import java.nio.file.StandardCopyOption Files.copy(Paths.get("foo.txt"), Paths.get("bar.txt")) // コピー // foo.txtを書き換える処理 Files.copy(Paths.get("foo.txt"), Paths.get("bar.txt"), StandardCopyOption.REPLACE_EXISTING) // 2回目のコピーで上書き
と、ここまでで力尽きたので、続きは明日以降!
外部コマンド
単純に実行する
外部のコマンドを実行し、標準出力を受け取る:
環境変数やカレントディレクトリを変更する
リダイレクトを使う
パイプを使う
spawn → wait (外部コマンドを起動し、終了を待つ)
シェルを実行する【危険!!】
時刻関係
文字列関係
文字列への式埋め込み
ヒアドキュメント
コマンドライン引数
終了時の処理&シグナルをtrapする
HTTPリクエスト(curl や wget の代替)
吉祥寺.pmへの参加者を募集しています
このブログは企業テックブログという訳でもないので、特にエンジニア募集とかないですけど、定期的に吉祥寺.pmというイベントをやっているので、良かったら参加してみてください。pmとはついていますが、Scalaのトークも歓迎です!
あと、良かったら、Twitterのアカウントもフォローしてください。設計のこととかツイートしています。