ファイル I/Oの要点
内容の確認日: 2026-10-04
この分野では、java.io パッケージの従来のストリーム (Reader・Writer・InputStream・OutputStream) と、java.nio.file パッケージの Path・Files の両方が問われます。メソッドが「何を返すか」「ファイルがあるとき・ないときにどうなるか (例外か、上書きか)」を正確に覚えることが大切です。
出題項目は「コンソールに対するデータの読み取りと書き込み」「I/O ストリームを使用したファイル・アクセス」「シリアライゼーションを使用したオブジェクトの読み取りと書き込み」「Path インタフェースおよび Files クラスを使用したファイルおよびディレクトリ・パスへのアクセス」「Stream API を使用したファイルアクセス操作」の 5 つです。
コンソールへの入出力
System.out.printf (format も同じ) は、書式を指定して出力します。%s は文字列、%d は整数、%f は小数、%n は改行です。%-5s は幅 5 で左寄せ、%5.2f は幅 5・小数点以下 2 桁で右寄せ、%03d は幅 3 で足りない桁を 0 で埋めます。
public class Main { public static void main(String[] args) { System.out.printf("[%-5s][%5.2f][%03d]%n", "ab", 3.14159, 7); }}java Main [ab ][ 3.14][007]
Console は System.console() で取得します。コンソールが使えない環境では null が返ることがあるので、使う前に確認します。readLine() は入力を String で、readPassword() は入力を画面に表示せずに char[] で返します (String ではないので、String 型の変数に代入するとコンパイルエラーです)。
Console console = System.console();if (console != null) { String name = console.readLine("名前: "); char[] password = console.readPassword("パスワード: "); console.printf("%s さん、ようこそ%n", name);}文字ストリームとバイト・ストリーム
java.io のストリームは 2 系統に分かれます。
・文字ストリーム: Reader・Writer のサブクラス (FileReader・FileWriter・BufferedReader・BufferedWriter など)。文字単位で読み書きする
・バイト・ストリーム: InputStream・OutputStream のサブクラス (FileInputStream・ObjectOutputStream など)。バイト単位で読み書きする
FileWriter をファイル名だけで作ると、既存のファイルの内容は消えて先頭から書き込まれます。第 2 引数に true を渡すと追記になります。BufferedWriter は書き込みをメモリにためておき、close() (または flush()) のときにまとめてファイルに書き込みます。BufferedReader の readLine() は 1 行を改行なしで返し、終わりに達すると null を返します。InputStream の read() は 1 バイトを 0 〜 255 の int で返し、終わりでは -1 を返します。
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileInputStream;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;import java.io.InputStream;public class Main { public static void main(String[] args) throws IOException { try (BufferedWriter w = new BufferedWriter(new FileWriter("memo.txt"))) { w.write("one"); w.newLine(); w.write("two"); } try (FileWriter w = new FileWriter("memo.txt", true)) { w.write("\nthree"); } try (BufferedReader r = new BufferedReader(new FileReader("memo.txt"))) { String line; while ((line = r.readLine()) != null) { System.out.println(line); } } try (InputStream in = new FileInputStream("memo.txt")) { System.out.println(in.read()); } }}java Main one two three 111
最後の 111 は、先頭の文字 o の文字コードです。
シリアライズ
Serializable を実装したクラスのオブジェクトは、ObjectOutputStream の writeObject でバイト列に変換 (シリアライズ) し、ObjectInputStream の readObject で元に戻せます (デシリアライズ)。主なルールは次のとおりです。
・transient を付けたフィールドと static フィールドは保存されない。復元したオブジェクトの transient フィールドは型の既定値 (null・0 など) になる
・デシリアライズでは、そのクラスのコンストラクタは呼ばれない (Serializable でない親クラスがあれば、その引数なしコンストラクタは呼ばれる)
・フィールドが参照しているオブジェクトもまとめて保存される。その中に Serializable でないものがあると NotSerializableException
・保存時と復元時でクラスの serialVersionUID が違うと InvalidClassException
import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;class User implements Serializable { private static final long serialVersionUID = 1L; String name; transient String password; int age; User(String name, String password, int age) { this.name = name; this.password = password; this.age = age; System.out.println("コンストラクタ"); }}public class Main { public static void main(String[] args) throws Exception { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (ObjectOutputStream out = new ObjectOutputStream(bytes)) { out.writeObject(new User("sato", "secret", 30)); } try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { User u = (User) in.readObject(); System.out.println(u.name + " " + u.password + " " + u.age); } }}java Main コンストラクタ sato null 30
「コンストラクタ」は new したときの 1 回だけ出力され、復元のときには出力されません。transient の password は null に戻っています。
Path の操作
Path は、ファイルやディレクトリの場所を表します。Path.of(...) (または Paths.get(...)) で作ります。Path の操作はパスの文字列を組み立てるだけで、ファイルが実際にあるかどうかは確かめません。
・getFileName(): 最後の要素 / getParent(): 最後の要素を除いたパス / getNameCount(): ルートを除いた要素の数
・normalize(): . (現在のディレクトリ) と .. (1 つ上) を取り除く
・resolve(p): パスをつなげる。p が絶対パスなら p そのものを返す
・relativize(p): 自分から p への相対パスを返す
import java.nio.file.Path;public class Main { public static void main(String[] args) { Path p = Path.of("/home/user/docs/../report.txt"); System.out.println(p.getFileName()); System.out.println(p.getParent()); System.out.println(p.getNameCount()); System.out.println(p.normalize()); Path base = Path.of("/home/user"); System.out.println(base.resolve("img/a.png")); System.out.println(base.relativize(Path.of("/home/user/img/a.png"))); System.out.println(base.resolve("/etc/hosts")); }}java Main report.txt /home/user/docs/.. 5 /home/user/report.txt /home/user/img/a.png img/a.png /etc/hosts
getParent() は .. を解決せずに、文字どおり最後の要素を除くだけです。.. も要素の 1 つとして数えられるので、getNameCount() は 5 になります (この例は macOS・Linux の実行結果です。Windows では区切りが \ になります)。
Files によるファイル操作
Files クラスの static メソッドで、ファイルやディレクトリを操作します。ファイルの有無による動きの違いが、よく問われます。
・createDirectory: 親ディレクトリがないと例外。createDirectories: 途中の親ディレクトリもまとめて作る
・copy・move: コピー先・移動先にファイルがあると FileAlreadyExistsException。StandardCopyOption.REPLACE_EXISTING を指定すると上書きする
・delete: ファイルがないと NoSuchFileException。deleteIfExists: ないときは false を返す
・write・writeString: 既定では上書き。readAllLines・readString: 内容を読む
import java.nio.file.Files;import java.nio.file.Path;import java.util.List;public class Main { public static void main(String[] args) throws Exception { Path dir = Path.of("work/sub"); Files.createDirectories(dir); Path a = dir.resolve("a.txt"); Files.write(a, List.of("alpha", "beta")); Path b = dir.resolve("b.txt"); Files.copy(a, b); System.out.println(Files.readAllLines(b)); System.out.println(Files.size(a) + " " + Files.exists(b)); System.out.println(Files.deleteIfExists(Path.of("none.txt"))); Files.copy(a, b); }}java Main [alpha, beta] 11 true false
実行時に例外が発生: FileAlreadyExistsException
Files.write(パス, 行のリスト) は各要素の後ろに改行を付けて書くので、alpha (5) + 改行 + beta (4) + 改行で 11 バイトです。
Files のストリーム操作
Files には、ファイルやディレクトリを Stream として扱うメソッドがあります。
・Files.lines(パス): ファイルの各行を要素とする Stream<String>
・Files.list(ディレクトリ): ディレクトリの直下の要素だけ (サブディレクトリの中は含まない)
・Files.walk(起点): 起点のディレクトリ自身を含めて、その下を深さ優先でたどる。第 2 引数で深さを制限できる
・Files.find(起点, 深さ, 条件): walk しながら条件に合うものだけを返す
これらが返す Stream は、ファイルやディレクトリを開いたままにしています。使い終わったら閉じる必要があるので、try-with-resources で囲むのが基本です。
import java.nio.file.Files;import java.nio.file.Path;import java.util.stream.Stream;public class Main { public static void main(String[] args) throws Exception { Path root = Path.of("tree"); Files.createDirectories(root.resolve("a/b")); Files.writeString(root.resolve("x.txt"), "1\n2\n3\n"); Files.writeString(root.resolve("a/b/y.txt"), "y"); try (Stream<Path> s = Files.walk(root)) { s.map(Path::toString).sorted().forEach(System.out::println); } try (Stream<Path> s = Files.list(root)) { System.out.println(s.count()); } try (Stream<String> lines = Files.lines(root.resolve("x.txt"))) { System.out.println(lines.mapToInt(Integer::parseInt).sum()); } }}java Main tree tree/a tree/a/b tree/a/b/y.txt tree/x.txt 2 6
walk の結果には起点の tree 自身が含まれます。list は直下の a と x.txt だけなので 2 です。
試験でよくある引っかけ
readPasswordの戻り値ConsoleのreadPassword()はchar[]を返します。Stringではありません。FileWriterの上書きと追記ファイル名だけで作ると既存の内容は消えます。追記するには第 2 引数に
trueを渡します。transientとstaticどちらもシリアライズされません。復元後の
transientフィールドは既定値になります。デシリアライズとコンストラクタ
Serializableなクラスのコンストラクタは、デシリアライズのときには呼ばれません。Files.copyの上書きコピー先があると
FileAlreadyExistsExceptionです。上書きするにはREPLACE_EXISTINGを指定します。walkとlistwalkは起点自身とその下すべて、listは直下の要素だけを返します。
問題で確認する
この分野の問題を、解説つきで読めます (登録不要)。
出典・参考
- Oracle University 日本「Java SE 17 Programmer II (1Z0-826-JPN) 試験」
- The Java Tutorials「Basic I/O」
- Java SE 17 API ドキュメント「Path」
- Java SE 17 API ドキュメント「Files」
コード例は、試験の対象バージョンで実際にコンパイル・実行して結果を確認しています。