Belajar Java
Stream, Reader, dan Writer
Versi ramah cetakTanpa bisa berinteraksi dengan dunia lain, suatu program tidak ada gunanya. Interaksi suatu program dengan dunia lain sering disebut input/output atau I/O. Sejak dulu, salah satu tantangan terbesar untuk mendesain bahasa pemrograman baru adalah mempersiapkan fasilitas untuk melakukan input dan output. Komputer bisa terhubung dengan beragam jenis input dan output dari berbagai perangkat. Jika bahasa pemrograman harus dibuat secara khusus untuk setiap jenis perangkat, maka kompleksitasnya akan tak lagi bisa ditangani.Salah satu kemajuan terbesar dalam sejarah pemrograman adalah adanya konsep (atau abstraksi) untuk memodelkan perangkat I/O. Dalam Java, abstraksi ini disebut dengan aliran (stream). Bagian ini akan memperkenalkan tentang aliran, akan tetapi tidak menjelaskan dengan komplit. Untuk lebih lengkapnya, silakan lihat dokumen resmi Java.
Ketika berhubungan dengan input/output, kita harus ingat bahwa ada dua kategori data secara umum : data yang dibuat oleh mesin, dan data yang bisa dibaca manusia. Data yang dibuat mesin ditulis dengan model yang sama dengan bagaimana data tersebut disimpan di dalam komputer, yaitu rangkaian nol dan satu. Data yang bisa dibaca manusia adalah data dalam bentuk rangkaian huruf. Ketika kita membaca suatu bilangan 3.13159, kita membacanya sebagai rangkaian huruf yang kita terjemahkan sebagai angka. Angka ini akan ditulis dalam komputer sebagai rangkaian bit yang kita tidak mengerti.
Untuk menghadapi kedua jenis data ini, Java memiliki dua kategori besar untuk aliran : aliran byte untuk data mesin (byte stream), dan aliran karakter (character stream) untuk data yang bisa dibaca manusia. Ada banyak kelas yang diturunkan dari kedua kategori ini.
Setiap objek yang mengeluarkan data ke aliran byte masuk sebagai kelas turunan dari kelas abstrak
OutputStream
. Objek yang membaca data dari aliran byte diturunkan dari kelas abstrak InputStream
. Jika kita menulis angka ke suatu OutputStream
, kita tidak akan bisa membaca data tersebut karena ditulis dalam bahasa mesin. Akan tetapi data tersebut bisa dibaca kembali oleh InputStream
. Proses baca tulis data akan menjadi sangat efisien, karena tidak ada penerjemahan yang harus dilakukan : bit yang digunakan untuk menyimpan data di dalam memori komputer hanya dikopi dari dan ke aliran tersebut.Untuk membaca dan menulis data karakter yang bisa dimengerti manusia, kelas utamanya adalah
Reader
dan Writer
. Semua kelas aliran karakter merupakan kelas turunan dari salah satu dari kelas abstrak ini. Jika suatu angka akan ditulis dalam aliran Writer
, komputer harus bisa menerjemahkannya ke dalam rangkaian karakter yang bisa dibaca maunsia. Membaca angka dari aliran
Reader
menjadi variabel numerik juga harus diterjemahkan, dari deretan karakter menjadi rangkaian bit yang dimengerti komputer. (Meskipun untuk data yang terdiri dari karakter, seperti dari editor teks, masih akan ada beberapa terjemahan yang dilakukan. Karakter disimpan dalam komputer dalam nilai Unicode 16-bit. Bagi orang yang menggunakan alfabet biasa, data karakter biasanya disimpan dalam file dalam kode ASCII, yang hanya menggunakan 8-bit. Kelas Reader
dan Writer
akan menangani perubahan dari 16-bit ke 8-bit dan sebaliknya, dan juga menangani alfabet lain yang digunakan negara lain.)Adalah hal yang mudah untuk menentukan apakah kita harus menggunakan aliran byte atau aliran karakter. Jika kita ingin data yang kita baca/tulis untuk bisa dibaca manusia, maka kita gunakan aliran karakter. Jika tidak, gunakan aliran byte.
System.in
dan System.out
sebenarnya adalah aliran byte dan bukan aliran karakter, karenanya bisa menangani input selain alfabet, misalnya tombol enter, tanda panah, escape, dsb.Kelas aliran standar yang akan dibahas berikutnya didefinisikan dalam paket
java.io
beserta beberapa kelas bantu lainnya. Kita harus mengimpor kelas-kelas tersebut dari paket ini jika kita ingin menggunakannya dalam program kita. Artinya dengan menggunakan "import java.io.*
" di awal kode sumber kita.Aliran tidak digunakan dalam GUI, karena GUI memiliki aliran I/O tersendiri. Akan tetapi kelas-kelas ini digunakan juga untuk file atau komunikasi dalam jaringan. Atau bisa juga digunakan untuk komunikasi antar thread yang sedang bekerja secara bersamaan. Dan juga ada kelas aliran yang digunakan untuk membaca dan menulis data dari dan ke memori komputer.
Operasi pada Aliran (Stream)
Kelas dasar I/OReader
, Writer
, InputStream
dan OutputStream
hanya menyediakan operasi I/O sangat dasar. Misalnya, kelas InputStream
memiliki metode instansipublic int read() throws IOException
read()
akan mengembalikan nilai -1. Jika ada kesalahan yang terjadi pada saat pengambilan input, maka pengecualian IOException
akan dilemparkan. Karena IOException
adalah kelas pengecualian yang harus ditangani, artinya kita harus menggunakan metode read()
di dalam penyataan try
atau mengeset subrutin untuk throws IOException
. (Lihat kembali pembahasan tentang pengecualian di bab sebelumnya)Kelas
InputStream
juga memiliki metode untuk membaca beberapa byte data dalam satu langkah ke dalam array byte
. Akan tetapi InputStream tidak memiliki metode untuk membaca jenis data lain, seperti int
atau double
dari aliran. Ini bukan masalah karena dalam prakteknya kita tidak akan menggunakan objek bertipe InputStream
secara langsung. Yang akan kita gunakan adalah kelas turunan dari InputStream
yang memiliki beberapa metode input yang lebih beragam daripada InputStream
itu sendiri.Begitu juga dengan kelas
OutputStream
memiliki metode output primitif untuk menulis satu byte data ke aliran output, yaitu metodepublic void write(int b) throws IOException
Kelas
Reader
dan Writer
memiliki operasi dasar yang hampir sama, yaitu read
dan write
, akan tetapi kelas ini berorientasi karakter (karena digunakan untuk membaca dan menulis data yang bisa dibaca manusia). Artinya operasi baca tulis akan mengambil dan menulis nilai char
bukan byte. Dalam prakteknya kita akan menggunakan kelas turunan dari kelas-kelas dasar ini.Salah satu hal menarik dari paket I/O pada Java adalah kemungkinan untuk menambah kompleksitas suatu aliran dengan membungkus aliran tersebut dalam objek aliran lain. Objek pembungkus ini juga berupa aliran, sehingga kita juga bisa melakukan baca tulis dari objek yang sama dengan tambahan kemampuan dalam objek pembungkusnya.
Misalnya,
PrintWriter
adalah kelas turunan dari Writer
yang memiliki metode tambahan untuk menulis tipe data Java dalam karakter yang bisa dibaca manusial. Jika kita memiliki objek bertipe Writer
atau turunannya, dan kita ingin menggunakan metode pada PrintWriter
untuk menulis data, maka kita bisa membungkus objek Writer
dalam objek PrintWriter
.Contoh jika
baskomKarakter
bertipe Writer
, maka kita bisa membuatPrintWriter printableBaskomKarakter = new PrintWriter(baskomKarakter);
printableBaskomKarakter
dengan menggunakan metode pada PrintWriter
yang lebih canggih, maka data tersebut akan ditempatkan di tempat yang sama dengan apabila kita menulis langsung pada baskomKarakter
. Artinya kita hanya perlu membuat antar muka yang lebih baik untuk aliran output yang sama. Atau dengan kata lain misalnya kita bisa menggunakan PrintWriter
untuk menulis file atau mengirim data pada jaringan.Untuk lengkapnya, metode pada kelas
PrintWriter
memiliki metode sebagai berikut :// Metode untuk menulis data dalam // bentuk yang bisa dibaca manusia public void print(String s) public void print(char c) public void print(int i) public void print(long l) public void print(float f) public void print(double d) public void print(boolean b) // Menulis baris baru ke aliran public void println() // Metode ini sama dengan di atas // akan tetapi keluarannya selalu // ditambah dengan baris baru public void println(String s) public void println(char c) public void println(int i) public void println(long l) public void println(float f) public void println(double d public void println(boolean b)
IOException
. Akan tetapi, kelas PrintWriter
memiliki metodepublic boolean checkError()
PrintWriter
menangkap pengecualian IOException
secara internal, dan mengeset nilai tertentu di dalam kelas ini jika kesalahan telah terjadi. Sehingga kita bisa menggunakan metode pada PrintWriter
tanpa khawatir harus menangkap pengecualian yang mungkin terjadi. Akan tetapi, jika kita ingin membuat progam yang tangguh tentunya kita harus selalu memanggil checkError()
untuk melihat apakah kesalahan telah terjadi ketika kita menggunakan salah satu metode pada PrintWriter
.Ketika kita menggunakan metode
PrintWriter
untuk menulis data ke aliran, data tersebut diubah menjadi rangkaian karakter yang bisa dibaca oleh manusia. Bagaimana caranya jika kita ingin membuat data dalam bentuk bahasa mesin?Paket
java.io
memiliki kelas aliran byte, yaitu DataOutputStream
yang bisa digunakan untuk menulis suatu data ke dalam aliran dalam format biner. DataOutputStream
berhubungan erat dengan OutputStream
seperti hubungan antara PrintWriter
dan Writer
.Artinya,
OutputStream
hanya berisi metode dasar untuk menulis byte, sedangkan DataOutputStream
memiliki metode writeDouble(double x)
untuk menulis nilai double, writeInt(int x)
untuk menulis nilai int, dan seterusnya. Dan juga kita bisa membungkus objek bertipe OutputStream
atau turunannya ke dalam aliran DataOutputStream
sehingga kita bisa menggunakan metode yang lebih kompleks.Misalnya, jika
baskomByte
adalah variabel bertipe OutputStream
, makaDataOutputStream baskomData = new DataOutputStream(baskomByte);
baskomByte
dalam baskomData
.Untuk mengambil data dari aliran,
java.io
memiliki kelas DataInputStream
. Kita bisa membungkus objek bertipe InputStream
atau turunannya ke dalam objek bertipe DataInputStream
. Metode di dalam DataInputStream
untuk membaca data biner bisa menggunakan readDouble()
, readInt()
dan seterusnya. Data yang ditulis oleh DataOutputStream
dijamin untuk bisa dibaca kembali oleh DataInputStream
, meskipun data kita tulis pada satu komputer dan data dibaca pada komputer jenis lain dengan sistem operasi berbeda. Kompatibilitas data biner pada Java adalah salah satu keunggulan Java untuk bisa dijalakan pada beragam platform.Salah satu fakta yang menyedihkan tentang Java adalah ternyata Java tidak memiliki kelas untuk membaca data dalam bentuk yang bisa dibaca oleh manusia. Dalam hal ini Java tidak memiliki kelas kebalikan dari
PrintWriter
sebagaimana DataOutputStream
dan DataInputStream
. Akan tetapi kita tetap bisa membuat kelas ini sendiri dan menggunakannya dengan cara yang persis sama dengan kelas-kelas di atas.Kelas
PrintWriter
, DataInputStream
, dan DataOutputStream
memungkinkan kita untuk melakukan input dan output semua tipe data primitif pada Java. Pertanyaannya bagaimana kita melakukan baca tulis suatu objek?Mungkin secara tradisional kita akan membuat fungsi sendiri untuk memformat objek kita menjadi bentuk tertentu, misalnya urutan tipe primitif dalam bentuk biner atau karakter kemudian disimpan dalam file atau dikirim melalui jaringan. Proses ini disebut serialisasi (serializing) objek.
Pada inputnya, kita harus bisa membaca data yang diserialisasi ini sesuai dengan format yang digunakan pada saat objek ini diserialisasi. Untuk objek kecil, pekerjaan semacam ini mungkin bukan masalah besar. Akan tetapi untuk ukuran objek yang besar, hal ini tidak mudah.
Akan tetapi Java memiliki cara untuk melakukan input dan output isi objek secara otomatis, yaitu dengan menggunakan
ObjectInputStream
dan ObjectOutputStream
. Kelas-kelas ini adalah kelas turunan dari InputStream
dan OutputStream
yang bisa digunakan untuk membaca dan menulis objek yang sudah diserialisasi.ObjectInputStream
dan ObjectOutputStream
adalah kelas yang bisa dibungkus oleh kelas InputStream
dan OutputStream
lain. Artinya kita bisa melakukan input dan output objek pada aliran byte apa saja.Metde untuk objek I/O adalah
readObject()
yang tersedia pada ObjectInputStream
dan writeObject(Object obj)
yang tersedia dalam ObjectOutputStream
. Keduanya bisa melemparkan IOException
. Ingat bahwa readObject()
mengembalikan nilai bertipe Object
yang artinya harus di-type cast ke tipe sesungguhnya.ObjectInputStream
dan ObjectOutputStream
hanya bekerja untuk objek yang mengimplementasikan interface yang bernama Serializable
. Lbih jauh semua variabel instansi pada objek harus bisa diserialisasi, karena interface Serializable
tidak mempunyai metode apa-apa. Interface ini ada hanya sebagai penanda untuk kompiler supaya kompiler tahu bahwa objek ini digunakan untuk baca tulis ke suatu media.Yang perlu kita lakukan adalah menambahkan "
implements Serializable
" pada definisi kelas. Banyak kelas standar Java yang telah dideklarasikan untuk bisa diserialisasi, termasuk semua komponen kelas Swing dan AWT. Artinya komponen GUI pun bisa disimpan dan dibaca dari dalam perangkat I/O menggunakan ObjectInputStream
dan ObjectOutputStream
Berbagai Jenis InputStream dan OutputStream
InputStream
Beberapa kelas turunan dari
InputStream
dapat dirangkum dalam tabel di bawah ini :Kelas | Kegunaan | Argumen yang dibutuhkan untuk membuat objek |
ByteArrayInputStream | Menggunakan buffer pada memori sebagai aliran input | Buffer yang akan digunakan sebagai aliran input |
StringBufferInputStream | Mengubah string menjadi InputStream | Suatu String (di dalamnya sebenarnya menggunakan StringBuffer ) |
FileInputStream | Untuk membaca informasi dari dalam file | String yang berupa nama suatu file, atau objek bertipe File atau FileDescriptor |
PipedInputStream | Menghasilkan data yang ditulis oleh PipedOutputStream . Mengimplementasi konsep "piping". Bisa digunakan untuk multi-threading | Objek PipedOutputStream |
SequenceInputStream | Menggabungkan dua atau lebih InputStream menjadi satu InputStream | Dua atau lebih objek bertipe InputStream atau kontainer bertipe Enumeration yang berisi InputStream yang akan digabungkan |
FilterInputStream | Kelas abstrak yang merupakan interface dari beberapa kelas bantu untuk menggunakan InputStream lain |
FilterInputStream
adalah lapisan di atas InputStream
yang berguna untuk memberi landasan pada kelas-kelas dekorator di atas. Kenapa dekorator? Karena kelas-kelas ini hanya memberikan fungsionalitas tambahan, akan tetapi tidak mengubah bagaimana I/O itu sendiri bekerja. Seperti disebutkan sebelumnya, bahwa kelas dasar InputStream
dan OutputStream
hanya memiliki metode-metode paling sederhana. Kelas-kelas ini memperbanyak metode baca/tulis untuk kemudahan pemrograman.Kelas
FilterInputStream
sendiri terdiri dari beberapa jenis, yang bisa dirangkum dalam tabel berikut ini :Kelas | Kegunaan | Argumen yang dibutuhkan untuk membuat objek |
DataInputStream | Digunakan bersama-sama dengan DataOutputStream sehingga kita bisa menulis tipe data primitif, kemudian membacanya kembali tanpa harus diformat sendiri | InputStream |
BufferedInputStream | Digunakan untuk menghindari pembacaan langsung dari media secara fisik setiap kali perintah read() diberikan. Atau dengan kata lain "gunakan buffer" untuk baca tulis | InputStream dengan kemungkinan menentukan besar buffer sendiri |
LineNumberInputStream | Mencatat nomor baris dalam InputStream . Kita bisa menggunakan perintah getLineNumber() dan setLineNumber(int) | InputStream |
PushBackInputStream | Memiliki satu byte buffer sehingga kita bisa meletakkan kembali karakter yang sudah diambil (dibaca) | InputStream |
Beberapa kelas turunan dari
OutputStream
dapat dirangkum dalam tabel di bawah ini :Kelas | Kegunaan | Argumen yang dibutuhkan untuk membuat objek |
ByteArrayOutputStream | Membuat buffer dalam memori. Semua data yang kita kirim akan disimpan di memori ini. | Opsional untuk memberikan besar buffer yang akan disiapkan |
FileOutputStream | Untuk menulis informasi ke dalam file | String yang berupa nama suatu file, atau objek bertipe File atau FileDescriptor |
PipedOutputStream | Informasi yang kita kirim di aliran output ini akan berakhir pada objek bertipe PipedInputStream . Mengimplementasi konsep "piping". Bisa digunakan untuk multi-threading | Objek PipedInputStream |
FilterOutputStream | Kelas abstrak yang merupakan interface dari beberapa kelas bantu untuk menggunakan OutputStream lain. |
FilterOutputStream
sendiri terdiri dari beberapa jenis, yang bisa dirangkum dalam tabel berikut ini :Kelas | Kegunaan | Argumen yang dibutuhkan untuk membuat objek |
DataOutputStream | Digunakan bersama-sama dengan DataInputStream sehingga kita bisa menulis tipe data primitif, kemudian membacanya kembali tanpa harus diformat sendiri | OutputStream |
PrintStream | Untuk mengeluarkan output yang sudah diformat. DataOutputStream hanya menangani bagaimana data disimpan sehingga bisa diambil kembali. PrintStream lebih berkonsentrasi pada "tampilan", sehingga data yang ditulis bisa dibaca dengan baik. | OutputStream dengan tambahan opsi boolean untuk memerintahkan buffer akan dikosongkan (flush) setiap kali baris baru ditulis. |
BufferedOutputStream | Digunakan untuk menghindari penulisan langsung dari media secara fisik setiap kali perintah write() diberikan. Atau dengan kata lain "gunakan buffer" untuk baca tulis. Kita bisa menggunakan perintah flush() untuk mengosongkan buffer dan mengirimkan hasilnya ke media fisik. | OutputStream dengan kemungkinan menentukan besar buffer sendiri |
0 komentar:
Posting Komentar