NIO.2

Bekerja dengan Path dan utilitas Files sebagai cara modern menangani berkas.

Daftar isi

Sejak Java 7, penanganan berkas di Java berpusat pada dua kelas paket java.nio.file, yaitu Path yang menunjuk lokasi dan Files yang menjalankan operasinya. Topik ini membiasakan keduanya sebagai cara modern bekerja dengan berkas, menggantikan kelas java.io.File yang lebih tua. Setelah menelusurinya, pembuatan folder, penulisan berkas, penyalinan, pemindahan, penghapusan, dan penelusuran isi folder dapat ditulis dalam beberapa baris kode yang mudah dibaca.

Path sebagai penunjuk lokasi#

Path adalah representasi jalur berkas pada sistem berkas. Objeknya tidak menyentuh berkas apa pun dan tidak memeriksa keberadaan lokasinya, sehingga Path menuju folder yang belum ada tetap sah dibuat. Jalurnya bisa relatif terhadap folder kerja program atau absolut sejak dari akar sistem. Pabrik Path.of(...) menerima satu jalur utuh atau beberapa potongan nama yang digabung dengan pemisah sesuai sistem operasi, dan metode-metodenya murni mengolah teks jalur tanpa efek samping.

Jalur yang sama ditulis dengan pemisah berbeda di tiap sistem operasi, yaitu tanda garis miring pada Linux dan macOS serta tanda garis miring terbalik pada Windows. Kelas Path menyerahkan urusan itu pada pabriknya, sehingga kode yang sama berjalan di keduanya tanpa penggabungan string manual. Objek Path juga bersifat imutabel, yakni setiap metode seperti resolve dan normalize mengembalikan objek baru, sedangkan jalur lama tidak pernah berubah. Kelas lama yang digantikannya, java.io.File, masih banyak ditemui pada kode lama, dan objek File memiliki metode toPath() untuk berpindah ke API baru ini.

java DemoPath.java
import java.nio.file.Path;

public class DemoPath {

    public static void main(String[] args) {
        Path relatif = Path.of("arsip", "2026", "catatan.txt");
        Path absolut = Path.of("/home/budi/arsip/catatan.txt");

        System.out.println("Path                : " + relatif);
        System.out.println("Nama berkas         : " + relatif.getFileName());
        System.out.println("Folder induk        : " + relatif.getParent());
        System.out.println("Jumlah elemen nama  : " + relatif.getNameCount());
        System.out.println("Absolut?            : " + relatif.isAbsolute() + " / " + absolut.isAbsolute());

        Path gabungan = relatif.getParent().resolve("foto.txt");
        Path kusut = Path.of("arsip/./2026/../2026/catatan.txt");
        System.out.println("Hasil resolve       : " + gabungan);
        System.out.println("Sebelum normalize   : " + kusut);
        System.out.println("Sesudah normalize   : " + kusut.normalize());
    }
}
text Keluaran
Path                : arsip/2026/catatan.txt
Nama berkas         : catatan.txt
Folder induk        : arsip/2026
Jumlah elemen nama  : 3
Absolut?            : false / true
Hasil resolve       : arsip/2026/foto.txt
Sebelum normalize   : arsip/./2026/../2026/catatan.txt
Sesudah normalize   : arsip/2026/catatan.txt

getFileName() mengambil elemen terakhir, getParent() mengambil jalur di depannya, dan getNameCount() menghitung jumlah elemen nama pada jalurnya. Metode resolve() menambahkan nama di ujung jalur, sedangkan normalize() merapikan tanda . dan .. tanpa menyentuh disk. Menjalankan programnya dari terminal cukup dengan satu perintah berikut.

bash
# berada di folder tempat DemoPath.java disimpan
java DemoPath.java
text Keluaran
Path                : arsip/2026/catatan.txt
Nama berkas         : catatan.txt
Folder induk        : arsip/2026
Jumlah elemen nama  : 3
Absolut?            : false / true
Hasil resolve       : arsip/2026/foto.txt
Sebelum normalize   : arsip/./2026/../2026/catatan.txt
Sesudah normalize   : arsip/2026/catatan.txt

Anatomi sebuah jalur beserta metode yang paling sering dipakai digambarkan berikut ini.

arsip/2026/catatan.txtelemen folderelemen foldernama berkasgetFileName()getParent() : arsip/2026getNameCount() : 3resolve("foto.txt") : arsip/2026/foto.txtmetode Path hanya mengolah teks jalurnya/home/budi/arsip/catatan.txtdiawali akar (root): jalur absolut, isAbsolute() : truetanpa akar di depan, jalur bersifat relatif terhadap folder kerja program
Bagian-bagian sebuah Path beserta metode yang membacanya

Utilitas Files untuk operasi nyata#

Semua operasi yang benar-benar menyentuh disk berada di kelas Files sebagai metode static, sehingga kelas ini tidak pernah dibuat sebagai objek dan setiap pemanggilannya diawali nama kelasnya. Files.createDirectories membuat folder beserta seluruh folder induknya tanpa protes bila sudah ada, dan Files.write menyimpan daftar baris ke berkas dalam satu panggilan. Untuk penyalinan dan pemindahan, opsi StandardCopyOption.REPLACE_EXISTING diperlukan bila berkas sasaran boleh ditimpa, sebab tanpa opsi itu kedua operasi melempar FileAlreadyExistsException. Program berikut mempraktikkan siklus lengkapnya, dari membuat hingga menghapus.

java DemoFiles.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;

public class DemoFiles {

    public static void main(String[] args) throws IOException {
        Path folder = Path.of("arsip");
        Path berkas = folder.resolve("catatan.txt");

        Files.createDirectories(folder);
        Files.write(berkas, List.of("Baris pertama", "Baris kedua"), StandardCharsets.UTF_8);

        System.out.println("Ada?   : " + Files.exists(berkas));
        System.out.println("Ukuran : " + Files.size(berkas) + " byte");
        System.out.println("Tipe   : berkas " + Files.isRegularFile(berkas)
                + " / folder " + Files.isDirectory(folder));

        Path salinan = folder.resolve("catatan-salin.txt");
        Files.copy(berkas, salinan, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Salinan ada? " + Files.exists(salinan));

        Path pindahan = Path.of("catatan-pindah.txt");
        Files.move(salinan, pindahan, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Pindahan ada? " + Files.exists(pindahan) + " (posisi lama: "
                + Files.exists(salinan) + ")");

        Files.delete(pindahan);
        System.out.println("Setelah dihapus? " + Files.exists(pindahan));
    }
}
text Keluaran
Ada?   : true
Ukuran : 26 byte
Tipe   : berkas true / folder true
Salinan ada? true
Pindahan ada? true (posisi lama: false)
Setelah dihapus? false

Ukuran 26 byte berasal dari 13 karakter baris pertama, 12 karakter baris kedua, ditambah dua pemisah baris. Files.move memindahkan salinan ke luar folder arsip, sehingga pemeriksaan Files.exists(salinan) setelahnya bernilai false. Seluruh operasi di atas melempar IOException, dan deklarasi throws pada main dipakai agar contohnya tetap pendek.

Ada dua metode pembuat folder yang mirip sehingga mudah tertukar. Files.createDirectory menuntut folder induknya sudah ada dan melempar FileAlreadyExistsException bila foldernya sendiri sudah terdaftar, sedangkan Files.createDirectories berlaku seperti mkdir -p pada command line, yaitu membuat seluruh induk yang kurang dan diam saja bila semuanya sudah ada. Untuk penyiapan folder kerja yang bisa dijalankan berulang, versi createDirectories itulah yang aman dipakai.

Menelusuri isi folder#

Files.list mengembalikan Stream<Path> berisi isi satu folder. Urutannya tidak dijamin oleh spesifikasi dan bergantung pada sistem berkas, sehingga contoh mengurutkannya sendiri lewat sorted() sebelum dicetak. Aliran itu membuka penangan berkas di belakang layar, sehingga wajib ditutup, dan try-with-resources mengurus penutupannya. Kata kunci var di sini menyimpulkan tipe Stream<Path> tanpa perlu mengimpor kelasnya.

java DemoDaftarIsi.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

public class DemoDaftarIsi {

    public static void main(String[] args) throws IOException {
        Path folder = Path.of("log");
        Files.createDirectories(folder.resolve("arsip-lama"));
        Files.write(folder.resolve("hari-1.txt"),
                List.of("Pagi: rapat singkat"), StandardCharsets.UTF_8);
        Files.write(folder.resolve("hari-2.txt"),
                List.of("Siang: merapikan data"), StandardCharsets.UTF_8);

        try (var aliran = Files.list(folder)) {
            List<Path> isi = aliran.sorted().toList();
            for (Path p : isi) {
                String jenis = Files.isDirectory(p) ? "folder" : "berkas";
                System.out.println(jenis + ": " + p.getFileName());
            }
        }
    }
}
text Keluaran
folder: arsip-lama
berkas: hari-1.txt
berkas: hari-2.txt

Files.isDirectory membedakan folder dari berkas biasa, dan hasil pengurutannya berdasarkan nama menjadikan keluaran stabil di mesin mana pun. Untuk folder dengan isi yang sangat banyak, aliran yang diproses seketika seperti ini jauh lebih hemat memori daripada mengumpulkan seluruh jalur terlebih dahulu. Metode saudaranya, Files.walk, menelusuri pohon folder hingga ke subfolder dalamnya, tetapi untuk daftar satu tingkat, Files.list sudah memadai dan hasilnya lebih mudah diurai.

Kesalahan umum#

  • Mengira membuat Path otomatis membuat berkasnya. Path hanya teks penunjuk, sedangkan berkas baru tercipta lewat Files.write, Files.createFile, atau Files.createDirectories.
  • Menyalin atau memindah tanpa StandardCopyOption.REPLACE_EXISTING ke sasaran yang sudah ada. Kedua operasi melempar FileAlreadyExistsException alih-alih menimpa diam-diam.
  • Memanggil Files.delete pada folder yang masih berisi. Kegagalannya berupa DirectoryNotEmptyException, sehingga isinya harus dikosongkan lebih dahulu.
  • Membiarkan hasil Files.list tanpa penutupan. Penangan folder di belakang aliran itu tetap terbuka, dan pola try-with-resources menutupnya dengan andal.

Ringkasan#

  • Path menunjuk lokasi tanpa menyentuh disk, sedangkan operasi nyata dikerjakan metode static kelas Files.
  • Path.of membangun jalur lintas sistem operasi, dengan metode seperti getParent, resolve, dan normalize yang murni mengolah teks jalur.
  • Files menyediakan exists, size, copy, move, delete, write, dan readAllLines untuk tugas sehari-hari.
  • Files.list mengembalikan Stream<Path> yang urutannya tidak dijamin dan wajib ditutup.

Referensi#