☕ Java
Input and Output
Input and output (I/O) in Java is how a program communicates with the outside world — reading data from the keyboard, files, or network, and writing data to the screen, files, or network. Java provides multiple layers of I/O abstraction: the low-level java.io streams, the higher-level java.nio channels, and convenience classes such as Scanner, BufferedReader, and PrintWriter that wrap the streams for common use cases.
Standard Streams
Java provides three standard streams available in every program without any import or setup. System.in is the standard input stream connected to the keyboard. System.out is the standard output stream connected to the console. System.err is the standard error stream also connected to the console but used for error messages. All three are fields of the java.lang.System class and are available automatically.
Java
// ── Three standard streams: ──────────────────────────────────────────
//
// System.in → InputStream — reads bytes from keyboard (stdin)
// System.out → PrintStream — writes to console (stdout)
// System.err → PrintStream — writes errors to console (stderr)
//
// These are always available — no import, no setup needed.
// ── System.out — write to console: ───────────────────────────────────
System.out.print("Hello"); // no newline at end
System.out.println("Hello"); // adds newline at end
System.out.println(); // blank line
System.out.printf("Name: %s%n", "Alice"); // formatted output
System.out.println(42); // works with any primitive type
System.out.println(3.14);
System.out.println(true);
System.out.println('A');
// ── System.err — write error messages: ───────────────────────────────
System.err.println("ERROR: File not found");
System.err.printf("ERROR: Invalid input: %s%n", userInput);
// System.err output is red in most IDEs and goes to stderr stream
// (can be redirected separately from stdout in shell)
// ── System.in — raw byte input (rarely used directly): ───────────────
int byteValue = System.in.read(); // reads one byte (0–255)
// Direct use is rare — wrap with Scanner or BufferedReader instead.
// ── Redirect standard streams programmatically: ───────────────────────
PrintStream fileOut = new PrintStream(new File("output.txt"));
System.setOut(fileOut); // redirect all System.out to file
System.out.println("This goes to output.txt, not console");
System.setOut(new PrintStream(FileDescriptor.out)); // restore
// ── print vs println vs printf: ──────────────────────────────────────
System.out.print("A");
System.out.print("B");
System.out.print("C");
// Output: ABC (all on same line — no newlines)
System.out.println("A");
System.out.println("B");
// Output:
// A
// B
System.out.printf("%-10s %5d%n", "Alice", 95);
System.out.printf("%-10s %5d%n", "Bob", 87);
// Output:
// Alice 95
// Bob 87I/O Class Hierarchy
Java's I/O system is built on four abstract base classes. InputStream and OutputStream handle raw bytes. Reader and Writer handle characters (with automatic encoding/decoding). Concrete implementations wrap these for specific sources — files, strings, byte arrays — and decorator classes add buffering, data conversion, and formatting on top.
Java
// ── Java I/O class hierarchy: ────────────────────────────────────────
//
// BYTE STREAMS (work with raw bytes — images, audio, binary data):
//
// InputStream (abstract)
// ├── FileInputStream → reads bytes from a file
// ├── ByteArrayInputStream → reads bytes from a byte array
// ├── FilterInputStream
// │ └── BufferedInputStream → adds buffering
// │ └── DataInputStream → reads primitives (int, double, etc.)
// └── ObjectInputStream → deserialise Java objects
//
// OutputStream (abstract)
// ├── FileOutputStream → writes bytes to a file
// ├── ByteArrayOutputStream → writes bytes to a byte array
// ├── FilterOutputStream
// │ └── BufferedOutputStream → adds buffering
// │ └── DataOutputStream → writes primitives
// │ └── PrintStream → System.out, System.err
// └── ObjectOutputStream → serialise Java objects
//
// CHARACTER STREAMS (work with text — handle encoding automatically):
//
// Reader (abstract)
// ├── InputStreamReader → bridge: byte stream → char stream
// │ └── FileReader → reads chars from a file
// ├── BufferedReader → adds buffering, readLine()
// ├── StringReader → reads chars from a String
// └── CharArrayReader → reads chars from a char array
//
// Writer (abstract)
// ├── OutputStreamWriter → bridge: char stream → byte stream
// │ └── FileWriter → writes chars to a file
// ├── BufferedWriter → adds buffering, newLine()
// ├── PrintWriter → printf, println for text output
// ├── StringWriter → writes chars to a String
// └── CharArrayWriter → writes chars to a char array
// ── Reading text input — three common approaches: ────────────────────
//
// Scanner → easiest, parses tokens and primitives automatically
// BufferedReader → faster for large input, reads full lines
// Console → password masking, direct terminal accessWriting Output
Java provides several methods for writing to standard output. println adds a platform-specific newline. print writes without a newline. printf and format use format specifiers for precise alignment and number formatting. Understanding which method to use for which purpose avoids unnecessary string concatenation and produces cleaner formatted output.
Java
// ── println — most common output method: ────────────────────────────
System.out.println("Hello, World!"); // String
System.out.println(42); // int
System.out.println(3.14159); // double
System.out.println(true); // boolean
System.out.println('Z'); // char
System.out.println(new int[]{1, 2, 3}); // prints [I@hashcode (not useful)
System.out.println(Arrays.toString(new int[]{1, 2, 3})); // [1, 2, 3]
// ── print — no trailing newline: ─────────────────────────────────────
for (int i = 1; i <= 5; i++) {
System.out.print(i);
if (i < 5) System.out.print(", ");
}
System.out.println(); // newline at end
// Output: 1, 2, 3, 4, 5
// ── printf / format — formatted output: ──────────────────────────────
// (see printf entry for full format specifier reference)
System.out.printf("Hello, %s! You are %d years old.%n", "Alice", 30);
System.out.printf("Pi = %.4f%n", Math.PI); // Pi = 3.1416
System.out.printf("%,d%n", 1_000_000); // 1,000,000
// ── String.format — build formatted string (not printed): ────────────
String msg = String.format("Order #%05d: %.2f", 42, 99.9);
System.out.println(msg); // Order #00042: 99.90
// ── PrintWriter — for writing to files or streams: ────────────────────
PrintWriter writer = new PrintWriter(new FileWriter("output.txt"));
writer.println("Line one");
writer.printf("Value: %d%n", 42);
writer.flush();
writer.close();
// ── try-with-resources — auto-close: ─────────────────────────────────
try (PrintWriter pw = new PrintWriter(new FileWriter("out.txt"))) {
pw.println("Written safely");
} // pw.close() called automaticallyChoosing the Right I/O Class
Choosing the right I/O class for the job prevents performance problems and unnecessary complexity. The decision depends on the data type (text vs binary), the source or destination (keyboard, file, network), and the required operations (line reading, token parsing, number parsing, password masking).
Java
// ── Decision guide: ──────────────────────────────────────────────────
//
// Reading text from the KEYBOARD:
// Simple / interactive programs → Scanner(System.in)
// Competitive programming → BufferedReader(InputStreamReader(System.in))
// Password input (no echo) → System.console().readPassword()
//
// Reading text from a FILE:
// Small files, simple parsing → Scanner(new File("file.txt"))
// Large files, line-by-line → BufferedReader(new FileReader("file.txt"))
// Modern Java (Java 11+) → Files.readString() / Files.lines()
//
// Reading BINARY data (images, audio):
// Always use byte streams → FileInputStream, BufferedInputStream
//
// Writing text to console:
// Simple output → System.out.println()
// Formatted output → System.out.printf()
// Error output → System.err.println()
//
// Writing text to a FILE:
// Simple writes → PrintWriter(new FileWriter("file.txt"))
// Large writes (performance) → BufferedWriter(new FileWriter("file.txt"))
// Modern Java (Java 11+) → Files.writeString()
// ── Performance comparison (reading 1 million lines): ─────────────────
//
// BufferedReader.readLine() → fastest (~0.5s)
// Scanner.nextLine() → slower (~2.0s) — parses tokens
// Files.lines() stream → similar to BufferedReader
//
// For keyboard input the difference is negligible.
// For file I/O or competitive programming, BufferedReader is preferred.
// ── Always close I/O resources: ──────────────────────────────────────
// BAD — may leak file handles:
Scanner sc = new Scanner(new File("data.txt"));
// ... use scanner ...
// close() never called if exception is thrown
// GOOD — try-with-resources guarantees close():
try (Scanner sc = new Scanner(new File("data.txt"))) {
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
} // sc.close() called here even if exception thrownRelated Topics in Java Basics
Variables in Java
A variable is just a named box in memory that holds a value. Java is strict about what goes in each box — you tell it the type upfront. Once you get this, the rest of Java clicks into place.
Data Types in Java
Java needs to know exactly what kind of data it's dealing with before it can store or process it. Integers, decimals, characters, true/false — each has its own type. Knowing which to use (and why) makes your programs efficient and bug-free.
Primitive Data Types
Java has eight primitive data types — the most basic building blocks for storing data. Unlike objects, primitives are stored directly in memory, making them fast and efficient. Understanding each type, its size, range, and when to use it is fundamental to writing correct Java programs.
Non-Primitive Data Types
Non-primitive data types — also called reference types — are everything beyond Java's eight primitives. Strings, arrays, classes, interfaces, enums, and records all fall into this category. They're more powerful than primitives, but they work differently in memory, comparison, and nullability. Understanding the distinction is essential for writing correct Java.