Files でファイルに書き込む
このレッスンで分かること
Files.writeString(path, text)で文字列をファイルに一発書き込み (Java 11+)Files.write(path, lines)はList<String>を行単位で書き込む- デフォルトは UTF-8、上書きモード。追記したいときは
StandardOpenOption.APPEND- 書き込み中の例外は
IOException(検査例外)。try-catchかthrowsで扱う
Files でファイルに書き込む とは
Java の標準ライブラリ
java.nio.file.Filesを使って、ファイルへの書き込みと読み込みをわずか数行で実現する方法を学ぼう。
ファイル I/O を最短コードで書ける java.nio.file.Files
アプリを作っていると、設定値を保存したい、ログを残したい、ユーザーの作成したテキストを保存したい、といった「ファイルにデータを書く」場面が必ず出てきます。Java でファイル操作を扱うクラスは歴史的にいくつもありますが、現代の Java で まず最初に覚えるべきは java.nio.file.Files です。Java 7 で導入され、Java 11 で writeString / readString が追加されたことで、ファイルへの読み書きが文字どおり 1 行で書けるようになりました。
古い書籍やネット記事には
FileWriterやBufferedReaderをネストして使う例がよく載っていますが、2020 年代の Java ではFiles.writeString/Files.readStringを第一候補に据えてください。try-with-resourcesやclose()を自分で書かなくて済むぶん、バグの入る余地が劇的に減ります。
本章では、一時ファイルにテキストを書き、それを読み返して返す、というシンプルな課題を通して、Files クラスと Path の使い方、そして古い API との違いを身につけていきましょう。Android アプリでも、サーバーサイドのバッチでも、ファイル I/O は避けて通れない基礎なので、ここでしっかり押さえます。
Path でファイルの場所を表す
Files クラスを使う前に、まず「どのファイルを操作するか」を表す型 java.nio.file.Path を知る必要があります。Path は Windows の C:\Users\foo.txt や macOS Linux の /tmp/foo.txt のような ファイルパスの抽象表現 で、OS の違いを吸収してくれます。
Java
import java.nio.file.Path;
import java.nio.file.Paths;
Path p1 = Path.of("/tmp/hello.txt"); // Java 11+
Path p2 = Paths.get("/tmp/hello.txt"); // Java 7+ の書き方
Path p3 = Path.of("data", "users", "a.json"); // OS 依存の区切り文字で結合Path.of(...) は Java 11 以降で使える比較的新しい書き方で、内部的には Paths.get(...) と同じです。複数の文字列を渡すと、Windows では \ で、Linux では / で勝手に結合してくれるので、パス区切り文字をハードコードする必要がありません。
ファイルパスを単なる
Stringで持ち回ると、区切り文字やエンコーディングの違いでバグが起きがちです。Pathを使えば、API 側がそういった OS 差を吸収してくれるので、移植性の高いコードになります。
Files.writeString で 1 行書き込み
本題の書き込み API は Files.writeString(Path, CharSequence) です。これ 1 行で、内部で自動的にファイルを開き、書き込み、閉じるまでをやってくれます。try-with-resources ですら書かなくて済みます。
Java
import java.nio.file.Files;
import java.nio.file.Path;
public class Demo {
public static void main(String[] args) throws Exception {
Path p = Path.of("hello.txt");
Files.writeString(p, "Hello, Java!");
String text = Files.readString(p);
System.out.println(text); // Hello, Java!
}
}ここでのポイントは次のとおりです。
Files.writeStringは既存ファイルがあれば 中身を上書き する (追記モードではない)- 文字コードは Java 11 での追加当初から常に
UTF-8固定 (JEP 400 以前も同様) - メソッドは
IOExceptionを投げるので、throws Exceptionかtry-catchが必須 - 戻り値は書き込んだ
Pathで、メソッドチェーンに使うこともできる
書き込みと対になる読み込み API が Files.readString(Path) です。これも 1 行で「ファイルの中身全部を String で返す」というシンプルな仕様で、BufferedReader で 1 行ずつループする必要はありません。
一時ファイルを安全に作る Files.createTempFile
本番のコードで「特定のパスを決め打ち」すると、テストや並列実行で衝突を起こします。そこで便利なのが Files.createTempFile(prefix, suffix) です。OS が管理する一時ディレクトリ (Windows なら %TEMP%、Linux macOS なら /tmp など) に、一意なファイル名でファイルを作ってくれます。
Java
import java.nio.file.Files;
import java.nio.file.Path;
Path tmp = Files.createTempFile("demo", ".txt");
System.out.println(tmp); // 例: /tmp/demo12345.txt
Files.writeString(tmp, "一時データ");
String read = Files.readString(tmp);
Files.delete(tmp); // 後片付け第一引数の "demo" がファイル名の 接頭辞、第二引数の ".txt" が 拡張子 で、間に乱数文字列が入ります。後片付けに Files.delete(path) を呼ぶか、JVM 終了時に消したい場合は tmp.toFile().deleteOnExit() を使うのが定石です。
chotdekiru の Java executor は一時ファイルの作成を許可しています。本課題でも
Files.createTempFileを遠慮なく使ってください。逆に、決め打ちのパス (/etc/passwdやC:\\Windows\\System32など) を触ろうとするとセキュリティ的にブロックされる可能性があります。
古い API (FileWriter / BufferedReader) との比較
なぜ昔のサンプルで FileWriter や BufferedReader が登場していたかというと、Java 6 以前にはまだ Files クラスがなく、これしか手段がなかったからです。試しに同じ処理を 2 つの API で書き比べてみます。
Java
// 古い書き方 (Java 6 以前のスタイル)
import java.io.*;
FileWriter fw = null;
try {
fw = new FileWriter("old.txt");
fw.write("hello");
} finally {
if (fw != null) fw.close();
}Java
// 新しい書き方 (Java 11+)
import java.nio.file.Files;
import java.nio.file.Path;
Files.writeString(Path.of("new.txt"), "hello");古い書き方では close() を finally で呼ばないとファイルハンドルが残ってしまう、文字コードを指定し忘れるとプラットフォーム依存になる、改行コードのことを意識する必要がある、と地雷が多めです。新しい書き方ではこれらがほぼ全部吸収されているので、特別な理由がなければ Files 一択です。
よくある間違い
ファイル I/O は地味にハマりやすい領域です。先回りして 3 つの落とし穴を押さえておきましょう。
close()を呼び忘れる / 古い書き方をする —new FileWriter(...)系の古い API を使うと、finallyでclose()を書かないとリソースが残ります。try-with-resources(try (var w = new FileWriter(...)) { ... }) を必ず使うか、いっそFiles.writeStringに置き換えるのが正解です- エンコーディングを指定し忘れる — 古い
new FileWriter("a.txt")は OS のデフォルトエンコーディングで書き込むので、Windows(Shift_JIS) とLinux(UTF-8) で結果が変わることがあります。Files.writeStringは常にUTF-8なので安全です IOExceptionを握りつぶす — ファイル操作は失敗する可能性が常にあります (権限・容量・パスの存在)。catch (IOException e) {}のように空ブロックで握りつぶすと、トラブルが起きても原因が分からなくなるので、必ずe.printStackTrace()かthrow new RuntimeException(e)で見える形にしましょう
もう一つよくあるのが、Files.write と Files.writeString を混同するパターンです。Files.write(Path, byte[]) はバイト列、Files.writeString(Path, String) は文字列、と引数が違うので、用途に応じて使い分けます。テキストデータなら writeString でほぼ事足ります。
Files.write(Path, Iterable<? extends CharSequence>)という、行のリストを渡してファイルに書き込むオーバーロードもあります。List<String>をそのまま改行区切りで書き出したいときに便利です。
実務で遭遇するパターン
中級レッスンで学んだことが、実際の業務コードでどう登場するかを整理します。
- ログ出力 ─ ライブラリではなく独自フォーマットでログを残したいとき、
Files.writeString+APPENDで素朴に書く - バッチの中間ファイル ─ 集計結果を CSV や TSV にダンプして次の工程へ渡すパターン
- 設定ファイルの書き出し ─ ユーザー設定を
~/.appname/config.jsonに保存する、CLI ツール定番 - サーバーレスのレポート生成 ─
Lambdaで/tmpに一時ファイルを書いて S3 にアップする流れ
コードレビューで指摘されがちなポイント
PR を出すとシニアから入りやすい指摘です。先回りで身につけておけばレビューが一発で通ります。
- 文字コード未指定 ─ Java 8 までの
Files.write(path, lines)はプラットフォーム依存。StandardCharsets.UTF_8を明示するよう指摘 - 大量データを
String連結で組み立てている ─+=のループは遅い。StringBuilderかBufferedWriterを使う - 追記したいのに上書きで開いている ─
StandardOpenOption.APPEND忘れで既存内容を消すバグの定番 try-with-resourcesを使っていない ─BufferedWriterを手動closeするコードは漏れの温床。try-with-resources必須
パフォーマンス考慮事項
Files.writeStringは内部で一度フルバッファ ─ 数 MB 以上はBufferedWriterでストリーミング書き込みのほうが省メモリ- fsync の有無 ─ デフォルトはクラッシュ時に直前の書き込みが失われる可能性あり。重要データは
FileChannel.force(true)を使う - OS のページキャッシュ ─ 連続書き込みはディスク IO ではなくキャッシュへのコピーが多い。
flush()だけでは永続化されない - 並列書き込みは禁物 ─ 同じファイルに複数スレッドで書くと内容が壊れる。
synchronizedかFileLockで排他制御
ここまでの要点
Files.writeString で 1 行書き込み、追記は APPEND、文字コードは UTF_8 を明示。大量書き込みは BufferedWriter + try-with-resources。
やってみよう
それでは課題に挑戦してみましょう。Solution.writeAndRead(String content) を実装し、与えられた content を一時ファイルに書き、それを読み返して返します。手順は次のとおりです。
- メソッドは
throws Exceptionか、内部でtry-catchする形でシグネチャを合わせる java.nio.file.Files.createTempFile("demo", ".txt")で一時ファイルを作るFiles.writeString(path, content)で書き込むFiles.readString(path)で読み返して、戻り値としてreturnする- 後片付けとして
Files.delete(path)を呼ぶ (テストには直接影響しないが、行儀がよい)
テストでは、空文字列 ""、英字 "hello"、日本語 "日本語" の 3 ケースで「書いた内容と読んだ内容が一致するか」を比較します。Files.writeString / Files.readString は UTF-8 がデフォルトなので、特に文字コードを指定しなくても日本語が正しく往復します。
慣れてきたら、Files.createTempDirectory で一時ディレクトリを作ったり、Files.write(path, List.of("line1", "line2")) で複数行を一気に書いたり、Files.lines(path) で Stream<String> として読んだりと、いろいろ寄り道してみてください。Files クラスのメソッド一覧を眺めるだけでも、Java の標準ライブラリの厚みを実感できるはずです。
よくある質問
Q. 中級の内容は実務でどれくらい使いますか?
A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。
Q. Stream と for ループはどっちで書くべき?
A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。
Q. 次のステップでは何を学ぶべきですか?
A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。
次のレッスン
次は ファイルを行単位で読む で、Java の標準ライブラリ `java を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- Files.write の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. Files.write とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
java.nio.file.Files.createTempFileを使って一時ファイルを作成すること (パスのハードコードは禁止)Files.writeString(path, content)で書き込み、Files.readString(path)で読み返した値をreturnすることthrows Exceptionかtry-catchでIOExceptionを適切に扱うこと
入出力例
test-cases.txt
writeAndRead("hello") → "hello"
writeAndRead("") → ""
writeAndRead("日本語") → "日本語"
writeAndRead("line1
line2") → "line1
line2"