ファイルコピーは「安全にデータを移動・退避させる」ための技
業務システムでは、「インポート用ファイルをバックアップしてから処理したい」「出力結果を別ディレクトリに退避したい」「テンプレートを所定の場所にコピーして使いたい」といった、“ファイルをコピーする”場面が頻繁に出てきます。
ここで雑にコピーすると、「上書きしてはいけないファイルを消してしまう」「途中で失敗して中途半端なファイルが残る」といった事故につながります。
だからこそ、ファイルコピーをユーティリティとして整理し、「どういうときに上書きするか」「存在していたらどうするか」「例外はどう扱うか」を決めておくと、実務でかなり効いてきます。
基本:Files.copy の使い方と「上書き」の扱い
一番シンプルなファイルコピー
Java 7 以降では、java.nio.file.Files の copy を使うのが基本です。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileCopyBasic {
public static void main(String[] args) throws IOException {
Path src = Path.of("data/input.csv");
Path dest = Path.of("backup/input.csv");
Files.copy(src, dest);
}
}
Javaこのコードは、「data/input.csv を backup/input.csv にコピーする」という意味です。
ここで重要なのは、デフォルトでは「コピー先がすでに存在していたら例外が投げられる」という点です。
コピー先が存在しているときにどうするかは、業務的にとても重要な判断なので、Files.copy は「何も指定しないなら“上書きしない”」という安全側の挙動になっています。
上書きしたい場合はオプションを明示する
「バックアップファイルは毎回上書きしてよい」といったケースでは、上書きオプションを明示します。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class FileCopyOverwrite {
public static void main(String[] args) throws IOException {
Path src = Path.of("data/input.csv");
Path dest = Path.of("backup/input.csv");
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
JavaStandardCopyOption.REPLACE_EXISTING を指定すると、「コピー先が存在していても上書きする」挙動になります。
ここで深掘りしたいポイントは、「“上書きするかどうか”をコード上で明示することが、事故防止につながる」ということです。
デフォルトの挙動に任せるのではなく、「このコピーは上書きしてよい」「このコピーは上書きしてはいけない」を、オプション指定でハッキリさせる癖をつけると安全です。
実務で使える「ファイルコピーユーティリティ」の最小形
「上書きあり」「上書きなし」をメソッド名で分ける
毎回 Files.copy にオプションを直書きすると、呼び出し側のコードが読みにくくなります。
そこで、「上書きあり」「上書きなし」をメソッドレベルで分けたユーティリティを用意します。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public final class FileCopier {
private FileCopier() {}
public static void copy(Path src, Path dest) throws IOException {
Files.copy(src, dest);
}
public static void copyOverwrite(Path src, Path dest) throws IOException {
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
Java使う側はこう書けます。
Path src = Path.of("data/input.csv");
Path dest = Path.of("backup/input.csv");
// 上書きしたくないコピー
FileCopier.copy(src, dest);
// 上書きしてよいコピー
FileCopier.copyOverwrite(src, dest);
Javaここでの重要ポイントは、「メソッド名に“業務的な意図”を乗せる」ことです。copyOverwrite と書かれていれば、「ここは上書きしてよいバックアップなんだな」と一目で分かります。Files.copy(..., REPLACE_EXISTING) があちこちに散らばるより、意図が読み取りやすくなります。
例題:インポート前にファイルをバックアップする
「処理前に元ファイルを退避しておく」パターン
よくある業務バッチとして、「in/ ディレクトリに置かれた CSV を読み込み、処理後に backup/ に退避する」という処理を考えます。
ここでは、「バックアップは上書きしてよい」とします。
import java.io.IOException;
import java.nio.file.Path;
public class ImportJob {
public void run() throws IOException {
Path src = Path.of("in/users.csv");
Path backup = Path.of("backup/users.csv");
// まずバックアップを取る
FileCopier.copyOverwrite(src, backup);
// ここから先は src を読み込んで処理する
importUsers(src);
}
private void importUsers(Path csv) {
// CSV 読み込み処理…
}
}
Javaここで深掘りしたいのは、「“バックアップを取る”という業務的な意味を、ファイルコピーの前に明示している」ことです。
単に Files.copy と書くのではなく、「なぜコピーしているのか」「上書きしてよいのか」をコードから読み取れるようにしておくと、
後から読む人が安心して変更できます。
ディレクトリごとコピーしたい場合の考え方
Files.walk を使って再帰コピーする
Files.copy は「ファイル単体」か「空のディレクトリ」しかコピーしてくれません。
「ディレクトリ配下を丸ごとコピーしたい」場合は、自分で再帰的にたどる必要があります。
シンプルなディレクトリコピーのユーティリティ例です。
import java.io.IOException;
import java.nio.file.*;
public final class DirectoryCopier {
private DirectoryCopier() {}
public static void copyTree(Path srcDir, Path destDir) throws IOException {
Files.walk(srcDir).forEach(src -> {
try {
Path relative = srcDir.relativize(src);
Path dest = destDir.resolve(relative);
if (Files.isDirectory(src)) {
Files.createDirectories(dest);
} else {
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new RuntimeException("ディレクトリコピー中にエラー: " + src, e);
}
});
}
}
Java使う側はこうです。
DirectoryCopier.copyTree(Path.of("templates"), Path.of("work/templates"));
Javaここでの重要ポイントは、「ディレクトリコピーは“ディレクトリ作成”と“ファイルコピー”の組み合わせである」と理解することです。Files.walk で元ディレクトリをたどり、ディレクトリなら createDirectories、ファイルなら copy、という分担にすると、
単体のファイルコピーユーティリティともきれいに組み合わせられます。
ファイルコピーの“落とし穴”と注意点
コピー元・コピー先の存在と権限
コピー前に、「コピー元が本当に“読み取り可能なファイル”か」「コピー先ディレクトリが存在していて書き込み可能か」を確認しておくと、
エラーを早めに検知できます。
import java.nio.file.Files;
public final class SafeFileCopier {
private SafeFileCopier() {}
public static void copyReadableFile(Path src, Path dest) throws IOException {
if (!Files.isRegularFile(src) || !Files.isReadable(src)) {
throw new IllegalArgumentException("コピー元が読み取り可能なファイルではありません: " + src);
}
Files.createDirectories(dest.getParent());
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
Javaここでのポイントは、「“前提条件”をコピー前にチェックして、以降の処理をシンプルにする」ことです。
「そもそもファイルじゃなかった」「ディレクトリがなかった」という問題を、コピー中の例外に任せるのではなく、
業務的に意味のあるメッセージで表に出すと、運用・調査が楽になります。
大きなファイルと I/O コスト
大きなファイルをコピーするときは、単純に Files.copy を呼ぶだけでも時間がかかります。
バッチ処理の中で大量のファイルをコピーする場合は、「どのタイミングでコピーするか」「並列にやりすぎないか」も設計ポイントになります。
ここで大事なのは、「ファイルコピーは“ただのユーティリティ呼び出し”ではなく、“重い I/O 処理”である」という意識です。
ログに進捗を出したり、スレッドプールを分けたりして、「コピーがボトルネックになっていないか」を意識できると、実務レベルの設計に近づきます。
まとめ:ファイルコピーユーティリティで身につけるべき感覚
ファイルコピーは、「ただデータを複製する」だけではなく、「何のためにコピーし、上書きしてよいのか、前提条件は何か」をコードで表現する行為です。
押さえておきたい感覚はこうです。
Files.copy のデフォルトは「コピー先が存在したら例外」であり、上書きしたいときは REPLACE_EXISTING を明示する。
「上書きあり/なし」をユーティリティメソッドに分け、メソッド名で意図を伝える。
インポート前のバックアップなど、「なぜコピーしているか」をコメントやメソッド名で表現する。
ディレクトリコピーは「ディレクトリ作成+ファイルコピー」の組み合わせとして設計する。
コピー元・先の前提条件(存在・権限)をチェックし、エラーは業務的に意味のあるメッセージで表に出す。
