Di Chapter sebelumnya kita melihat bahwa java.io.File membingkai path sekadar sebagai string dengan sedikit operasi. Kebutuhan nyata jauh lebih kaya: menggabungkan folder dengan nama berkas, membersihkan segmen .., menghitung jarak relatif antara dua path, dan membedakan path mutlak dari path relatif. API NIO.2 yang hadir di Java 7 menjawab kebutuhan itu melalui interface java.nio.file.Path — tipe immutable yang menjadi bahasa bersama seluruh I/O modern Java. Pada Chapter ini kita belajar membangun, mengurai, dan memanipulasi Path sebelum menyentuh isi berkasnya.

Membuat dan Mengurai Objek Path #

Objek Path dibuat lewat factory Path.of(...), bukan new. Method ini menerima satu string utuh atau rangkaian segmen yang digabungkan otomatis dengan pemisah milik platform; di Linux pemisahnya /, dan kita tidak pernah menuliskannya secara manual sehingga kode tetap portabel. Berbeda dari File, Path bersifat immutable: setiap operasi mengembalikan objek Path baru dan objek lama tidak berubah. Seperti File, membuat Path tidak menyentuh disk sama sekali — string hanya diurai dan divalidasi bentuknya.

Program berikut membangun path tiga segmen lalu mengurai balik setiap komponennya, ditutup konversi dua arah dengan API lama:

java MembuatPath.java
import java.io.File;
import java.nio.file.Path;

public class MembuatPath {
    public static void main(String[] args) {
        Path p = Path.of("data", "arsip", "daftar.txt");

        System.out.println("toString      : " + p);
        System.out.println("getFileName   : " + p.getFileName());
        System.out.println("getNameCount  : " + p.getNameCount());
        System.out.println("getName(0)    : " + p.getName(0));
        System.out.println("getName(2)    : " + p.getName(2));
        System.out.println("subpath(0, 2) : " + p.subpath(0, 2));
        System.out.println("isAbsolute    : " + p.isAbsolute());
        System.out.println("startsWith    : " + p.startsWith("data"));
        System.out.println("endsWith      : " + p.endsWith("daftar.txt"));

        Path kosong = Path.of("");
        System.out.println("path kosong   : '" + kosong + "'");

        for (int i = 0; i < p.getNameCount(); i++) {
            System.out.println("elemen ke-" + i + "    : " + p.getName(i));
        }

        File berkasLama = new File("daftar.txt");
        Path dariFile = berkasLama.toPath();
        Path sama = Path.of("daftar.txt");
        System.out.println("toPath equals : " + dariFile.equals(sama));
        System.out.println("toFile        : " + sama.toFile().getName());
    }
}

Output yang Dihasilkan:

text
toString      : data/arsip/daftar.txt
getFileName   : daftar.txt
getNameCount  : 3
getName(0)    : data
getName(2)    : daftar.txt
subpath(0, 2) : data/arsip
isAbsolute    : false
startsWith    : true
endsWith      : true
path kosong   : ''
elemen ke-0    : data
elemen ke-1    : arsip
elemen ke-2    : daftar.txt
toPath equals : true
toFile        : daftar.txt

Mari kita telaah bagian-bagian pentingnya:

  1. Baris 6 (Path.of("data", "arsip", "daftar.txt")): menggabungkan tiga segmen menjadi satu Path; toString() menampilkan bentuk gabungannya dengan pemisah platform.
  2. Baris 8-13 (getFileName, getNameCount, getName, subpath): mengurai path menjadi elemen bernomor mulai nol. subpath(0, 2) mengambil jangkauan elemen 0 sampai sebelum elemen 2 — aturan batas atas eksklusif sama seperti String.substring.
  3. Baris 14-16 (isAbsolute, startsWith, endsWith): startsWith dan endsWith bekerja per elemen path, bukan per karakter teks, sehingga daftar.txt cocok sebagai nama terakhir meskipun panjang path berbeda.
  4. Baris 18-19 (Path.of("")): satu-satunya Path tanpa elemen — berguna sebagai identitas pada operasi normalize() yang kita lihat nanti.
  5. Baris 21-23 (perulangan getName): Path mengimplementasikan Iterable<Path>, sehingga perulangan for-each juga bisa dipakai tanpa indeks.
  6. Baris 25-30 (toPath/toFile): jembatan dua arah dengan java.io.File. Dua path hasil konversi dibandingkan dengan equals() dan terbukti sama.

Resolve, Normalize, dan Relativize #

Tiga operasi membentuk mayoritas manipulasi path sehari-hari. resolve() menggabungkan path dasar dengan tambahan; normalize() membersihkan segmen redundan . dan ..; relativize() menghitung path yang ditempuh dari satu path ke path lain. Ketiganya bekerja murni pada string — tanpa memeriksa keberadaan berkas di disk.

import java.nio.file.Path;

public class MemanipulasiPath {
    public static void main(String[] args) {
        Path dasar = Path.of("proyek", "src");

        System.out.println("resolve relatif : " + dasar.resolve("Aplikasi.java"));
        System.out.println("resolve absolut : " + dasar.resolve(Path.of("/etc")));
        System.out.println("resolveSibling  : " + dasar.resolveSibling("test"));

        Path berantakan = Path.of("proyek", "src", "..", "doc", "readme.md");
        System.out.println("sebelum         : " + berantakan);
        System.out.println("normalize       : " + berantakan.normalize());

        Path target = Path.of("proyek", "doc", "readme.md");
        System.out.println("relativize      : " + dasar.relativize(target));

        Path relatif = Path.of("data", "daftar.txt");
        Path absolut = relatif.toAbsolutePath();
        System.out.println("toAbsolutePath isAbsolute  : " + absolut.isAbsolute());
        System.out.println("absolut endsWith daftar.txt: " + absolut.endsWith("daftar.txt"));

        Path denganTitik = Path.of("a", ".", "b");
        System.out.println("normalize titik : '" + denganTitik.normalize() + "'");
    }
}

Output yang Dihasilkan:

text
resolve relatif : proyek/src/Aplikasi.java
resolve absolut : /etc
resolveSibling  : proyek/test
sebelum         : proyek/src/../doc/readme.md
normalize       : proyek/doc/readme.md
relativize      : ../doc/readme.md
toAbsolutePath isAbsolute  : true
absolut endsWith daftar.txt: true
normalize titik : 'a/b'

Perilaku tiap operasi terbaca langsung dari keluarannya:

  1. resolve("Aplikasi.java") menempelkan segmen relatif ke belakang path dasar. Ketika argumennya path mutlak (/etc), resolve justru mengembalikan argumen itu apa adanya — path mutlak dianggap sudah komplet dan tidak perlu landasan.
  2. resolveSibling("test") mengganti elemen terakhir, persis seperti mengubah nama berkas pada folder yang sama: proyek/src menjadi proyek/test.
  3. normalize() meringkas proyek/src/../doc/readme.md menjadi proyek/doc/readme.md karena src/.. saling meniadakan; segmen . juga dibuang hingga a/./b menjadi a/b. Operasi ini string-only — .. tidak diperiksa terhadap struktur nyata di disk.
  4. relativize(target) menjawab pertanyaan “dari proyek/src, bagaimana mencapai proyek/doc/readme.md?” dengan jawaban ../doc/readme.md.
  5. toAbsolutePath() mengubah path relatif menjadi mutlak dengan menambahkan direktori kerja di depannya. Nilai hasilnya berbeda di setiap mesin, sehingga program ini hanya mencetak sifatnya (isAbsolute dan kecocokan akhiran) — bukan string absolutnya.

Path Relatif, Path Mutlak, dan Pembandingnya #

Pemahaman isAbsolute() penting sebelum beroperasi pada berkas. Path relatif seperti data/daftar.txt ditafsirkan terhadap direktori kerja proses Java — folder dari mana java dijalankan — sedangkan path mutlak seperti /etc/hosts berdiri sendiri tanpa konteks. Method toAbsolutePath() memperjelas path relatif menjadi mutlak, sedangkan relativize() berjalan arah sebaliknya untuk dua path yang berangkat dari titik sama.

Sebagai pembanding ringkas dua generasi API:

Aspekjava.io.Filejava.nio.file.Path
DiperkenalkanJDK 1.0 (1996)Java 7 (NIO.2)
Sifat objekmutable, method ubah isiimmutable, operasi kembalikan objek baru
Penggabungan segmenkonstruktor File(File, String)of(...), resolve, resolveSibling
Pembersihan ..manualnormalize()
Jarak antar pathtidak tersediarelativize()
Operasi diskada di class yang samadipisah ke class Files

Pemisahan tanggung jawab di baris terakhir adalah perubahan desain terbesar: Path hanya merepresentasikan nama, sementara seluruh operasi sistem berkas tinggal di utility class Files. Chapter berikutnya membuka kotak peralatan Files itu — dari exists sampai readString — yang membuat tugas I/O sehari-hari selesai dengan beberapa baris kode.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

MemanipulasiPath.java

Unduh berkas
java
import java.nio.file.Path;

public class MemanipulasiPath {
    public static void main(String[] args) {
        Path dasar = Path.of("proyek", "src");

        System.out.println("resolve relatif : " + dasar.resolve("Aplikasi.java"));
        System.out.println("resolve absolut : " + dasar.resolve(Path.of("/etc")));
        System.out.println("resolveSibling  : " + dasar.resolveSibling("test"));

        Path berantakan = Path.of("proyek", "src", "..", "doc", "readme.md");
        System.out.println("sebelum         : " + berantakan);
        System.out.println("normalize       : " + berantakan.normalize());

        Path target = Path.of("proyek", "doc", "readme.md");
        System.out.println("relativize      : " + dasar.relativize(target));

        Path relatif = Path.of("data", "daftar.txt");
        Path absolut = relatif.toAbsolutePath();
        System.out.println("toAbsolutePath isAbsolute  : " + absolut.isAbsolute());
        System.out.println("absolut endsWith daftar.txt: " + absolut.endsWith("daftar.txt"));

        Path denganTitik = Path.of("a", ".", "b");
        System.out.println("normalize titik : '" + denganTitik.normalize() + "'");
    }
}

MembuatPath.java

Unduh berkas
java
import java.io.File;
import java.nio.file.Path;

public class MembuatPath {
    public static void main(String[] args) {
        Path p = Path.of("data", "arsip", "daftar.txt");

        System.out.println("toString      : " + p);
        System.out.println("getFileName   : " + p.getFileName());
        System.out.println("getNameCount  : " + p.getNameCount());
        System.out.println("getName(0)    : " + p.getName(0));
        System.out.println("getName(2)    : " + p.getName(2));
        System.out.println("subpath(0, 2) : " + p.subpath(0, 2));
        System.out.println("isAbsolute    : " + p.isAbsolute());
        System.out.println("startsWith    : " + p.startsWith("data"));
        System.out.println("endsWith      : " + p.endsWith("daftar.txt"));

        Path kosong = Path.of("");
        System.out.println("path kosong   : '" + kosong + "'");

        for (int i = 0; i < p.getNameCount(); i++) {
            System.out.println("elemen ke-" + i + "    : " + p.getName(i));
        }

        File berkasLama = new File("daftar.txt");
        Path dariFile = berkasLama.toPath();
        Path sama = Path.of("daftar.txt");
        System.out.println("toPath equals : " + dariFile.equals(sama));
        System.out.println("toFile        : " + sama.toFile().getName());
    }
}