Kotlin memperluas java.io.File dengan beragam extension function yang menyembunyikan boilerplate Java stream. Tanpa extension ini, membaca file di Java memerlukan FileInputStream → InputStreamReader → BufferedReader → try-with-resources. Dengan Kotlin, semuanya jadi satu baris: file.readText(). Bab ini membahas extension function paling sering dipakai beserta interop dengan stream Java dasar (InputStream, OutputStream, Reader, Writer).
Extension Function Utama #
Empat extension function paling sering dipakai di java.io.File:
| Function | Mengembalikan | Kapan dipakai |
|---|---|---|
readText() | String | File kecil, dibaca sekaligus |
writeText(s) | Unit | Menulis teks (overwrite) |
appendText(s) | Unit | Menambah teks di akhir |
readLines() | List<String> | File per-baris, dimuat semua |
useLines { } | T | Pemrosesan baris lazily |
forEachLine { } | Unit | Iterasi baris tanpa List |
readBytes() | ByteArray | File biner |
writeBytes(b) | Unit | Tulis biner |
import java.io.File
const val DEMO_DIR = "/tmp/kotlin-tutorial-java-io"
fun main() {
File(DEMO_DIR).deleteRecursively()
File(DEMO_DIR).mkdirs()
println("=== 1. File.writeText: menulis teks ke file ===")
val fileA = File("$DEMO_DIR/salam.txt")
fileA.writeText("Halo dari Kotlin!")
println("fileA.path = ${fileA.name}")
println("fileA.length = ${fileA.length()} bytes")
println()
println("=== 2. File.readText: membaca seluruh isi sebagai String ===")
val isi = fileA.readText()
println("isi = $isi")
println()
println("=== 3. File.readLines: membaca baris per baris ===")
val fileB = File("$DEMO_DIR/daftar.txt")
fileB.writeText("Apel\nMangga\nJeruk\nPisang\n")
val baris: List<String> = fileB.readLines()
println("jumlah baris = ${baris.size}")
baris.forEachIndexed { i, b -> println(" baris[${i}] = $b") }
println()
println("=== 4. File.appendText: menambah teks di akhir ===")
fileB.appendText("Anggur\n")
println("setelah appendText, jumlah baris = ${fileB.readLines().size}")
println()
println("=== 5. File.useLines: pemrosesan baris dengan resource aman ===")
val hasilUpper = fileB.useLines { lines ->
lines.map { it.uppercase() }.toList()
}
println("upper = $hasilUpper")
println()
println("=== 6. File.writeBytes / readBytes: data biner ===")
val fileBin = File("$DEMO_DIR/data.bin")
fileBin.writeBytes(byteArrayOf(0x48, 0x65, 0x6C, 0x6C, 0x6F)) // "Hello"
val bytes: ByteArray = fileBin.readBytes()
println("bytes (hex) = ${bytes.joinToString(" ") { "%02X".format(it) }}")
println("bytes (string) = ${String(bytes)}")
println()
println("=== 7. InputStream / OutputStream: stream rendah (interop Java) ===")
val fileStream = File("$DEMO_DIR/stream.txt")
fileStream.outputStream().use { out ->
out.write("baris dari stream".toByteArray())
}
val dariStream = fileStream.inputStream().use { it.readBytes() }
println("dari stream = ${String(dariStream)}")
println()
println("=== 8. Reader / Writer: stream karakter ===")
val fileWriter = File("$DEMO_DIR/karakter.txt")
fileWriter.writer().use { w ->
w.write("Halo karakter")
w.write("\nbaris kedua")
}
val dariReader = fileWriter.reader().use { r -> r.readText() }
println("dari reader = $dariReader")
println()
println("=== 9. forEachLine: iterasi baris tanpa memuat semua ke memori ===")
val fileBig = File("$DEMO_DIR/besar.txt")
fileBig.writeText((1..5).joinToString("\n") { "baris ke-$it" })
val jumlahKata = mutableListOf<String>()
fileBig.forEachLine { line ->
jumlahKata.add(line)
}
println("jumlah baris diproses = ${jumlahKata.size}")
println("baris terakhir = ${jumlahKata.last()}")
println()
println("=== 10. Bersih-bersih ===")
File(DEMO_DIR).deleteRecursively()
println("demo dir dihapus = ${!File(DEMO_DIR).exists()}")
}Output yang Dihasilkan:
=== 1. File.writeText: menulis teks ke file ===
fileA.path = salam.txt
fileA.length = 17 bytes
=== 2. File.readText: membaca seluruh isi sebagai String ===
isi = Halo dari Kotlin!
=== 3. File.readLines: membaca baris per baris ===
jumlah baris = 4
baris[0] = Apel
baris[1] = Mangga
baris[2] = Jeruk
baris[3] = Pisang
=== 4. File.appendText: menambah teks di akhir ===
setelah appendText, jumlah baris = 5
=== 5. File.useLines: pemrosesan baris dengan resource aman ===
upper = [APEL, MANGGA, JERUK, PISANG, ANGGUR]
=== 6. File.writeBytes / readBytes: data biner ===
bytes (hex) = 48 65 6C 6C 6F
bytes (string) = Hello
=== 7. InputStream / OutputStream: stream rendah (interop Java) ===
dari stream = baris dari stream
=== 8. Reader / Writer: stream karakter ===
dari reader = Halo karakter
baris kedua
=== 9. forEachLine: iterasi baris tanpa memuat semua ke memori ===
jumlah baris diproses = 5
baris terakhir = baris ke-5
=== 10. Bersih-bersih ===
demo dir dihapus = trueMari kita bedah bagian-bagian penting:
- Bagian 1 (
writeText): extension function yang membukaFileOutputStreamdi dalamtry-finally, menulis string, lalu menutup stream.length()mengembalikan 17 byte (panjang “Halo dari Kotlin!” = 17 karakter ASCII). - Bagian 2 (
readText): kebalikan dariwriteText. Membaca seluruh file menjadi satuString. Hati-hati: file besar akan dimuat seluruhnya ke memori — tidak cocok untuk log file berukuran gigabyte. - Bagian 3 (
readLines): membaca file baris per baris dan mengembalikanList<String>. Baris terakhir yang kosong (dari\ndi akhir) tidak disertakan. Output menunjukkan 4 baris buah, bukan 5. - Bagian 4 (
appendText): menambah teks tanpa menghapus yang ada. SetelahappendText("Anggur\n"), file memiliki 5 baris. Operasi ini membuka file dalam mode append. - Bagian 5 (
useLines { }): memproses baris secara lazy. Lambda menerimaSequence<String>yang dibaca satu per satu. Penting:Sequencehanya bisa dikonsumsi satu kali; jika butuh list, panggil.toList()di dalam blok. Setelah blok selesai, resource otomatis ditutup. - Bagian 6 (
readBytes/writeBytes): versi biner darireadText/writeText. Berguna untuk file gambar, data terkompresi, atau format biner lain.byteArrayOf(0x48, 0x65, ...)membentuk “Hello” dalam hex48 65 6C 6C 6F. - Bagian 7 (
outputStream/inputStream): extension function yang membuka stream Java dasar. Pola.use { }wajib untuk memastikan stream ditutup.useadalah extension Kotlin padaCloseableyang setara dengantry-with-resourcesdi Java. - Bagian 8 (
writer/reader): versi karakter dari stream.WriterdanReaderbekerja denganChar, bukanbyte. Cocok untuk file teks dengan encoding non-UTF-8 (default ke UTF-8 di Kotlin). - Bagian 9 (
forEachLine { }): iterasi baris dengan callback. Tidak memuat semua baris ke memori sepertireadLines(), tetapi menghandle satu baris pada satu waktu. Cocok untuk file besar.
Hierarki Stream Java #
Memahami hierarki stream Java membantu saat membaca dokumentasi library yang masih Java-centric:
InputStream (byte stream, baca)
├── FileInputStream (dari File)
├── BufferedInputStream (buffered)
└── ...
OutputStream (byte stream, tulis)
├── FileOutputStream (ke File)
├── BufferedOutputStream
└── ...
Reader (char stream, baca)
├── InputStreamReader (decorator: InputStream -> Reader)
└── BufferedReader (buffered, readLine())
Writer (char stream, tulis)
├── OutputStreamWriter
└── BufferedWriterExtension function Kotlin seperti File.readText() dan File.readLines() membungkus kombinasi FileInputStream → InputStreamReader → BufferedReader di dalam satu pemanggilan.
Encoding dan Charset #
Secara default, extension function Kotlin memakai Charsets.UTF_8. Untuk encoding lain, berikan parameter eksplisit:
val isi = file.readText(Charsets.ISO_8859_1)
file.writeText(isi, Charsets.ISO_8859_1)Referensi Lanjutan #
- Kotlin stdlib java.io.file
— alias Kotlin stdlib untuk
java.io.Filedengan extension baca/tulis teks - Java SE 21 File
— dokumentasi
java.io.FileJava SE 21 untuk konvensi path dan atribut - Kotlin stdlib kotlin.io
— paket
kotlin.ioyang memayungi extensionreadText,writeText, danuseLines
Selanjutnya #
Bab berikutnya, java.nio Interoperability, akan memperkenalkan API NIO.2 modern: Path, Files.newBufferedReader, Files.newBufferedWriter, dan Files.readAllLines. API ini direkomendasikan untuk kode baru karena fitur yang lebih kaya dan error handling yang lebih baik.