Files.copy を一言でいうと
Files.copy は、
「ファイル(またはディレクトリ)の中身を、別の場所にコピーするためのメソッド」
です。
「あるパスのものを、別のパスへコピーしたい」
「入力ストリームからファイルに書き出したい」
といったときに使います。
古い FileInputStream + FileOutputStream + byte[] でゴリゴリ書いていた
「コピー処理」を、1 行で安全に書けるようにしてくれる存在です。
基本形:Path から Path へファイルをコピーする
一番シンプルな使い方
まずは「ファイルからファイルへ」の基本形です。
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
public class FilesCopyBasic {
public static void main(String[] args) throws IOException {
Path source = Path.of("data.txt"); // コピー元
Path target = Path.of("backup/data.txt"); // コピー先
Files.copy(source, target);
}
}
Javaこの一行で、
data.txt の中身が、backup/data.txt にコピーされます。
ポイントとして、
コピー先ディレクトリ(backup)は、事前に存在している必要があるFiles.copy 自体は、ディレクトリの作成まではしてくれない
という点は意識しておいてください。
コピー先に同名ファイルがある場合の挙動
何もオプションを付けない Files.copy(source, target) では、
コピー先にファイルがすでに存在すると、FileAlreadyExistsException が投げられます。
「上書きしたい」のか、「存在したらエラーで止めたい」のかによって、
書き方を変える必要があります(後で詳しく説明します)。
CopyOption を使って「上書き」や「属性コピー」を制御する
上書きしたい場合:StandardCopyOption.REPLACE_EXISTING
コピー先にすでにファイルがあっても、上書きしていい場合は、
オプションを指定します。
import java.nio.file.*;
public class FilesCopyReplace {
public static void main(String[] args) throws Exception {
Path source = Path.of("data.txt");
Path target = Path.of("backup/data.txt");
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
JavaREPLACE_EXISTING を付けると、
「コピー先に同名ファイルがあっても、上書きしてかまわない」
という意味になります。
逆にいうと、デフォルトでは「誤って上書きしないように」
例外で教えてくれる安全寄りの挙動になっています。
ファイル属性もコピーしたい場合:COPY_ATTRIBUTES
タイムスタンプやパーミッションなどの属性もコピーしたい場合は、
Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
Javaのように、複数のオプションを並べて指定できます。
初心者のうちは、
上書きしたい → REPLACE_EXISTING
属性もコピーしたい → COPY_ATTRIBUTES を足す
くらいを押さえておけば十分です。
InputStream / OutputStream と組み合わせたコピー
InputStream → Path(ダウンロードしたデータをファイルに保存するイメージ)
Files.copy は、「Path → Path」だけでなく
「InputStream → Path」や「Path → OutputStream」も扱えます。
例えば、ネットワークやクラスパスから得た InputStream を
ファイルに保存したい場合。
import java.io.InputStream;
import java.nio.file.*;
public class FilesCopyFromStream {
public static void main(String[] args) throws Exception {
// 例として、クラスパス上のリソースを InputStream として開く
try (InputStream in = FilesCopyFromStream.class
.getResourceAsStream("/sample.txt")) {
if (in == null) {
System.out.println("リソースが見つかりません");
return;
}
Path target = Path.of("output.txt");
Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
}
}
}
Javaここで起きていることは、
InputStream から読み出して
それを target のファイルにコピーする
という処理を Files.copy が一気にやってくれている、ということです。
byte[] バッファを自分で用意して read/write する必要がありません。
Path → OutputStream(ファイルの内容をどこかへ書き出す)
逆に、ファイルの中身を OutputStream に流したい場合もあります。
例えば、HTTP レスポンスにそのままファイル内容を流したい場合などです。
import java.io.OutputStream;
import java.nio.file.*;
public class FilesCopyToStream {
public static void main(String[] args) throws Exception {
Path source = Path.of("data.txt");
try (OutputStream out = Files.newOutputStream(Path.of("copy.txt"))) {
Files.copy(source, out);
}
}
}
Javaここでは OutputStream もファイルにしていますが、
実際にはネットワークのストリームだったり、圧縮ストリームだったり、
好きな OutputStream に差し替えられます。
ディレクトリをコピーするときの注意点
Files.copy は「単一ファイル or 1 つのディレクトリ」単位
Files.copy は、Path がディレクトリを指していた場合、
「ディレクトリ自体を一つの“エントリ”としてコピーしようとする」
だけです。
つまり、「中のファイルやサブディレクトリを全部まとめてコピーする」
ような挙動にはなりません。
ディレクトリ丸ごとコピーしたい場合は、
再帰的に中身を列挙して、
1 つずつ Files.copy する
といった処理を書く必要があります(もしくは外部ライブラリを使う)。
初心者のうちは、
Files.copy は基本「1 つのファイルをコピーする」もの
と捉えておくと混乱しにくいです。
エラー(例外)とよくある失敗パターン
代表的な例外
Files.copy は I/O なので、IOException(またはそのサブクラス)を投げます。
よく出会うのは、
コピー元が存在しない → NoSuchFileException
コピー先がすでに存在している(REPLACE_EXISTING なし) → FileAlreadyExistsException
権限がない、ディスクいっぱいなど → IOException
です。
例として、シンプルなエラーハンドリングを書いてみます。
import java.nio.file.*;
import java.io.IOException;
public class FilesCopyWithErrorHandling {
public static void main(String[] args) {
Path source = Path.of("data.txt");
Path target = Path.of("backup/data.txt");
try {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("コピー成功: " + target);
} catch (NoSuchFileException e) {
System.err.println("コピー元が見つかりません: " + e.getFile());
} catch (FileAlreadyExistsException e) {
System.err.println("コピー先ファイルがすでに存在します: " + e.getFile());
} catch (IOException e) {
System.err.println("コピー中に I/O エラーが発生しました: " + e.getMessage());
}
}
}
Java最初は throws IOException で逃がしてしまっても構いませんが、
実務では「どのパターンで失敗しうるか」を意識すると、一段レベルが上がります。
よくある失敗パターン
コピー先のディレクトリが存在しないのに Files.copy(source, target) してしまう
→ NoSuchFileException。target.getParent() のディレクトリを事前に作っておく必要があります。
コピー先がディレクトリなのに、上書きオプションを付けて無理やりコピーしようとする
→ 意図しない挙動なので、基本やらない(target.resolve(source.getFileName()) で中にコピーするのが自然)
ディレクトリ丸ごとコピーできると勘違いする
→ 中身はコピーされないので注意
「自分でバイト配列を書かない」ことの意味
昔の手書きコピーとの比較
昔ながらのファイルコピーは、よくこんなコードで書かれていました。
try (InputStream in = new FileInputStream("data.txt");
OutputStream out = new FileOutputStream("copy.txt")) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
Javaやっていることは、
バッファを用意する
read / write を繰り返す
close を忘れないようにする
という、典型的な「I/O のお作法」です。
これを Files.copy なら、たった 1 行に圧縮できます。
Files.copy(Path.of("data.txt"), Path.of("copy.txt"));
Javaもちろん、内部では似たような処理をしていますが、Files.copy に任せることで、
バッファサイズの調整
読み書きループの書き間違い
クローズ忘れ
といった自前実装ミスを避けられます。
初心者のうちは、この「高レベル API に任せる」感覚をしっかり持っておくと、
I/O まわりのバグをかなり減らせます。
まとめ:Files.copy を自分の中でこう位置づける
Files.copy を初心者向けにまとめると、こうなります。
「Path・InputStream・OutputStream の組み合わせで、ファイル(またはデータ)のコピーを安全に 1 行で書けるメソッド」
特に意識しておきたいポイントは、
コピー元とコピー先を Path で指定して、単一ファイルをコピーするのが基本
コピー先がすでにあるときは例外になる(上書きしたいなら REPLACE_EXISTING を指定)
InputStream → Path、Path → OutputStream のコピーもこなせる
ディレクトリ丸ごとコピーではない(中身の再帰コピーは自分で書くか別手段が必要)
昔ながらの read/write ループを自分で書かずに済むので、安全で読みやすい
