Java | Java 標準ライブラリ:Files.copy

Java Java
スポンサーリンク

Files.copy を一言でいうと

Files.copy は、

「ファイル(またはディレクトリ)の中身を、別の場所にコピーするためのメソッド」

です。

「あるパスのものを、別のパスへコピーしたい」
「入力ストリームからファイルに書き出したい」

といったときに使います。

古い FileInputStreamFileOutputStreambyte[] でゴリゴリ書いていた
「コピー処理」を、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);
    }
}
Java

REPLACE_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) してしまう
NoSuchFileExceptiontarget.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 ループを自分で書かずに済むので、安全で読みやすい

タイトルとURLをコピーしました