Setiap proses Unix lahir dengan tiga aliran yang sudah terbuka: stdin untuk masukan, stdout untuk keluaran data, dan stderr untuk keluaran diagnostik. JVM membungkus ketiganya sebagai System.in, System.out, dan System.err, dan kebiasaan kita memilahkan arah data menentukan apakah program bisa disambungkan ke pipeline — java Hitung | sort — atau justru merusaknya. Konvensinya sederhana: stdout membawa data, stderr membawa pesan untuk manusia, dan stdin menerima data dari sumber mana pun tanpa bertanya asalnya. Chapter ini membahas cara membaca stdin sampai habis, memilahkan kedua arah keluaran, dan mengenali lingkungan eksekusi — konsol sungguhan atau sekadar pipa.

Membaca System.in Sampai Habis #

System.in adalah InputStream mentah; pola lazimnya membungkus dengan BufferedReader lewat InputStreamReader dan charset eksplisit, lalu membaca baris demi baris sampai readLine() mengembalikan null — penanda EOF. Loop berhenti alami ketika sumber masukan menutup pipa, sama seperti mekanisme EOF yang kita kirim ke proses anak pada chapter sebelumnya:

java BacaMasukan.java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class BacaMasukan {
    public static void main(String[] args) throws Exception {
        BufferedReader pembaca = new BufferedReader(
                new InputStreamReader(System.in, StandardCharsets.UTF_8));

        String nama;
        int nomor = 0;
        while ((nama = pembaca.readLine()) != null) {
            nomor++;
            System.out.println(nomor + ". " + nama.toUpperCase());
        }
        System.out.println("total masukan: " + nomor);
    }
}

Output yang Dihasilkan:

text
1. BUDI
2. SITI
3. DEWI
total masukan: 3

Bedah singkatnya: baris new InputStreamReader(System.in, StandardCharsets.UTF_8) memastikan byte mentah didekode sebagai UTF-8 apa pun locale mesin; loop while ((nama = pembaca.readLine()) != null) berjalan untuk ketiga baris masukan lalu berhenti di EOF; toUpperCase() memakai locale default — untuk alfabet Latin hasilnya stabil.

Alternatif Scanner lebih ringkas untuk masukan berbentuk token (sc.nextInt()), tetapi BufferedReader lebih tepat saat data berupa baris penuh atau volume besar, karena tidak mengurai tiap token secara terpisah.

Memilahkan Data dari Diagnostik #

Pemisahan stdout dan stderr bukan gaya penulisan — keduanya adalah dua pipa berbeda pada sistem operasi. Program yang mencampur pesan manusia ke stdout membuat java Laporan | sort menerima baris aneh di tengah data. Program berikut menulis data ke System.out dan catatan ke System.err. Blok output di bawah menampilkan kedua aliran tergabung — persis seperti terminal yang menjalankan java LaporanArus.java 2>&1:

java LaporanArus.java
public class LaporanArus {
    public static void main(String[] args) {
        // stdout: data hasil — layak dialirkan ke program berikutnya.
        System.out.println("apel,10");
        System.out.println("jeruk,7");
        System.out.println("durian,4");

        // stderr: pesan diagnostik untuk manusia, bukan data.
        System.err.println("catatan: dua baris lama ditolak karena stok habis");
        System.err.println("catatan: total baris valid = 3");
    }
}

Output yang Dihasilkan:

text
apel,10
jeruk,7
durian,4
catatan: dua baris lama ditolak karena stok habis
catatan: total baris valid = 3

Saat stdout dialirkan ke berkas — java LaporanArus.java > hasil.csv — tiga baris pertama masuk ke hasil.csv sementara dua catatan tetap tampil di terminal. Inilah perilaku yang diharapkan pengguna pipeline: data dan diagnostik tidak saling mengotori.

Konsol, Pipe, dan Pengalihan Runtime #

Program interaktif perlu tahu apakah aliran standar tersambung ke terminal sungguhan atau ke pipa. Petunjuknya ada di System.console(): method ini mengembalikan objek Console ketika stdin dan stdout sama-sama tersambung ke terminal, dan null ketika salah satunya dialihkan. Selain mendeteksi, JVM juga membiarkan kita mengganti arah saat runtime lewat System.setOut, System.setErr, dan System.setIn:

import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class KenaliLingkungan {
    public static void main(String[] args) throws Exception {
        // Saat stdout dialihkan ke pipa atau berkas, konsol interaktif tidak ada.
        System.out.println("System.console() : " + System.console());

        // Arahkan ulang System.out ke berkas dengan charset eksplisit.
        Path berkas = Path.of("catatan.log");
        PrintStream pengganti = new PrintStream(
                Files.newOutputStream(berkas), true, StandardCharsets.UTF_8);
        PrintStream asli = System.out;
        System.setOut(pengganti);
        System.out.println("baris ini masuk ke berkas");
        System.setOut(asli);
        pengganti.close();
        System.out.println("isi berkas : " + Files.readString(berkas).trim());
    }
}

Output yang Dihasilkan:

text
System.console() : null
isi berkas : baris ini masuk ke berkas

Mari kita telaah alurnya:

  1. Baris System.console(): program dijalankan dengan stdout berupa pipa, sehingga hasilnya null — sinyal untuk mematikan prompt interaktif dan memilih mode non-interaktif.
  2. Baris new PrintStream(Files.newOutputStream(berkas), true, StandardCharsets.UTF_8): konstruktor ini meminta flush otomatis dan charset secara eksplisit; tanpa charset, perilaku bergantung pada locale mesin.
  3. Baris System.setOut(pengganti) … System.setOut(asli): seluruh kode yang menulis ke System.out — termasuk library pihak ketiga — otomatis mengikuti arah baru, lalu arah aslinya dipulihkan sebelum program menutup pengganti.

Kita kini menguasai ketiga pintu aliran sebuah proses Java. Namun kebebasan keluar-masuk JVM — kode native, pustaka eksternal, proses anak — selalu membawa risiko yang menyertainya. Chapter penutup section ini merangkum mitigasinya: keamanan interoperability.

Referensi #

Berkas Sumber

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

BacaMasukan.java

Unduh berkas
java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class BacaMasukan {
    public static void main(String[] args) throws Exception {
        BufferedReader pembaca = new BufferedReader(
                new InputStreamReader(System.in, StandardCharsets.UTF_8));

        String nama;
        int nomor = 0;
        while ((nama = pembaca.readLine()) != null) {
            nomor++;
            System.out.println(nomor + ". " + nama.toUpperCase());
        }
        System.out.println("total masukan: " + nomor);
    }
}

KenaliLingkungan.java

Unduh berkas
java
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class KenaliLingkungan {
    public static void main(String[] args) throws Exception {
        // Saat stdout dialihkan ke pipa atau berkas, konsol interaktif tidak ada.
        System.out.println("System.console() : " + System.console());

        // Arahkan ulang System.out ke berkas dengan charset eksplisit.
        Path berkas = Path.of("catatan.log");
        PrintStream pengganti = new PrintStream(
                Files.newOutputStream(berkas), true, StandardCharsets.UTF_8);
        PrintStream asli = System.out;
        System.setOut(pengganti);
        System.out.println("baris ini masuk ke berkas");
        System.setOut(asli);
        pengganti.close();
        System.out.println("isi berkas : " + Files.readString(berkas).trim());
    }
}

LaporanArus.java

Unduh berkas
java
public class LaporanArus {
    public static void main(String[] args) {
        // stdout: data hasil — layak dialirkan ke program berikutnya.
        System.out.println("apel,10");
        System.out.println("jeruk,7");
        System.out.println("durian,4");

        // stderr: pesan diagnostik untuk manusia, bukan data.
        System.err.println("catatan: dua baris lama ditolak karena stok habis");
        System.err.println("catatan: total baris valid = 3");
    }
}