Bab sebelumnya memakai extension function Kotlin di java.io.File. Bab ini beralih ke API NIO.2 modern: java.nio.file.Path dan kelas utilitas java.nio.file.Files. NIO.2 diperkenalkan di Java 7 (2011) dan direkomendasikan untuk kode baru karena fitur yang lebih kaya, dukungan symbolic link, atomic move, serta exception yang lebih informatif. Kotlin tidak menyembunyikan API ini — ia tetap dapat dipakai langsung, dan banyak operasi NIO.2 lebih ringkas daripada versi java.io.File.
Path vs Paths.get #
Ada dua cara membuat Path:
Path.of("data/file.txt")— diperkenalkan di Java 11, lebih ringkas.Paths.get("data/file.txt")— versi lama, masih kompatibel.
Keduanya menghasilkan objek Path yang sama. Output Tes 1 membuktikan pathA == pathB mengembalikan true.
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.nio.charset.StandardCharsets
const val DEMO_DIR = "/tmp/kotlin-tutorial-java-nio"
fun main() {
val dir = Paths.get(DEMO_DIR)
if (Files.exists(dir)) Files.walk(dir).sorted(Comparator.reverseOrder()).forEach { Files.deleteIfExists(it) }
Files.createDirectories(dir)
println("=== 1. Path.of vs Paths.get ===")
val pathA: Path = Path.of("data/contoh.txt")
val pathB: Path = Paths.get("data/contoh.txt")
println("Path.of(...) = $pathA")
println("Paths.get(...) = $pathB")
println("pathA == pathB = ${pathA == pathB}")
println()
println("=== 2. Files.createDirectories: buat dir bertingkat ===")
val subDir = dir.resolve("a/b/c")
Files.createDirectories(subDir)
println("subDir.exists = ${Files.exists(subDir)}")
println("subDir.fileName = ${subDir.fileName}")
println()
println("=== 3. Files.newBufferedWriter: tulis teks (NIO.2) ===")
val fileA = dir.resolve("catatan.txt")
Files.newBufferedWriter(fileA, StandardCharsets.UTF_8).use { w ->
w.write("baris satu")
w.newLine()
w.write("baris dua")
}
println("fileA.size = ${Files.size(fileA)} bytes")
println()
println("=== 4. Files.newBufferedReader: baca teks (NIO.2) ===")
val isiDibaca = Files.newBufferedReader(fileA, StandardCharsets.UTF_8).use { r ->
r.readText()
}
println("isi =")
println(isiDibaca.prependIndent(" "))
println()
println("=== 5. Files.readAllLines: List<String> seluruh baris ===")
val baris: List<String> = Files.readAllLines(fileA, StandardCharsets.UTF_8)
println("jumlah baris = ${baris.size}")
baris.forEachIndexed { i, b -> println(" baris[$i] = $b") }
println()
println("=== 6. Files.readString: baca seluruh file (Java 11+) ===")
val seluruh: String = Files.readString(fileA)
println("readString.length = ${seluruh.length}")
println()
println("=== 7. Files.writeString: tulis teks (Java 11+) ===")
val fileB = dir.resolve("baru.txt")
Files.writeString(fileB, "konten baru\nbaris kedua\n")
println("fileB.exists = ${Files.exists(fileB)}")
println("fileB.size = ${Files.size(fileB)} bytes")
println()
println("=== 8. Files.write dengan StandardOpenOption ===")
// Append, CREATE (buat jika belum ada)
Files.write(fileB, listOf("baris ketiga", "baris keempat"),
StandardCharsets.UTF_8,
StandardOpenOption.APPEND)
val semuaBaris = Files.readAllLines(fileB)
println("jumlah baris setelah append = ${semuaBaris.size}")
println()
println("=== 9. Files.lines: stream baris lazily (Java 8+) ===")
val hasilFilter = Files.lines(fileB)
.filter { it.contains("baris") }
.map { it.uppercase() }
.toList()
println("baris yang cocok + upper = $hasilFilter")
println()
println("=== 10. Files.exists, Files.delete, Files.copy ===")
val fileC = dir.resolve("salinan.txt")
Files.copy(fileB, fileC)
println("fileB.exists = ${Files.exists(fileB)}")
println("fileC (copy dari fileB).exists = ${Files.exists(fileC)}")
println("fileC.size = ${Files.size(fileC)} bytes")
println()
println("=== 11. Files.move: ganti nama atau pindah ===")
val fileD = dir.resolve("dipindah.txt")
Files.move(fileC, fileD)
println("fileC.exists setelah move = ${Files.exists(fileC)}")
println("fileD.exists setelah move = ${Files.exists(fileD)}")
println()
println("=== 12. Bersih-bersih ===")
Files.walk(dir).sorted(Comparator.reverseOrder()).forEach { Files.deleteIfExists(it) }
println("dir.exists setelah hapus = ${Files.exists(dir)}")
}Output yang Dihasilkan:
=== 1. Path.of vs Paths.get ===
Path.of(...) = data/contoh.txt
Paths.get(...) = data/contoh.txt
pathA == pathB = true
=== 2. Files.createDirectories: buat dir bertingkat ===
subDir.exists = true
subDir.fileName = c
=== 3. Files.newBufferedWriter: tulis teks (NIO.2) ===
fileA.size = 20 bytes
=== 4. Files.newBufferedReader: baca teks (NIO.2) ===
isi =
baris satu
baris dua
=== 5. Files.readAllLines: List<String> seluruh baris ===
jumlah baris = 2
baris[0] = baris satu
baris[1] = baris dua
=== 6. Files.readString: baca seluruh file (Java 11+) ===
readString.length = 20
=== 7. Files.writeString: tulis teks (Java 11+) ===
fileB.exists = true
fileB.size = 24 bytes
=== 8. Files.write dengan StandardOpenOption ===
jumlah baris setelah append = 4
=== 9. Files.lines: stream baris lazily (Java 8+) ===
baris yang cocok + upper = [BARIS KEDUA, BARIS KETIGA, BARIS KEEMPAT]
=== 10. Files.exists, Files.delete, Files.copy ===
fileB.exists = true
fileC (copy dari fileB).exists = true
fileC.size = 51 bytes
=== 11. Files.move: ganti nama atau pindah ===
fileC.exists setelah move = false
fileD.exists setelah move = true
=== 12. Bersih-bersih ===
dir.exists setelah hapus = falseMari kita bedah bagian-bagian penting:
- Bagian 1 (
Path.ofvsPaths.get): keduanya menghasilkan objekPathyang setara.Path.oflebih baru (Java 11) dan lebih disukai untuk kode baru.Paths.gettetap kompatibel untuk library lama. - Bagian 2 (
Files.createDirectories): membuat direktori beserta semua parent yang belum ada. Berbeda denganFile.mkdirs()yang mengembalikanBoolean,Files.createDirectoriesmelemparIOExceptionjika gagal — pesan error lebih informatif. - Bagian 3 (
Files.newBufferedWriter): membukaBufferedWriterlangsung tanpa dekorasi manual. Method.use { }memastikan writer ditutup.w.newLine()menulis newline sesuai sistem (tidak hardcode\n). - Bagian 4 (
Files.newBufferedReader+readText): extension function KotlinBufferedReader.readText()membaca seluruh isi..use { }menjamin resource ditutup. - Bagian 5 (
Files.readAllLines): setara denganFile.readLines()di Kotlin. MengembalikanList<String>. File diseluruhnya dibaca ke memori. - Bagian 6 (
Files.readString): shortcut Java 11+ untuk membaca seluruh file menjadi satuString. Output menunjukkan length 20 — 9 byte “baris satu” + 1 newline + 9 byte “baris dua” = 20. Cocok untuk file kecil. - Bagian 7 (
Files.writeString): shortcut Java 11+ untuk menulis teks. Default charset UTF-8. Menghapus isi sebelumnya (overwrite). - Bagian 8 (
Files.writedenganStandardOpenOption.APPEND): lebih fleksibel daripadawriteString. MenerimaIterable<CharSequence>sehingga bisa menulis List<String>sekaligus.StandardOpenOption.APPENDmenambah tanpa menghapus. - Bagian 9 (
Files.lines): mengembalikanStream<String>(Java Stream) — baris dibaca lazily. Kita dapat memfilter dan memetakan tanpa memuat seluruh file ke memori..toList()di akhir mengkonsumsi stream dan menutup resource. - Bagian 10 (
Files.copy): menyalin file. Default tidak menimpa file tujuan jika sudah ada — gunakanStandardCopyOption.REPLACE_EXISTINGuntuk menimpa. - Bagian 11 (
Files.move): mengganti nama atau memindah file. Default atomik di sistem file yang sama; tambahkanStandardCopyOption.ATOMIC_MOVEuntuk memaksakan atomisitas.
StandardOpenOption #
Enum StandardOpenOption mengontrol cara file dibuka:
| Opsi | Efek |
|---|---|
READ | Buka untuk baca (default untuk read) |
WRITE | Buka untuk tulis |
APPEND | Tambah di akhir, bukan timpa |
CREATE | Buat file jika belum ada |
CREATE_NEW | Buat file baru; gagal jika sudah ada |
TRUNCATE_EXISTING | Hapus isi file yang ada saat dibuka |
DELETE_ON_CLOSE | Hapus file saat stream ditutup |
Files.lines vs Files.readAllLines #
Perbedaan penting:
| Aspek | Files.readAllLines | Files.lines |
|---|---|---|
| Mengembalikan | List<String> | Stream<String> |
| Memori | Seluruh file dimuat | Satu baris pada satu waktu |
| Sumber | Eager | Lazy |
| Cocok untuk | File kecil | File besar (gigabyte) |
| Penutupan | Otomatis | Wajib via .use { } atau try-with-resources |
Referensi Lanjutan #
- Kotlin stdlib java.nio.file
— alias Kotlin stdlib untuk paket
java.nio.file(Path, Files, FileSystem) - Kotlin stdlib kotlin.io.path
— paket
kotlin.io.pathdengan extensionPath.readText,Path.writeText, danPath.listDirectoryEntries - Java SE 21 java.nio.file package
— ringkasan paket
java.nio.fileJava SE 21 untuk operasi file modern
Selanjutnya #
Bab berikutnya, Membaca File Teks, akan membahas lebih dalam operasi baca teks dengan fokus pada pola aman resource: File.readText(), File.readLines(), dan File.useLines { }. Kita akan melihat kapan memakai yang mana berdasarkan ukuran file dan kebutuhan pemrosesan.