Your First Java Program
Java is a compiled and interpreted language. Source code is written in .java files, compiled to bytecode (.class) with javac, then run on any platform by the JVM (Java Virtual Machine) with java. Every Java program needs a class with a main method — the program's entry point. System.out.println() prints a line to the console.
public class Hello { // main method — entry point of every Java program public static void main(String[] args) { // Output: Hello, world! System.out.println("Hello, world!"); } }
# Compile source file to bytecode (.class) $ javac Hello.java # Run the compiled bytecode on the JVM $ java Hello Hello, world!
The class name must match the filename exactly (case-sensitive). Hello.java must contain public class Hello. The JVM runs the main method automatically when you call java Hello.
Typed Variable Declaration
Java is statically typed — every variable must have its type declared. Java has two categories of types: primitive types (int, float, char, boolean, etc.) that hold values directly, and reference types (String, arrays, objects) that hold a reference to an object in memory.
int num = 5; float floatNum = 5.99f; // 'f' suffix required for float literals char letter = 'D'; // single quotes for char boolean bool = true; String site = "quickref.me"; // String is a class, not primitive
Java's 8 Primitive Types
Primitive types are the most basic data types. They are not objects, they hold values directly, and they have fixed sizes. Java has exactly 8 primitive types. Knowing their sizes and ranges helps you choose the right type and avoid overflow bugs.
| Data Type | Size | Default | Range |
|---|---|---|---|
byte | 1 byte | 0 | -128 to 127 |
short | 2 bytes | 0 | -2^15 to 2^15-1 |
int | 4 bytes | 0 | -2^31 to 2^31-1 |
long | 8 bytes | 0 | -2^63 to 2^63-1 |
float | 4 bytes | 0.0f | ~7 decimal digits |
double | 8 bytes | 0.0d | ~15 decimal digits |
char | 2 bytes | \u0000 | 0 to 65535 (Unicode) |
boolean | N/A | false | true / false |
Java's char is 2 bytes (UTF-16) unlike C/C++ where it is 1 byte. long literals need an L suffix: long x = 123456789L;
String Basics
In Java, String is a class (not a primitive). Strings are immutable — once created, their content cannot be changed. Concatenate with +. Full coverage in Module 2.
String first = "John"; String last = "Doe"; String name = first + " " + last; // concatenation with + System.out.println(name); // => John Doe
Enhanced for Loop Over a String
String.toCharArray() converts a string to a char[] array so you can loop over each character. Full loop coverage in Module 5.
String word = "QuickRef"; for (char c : word.toCharArray()) { System.out.print(c + "-"); } // Outputs: Q-u-i-c-k-R-e-f-
Fixed-size Typed Collections
Arrays in Java are objects that hold a fixed number of elements of the same type. The size is set at creation and cannot change. Full coverage in Module 3.
char[] chars = new char[10]; // empty array of 10 chars chars[0] = 'a'; chars[1] = 'b'; String[] letters = {"A", "B", "C"}; // array literal int[] mylist = {100, 200}; boolean[] answers = {true, false};
Classic Temp Variable Swap
To swap two variables you need a temporary variable to hold one value while you overwrite it. This is the universal approach that works in any language.
int a = 1; int b = 2; System.out.println(a + " " + b); // => 1 2 int temp = a; // save a a = b; // a gets b's value b = temp; // b gets saved a System.out.println(a + " " + b); // => 2 1
Converting Between Types
Java has two kinds of casting: Widening (automatic) — converting a smaller type to a larger type (no data loss). Narrowing (explicit) — converting a larger type to smaller; must be done manually with a cast operator (type) and may lose precision. Parsing and converting between strings and numbers requires wrapper class methods.
// Widening (automatic) — byte < short < int < long < float < double int i = 10; long l = i; // int → long, automatic, no data loss // l = 10 // Narrowing (explicit) — must use cast operator double d = 10.02; long l = (long) d; // double → long, truncates decimal // l = 10 (0.02 is lost) // Number ↔ String conversion String.valueOf(10); // int → String => "10" Integer.parseInt("10"); // String → int => 10 Double.parseDouble("10"); // String → double => 10.0
if / else if / else
Java uses curly-brace blocks and else if (two words) for multi-branch conditions. Full coverage with switch and ternary in Module 4.
int j = 10; if (j == 10) { System.out.println("I get printed"); // runs } else if (j > 10) { System.out.println("I don't"); } else { System.out.println("I also don't"); }
Reading from the Keyboard
Java uses the Scanner class from java.util to read user input. Create a Scanner wrapping System.in (keyboard). Use nextLine() for strings and nextInt(), nextDouble() etc. for numbers.
import java.util.Scanner; // import first Scanner in = new Scanner(System.in); String str = in.nextLine(); // read a full line System.out.println(str); int num = in.nextInt(); // read an integer System.out.println(num);
After calling nextInt(), the newline character remains in the buffer. Call in.nextLine() once immediately after to consume it before reading the next full line with nextLine().
Three Ways to Create a String
String literals are cached in the String Pool — using the same literal twice gives you the same object. Using new String() always creates a brand-new object in heap memory, bypassing the pool. String.valueOf() converts any primitive or object to its string representation.
String str1 = "value"; // literal — uses String Pool String str2 = new String("value"); // new object on heap String str3 = String.valueOf(123); // number → string "123"
How + Works with Strings
Java evaluates + left to right. When one operand is a String, the other is converted to String. When both are numbers, arithmetic is performed first. Understanding evaluation order prevents subtle bugs.
String s = 3 + "str" + 3; // => "3str3" (3+"str" → "3str", +"3" → "3str3") s = 3 + 3 + "str"; // => "6str" (3+3=6, 6+"str" → "6str") s = "3" + 3 + "str"; // => "33str" ("3"+3 → "33", +"str" → "33str") s = "3" + "3" + "23"; // => "3323" (all strings, concat) s = "" + 3 + 3 + "23";// => "3323" (""+3 → "3", +"3" → "33", +"23") s = 3 + 3 + 23; // => 29 (no String — pure arithmetic)
Mutable String Buffer
Because String is immutable, concatenating many strings with + in a loop creates many temporary objects and is slow. StringBuilder is a mutable character buffer — it modifies the same object in place. Use it whenever you are building strings dynamically. Key methods: append(), delete(start, end), insert(offset, str), toString().
// Initial capacity 10 (auto-grows as needed) StringBuilder sb = new StringBuilder(10); // [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ] indices 0-9 sb.append("QuickRef"); // [Q][u][i][c][k][R][e][f][ ][ ] sb.delete(5, 9); // delete chars at index 5 to 8 // [Q][u][i][c][k][ ][ ][ ][ ][ ] sb.insert(0, "My "); // insert "My " at index 0 // [M][y][ ][Q][u][i][c][k][ ][ ] sb.append("!"); // [M][y][ ][Q][u][i][c][k][!][ ] System.out.println(sb.toString()); // => "My Quick!"
Use StringBuilder in loops and when building strings from many parts. Use StringBuffer (same API) when you need thread-safety. For simple single-step concatenation, the + operator is fine.
Never Compare Strings with ==
== compares object references (memory addresses), not content. Two String objects can have the same content but different addresses, so == returns false. Always use .equals() for content comparison. Use .equalsIgnoreCase() for case-insensitive comparison.
String s1 = new String("QuickRef"); String s2 = new String("QuickRef"); s1 == s2 // false — different objects in memory s1.equals(s2) // true — same content "AB".equalsIgnoreCase("ab") // true — case-insensitive match
Transforming Strings
Because strings are immutable, these methods all return a new String — they never modify the original. Assign the result to capture it.
String str = "Abcd"; str.toUpperCase(); // => "ABCD" str.toLowerCase(); // => "abcd" str.concat("#"); // => "Abcd#" str.replace("b", "-"); // => "A-cd" " abc ".trim(); // => "abc" (removes leading/trailing spaces) "ab".toCharArray(); // => {'a', 'b'} (String → char[])
Querying String Properties
These methods let you inspect a String without modifying it — finding characters, lengths, substrings, and checking for patterns.
String str = "abcd"; str.charAt(2); // => 'c' (char at index 2) str.indexOf("a"); // => 0 (first position of "a") str.indexOf("z"); // => -1 (not found) str.length(); // => 4 str.toString(); // => "abcd" (returns itself) str.substring(2); // => "cd" (from index 2 to end) str.substring(2, 3); // => "c" (index 2 inclusive, 3 exclusive) str.contains("c"); // => true str.endsWith("d"); // => true str.startsWith("a"); // => true str.isEmpty(); // => false
Strings Cannot Be Changed After Creation
Once a String object is created in Java, its content cannot be modified. Methods that appear to modify a String (like concat()) actually return a brand-new String object. If you don't capture the return value, the modification is lost.
// WRONG — return value not captured, str unchanged String str = "hello"; str.concat("world"); System.out.println(str); // => "hello" (unchanged!) // CORRECT — capture the new String returned by concat() String str2 = "hello"; String result = str2.concat("world"); System.out.println(result); // => "helloworld"
Four Ways to Declare an Array
Java arrays have a fixed size set at creation. You can declare with just a reference, use an array literal, use new int[]{...}, or create an empty array with new int[size] and fill elements later. Accessing an out-of-bounds index throws ArrayIndexOutOfBoundsException.
int[] a1; // reference only, not yet allocated int[] a2 = {1, 2, 3}; // array literal int[] a3 = new int[]{1, 2, 3}; // new keyword with values int[] a4 = new int[3]; // empty array, size 3 (default 0) a4[0] = 1; a4[1] = 2; a4[2] = 3;
Index-based Access
Elements are accessed and modified using zero-based index notation arr[i]. The .length property (not a method — no parentheses) returns the array's fixed size.
int[] a = {1, 2, 3}; System.out.println(a[0]); // => 1 a[0] = 9; // modify element at index 0 System.out.println(a[0]); // => 9 System.out.println(a.length); // => 3 (property, no parentheses)
Index Loop vs Enhanced For Loop
Use the traditional indexed for loop when you need the index (e.g., to modify elements). Use the enhanced for-each loop when you only need to read elements — it is cleaner and less error-prone.
int[] arr = {1, 2, 3}; for (int i = 0; i < arr.length; i++) { arr[i] = arr[i] * 2; // modify using index System.out.print(arr[i] + " "); } // Outputs: 2 4 6
String[] arr = {"a", "b", "c"}; for (int a : arr) { System.out.print(a + " "); } // Outputs: a b c
Arrays of Arrays
Java's 2D arrays are arrays of arrays — rows can have different lengths (jagged arrays). Access elements with matrix[row][col]. Use nested loops to traverse. Arrays.deepToString() gives a readable string representation of a 2D array.
import java.util.Arrays; int[][] matrix = { {1, 2, 3}, {4, 5} }; // jagged — row 1 has 2 elements int x = matrix[1][0]; // => 4 (row 1, col 0) // Pretty-print 2D array Arrays.deepToString(matrix); // => "[[1, 2, 3], [4, 5]]" // Nested loop to traverse all elements for (int i = 0; i < matrix.length; ++i) { for (int j = 0; j < matrix[i].length; ++j) { System.out.println(matrix[i][j]); } } // Outputs: 1 2 3 4 5
Arrays.sort() and Arrays.toString()
Arrays.sort(arr) sorts an array in-place in ascending order using a dual-pivot Quicksort. Arrays.toString(arr) returns a human-readable string representation of a 1D array (use Arrays.deepToString() for 2D).
import java.util.Arrays; char[] chars = {'b', 'a', 'c'}; Arrays.sort(chars); // sorts in-place Arrays.toString(chars); // => "[a, b, c]"
Complete Operator List
Java has a rich set of operators. They fall into categories: arithmetic, assignment, comparison (relational), logical, bitwise, ternary, and type-comparison (instanceof).
Multi-branch Conditional
Java conditionals use curly-brace blocks. The else if keyword adds extra conditions (evaluated top to bottom — only the first matching branch runs). else is the catch-all fallback.
int k = 15; if (k > 20) { System.out.println(1); } else if (k > 10) { System.out.println(2); // runs — 15 > 10 } else { System.out.println(3); }
Multi-way Branching on a Value
Switch compares a value against multiple case labels. In Java, switch works with int, char, String, and enum types. Always end each case with break to prevent fall-through. default runs when no case matches.
int month = 3; String str; switch (month) { case 1: str = "January"; break; case 2: str = "February"; break; case 3: str = "March"; // matches month==3 break; default: str = "Some other month"; break; } // Outputs: Result March System.out.println("Result " + str);
One-line Conditional Expression
The ternary operator condition ? value_if_true : value_if_false is a compact if-else that evaluates to a value. Use it for simple value selection — avoid nesting ternaries as it reduces readability.
int a = 10; int b = 20; // condition ? value_if_true : value_if_false int max = (a > b) ? a : b; System.out.println(max); // => 20
Counter-Controlled Iteration
The for loop packs initialization, condition, and update into one header line: for (init; condition; update). You can declare multiple variables and update multiple variables in one loop header.
// Standard for loop for (int i = 0; i < 10; i++) { System.out.print(i); } // Outputs: 0123456789 // Two variables — i counts up, j counts down for (int i = 0, j = 0; i < 3; i++, j--) { System.out.print(j + "|" + i + " "); } // Outputs: 0|0 -1|1 -2|2
Iterating Without an Index
The enhanced for loop (for-each) iterates over arrays and any Iterable (List, Set, etc.) without needing an index variable. It is cleaner and avoids off-by-one errors. Use it when you only need to read — you cannot modify the original array through the loop variable.
int[] numbers = {1, 2, 3, 4, 5}; for (int number : numbers) { // "for each number in numbers" System.out.print(number); } // Outputs: 12345
Condition-First Loop
The while loop checks its condition before each iteration. If the condition is false initially, the body never runs. Use when the number of iterations is not known in advance.
int count = 0; while (count < 5) { System.out.print(count); count++; } // Outputs: 01234
Execute-First Loop
The do/while loop runs the body first, then checks the condition. The body always executes at least once even if the condition is immediately false. Useful for input validation loops (show prompt, then check).
int count = 0; do { System.out.print(count); count++; } while (count < 5); // semicolon required! // Outputs: 01234
Skip the Current Iteration
continue jumps to the next iteration immediately, skipping the rest of the current loop body. Useful for filtering out unwanted values.
for (int i = 0; i < 5; i++) { if (i == 3) { continue; // skip printing when i is 3 } System.out.print(i); } // Outputs: 01245 (3 is skipped)
Exit the Loop Entirely
break exits the entire loop immediately — no more iterations. Useful for search patterns (stop when found) or early termination conditions.
for (int i = 0; i < 5; i++) { System.out.print(i); if (i == 3) { break; // exit loop when i reaches 3 } } // Outputs: 0123 (stops after printing 3)
Choosing the Right Collection
Java's Collections Framework provides implementations for common data structures. Key properties to consider: ordering (maintains insertion order?), sorting (automatically sorted?), thread-safety (safe for concurrent access?), duplicates allowed?, and null support.
| Collection | Interface | Ordered | Sorted | Thread Safe | Duplicate | Nullable |
|---|---|---|---|---|---|---|
ArrayList | List | Y | N | N | Y | Y |
Vector | List | Y | N | Y | Y | Y |
LinkedList | List, Deque | Y | N | N | Y | Y |
CopyOnWriteArrayList | List | Y | N | Y | Y | Y |
HashSet | Set | N | N | N | N | One null |
LinkedHashSet | Set | Y | N | N | N | One null |
TreeSet | Set | Y | Y | N | N | N |
HashMap | Map | N | N | N | N (key) | One null key |
HashTable | Map | N | N | Y | N (key) | N (key) |
LinkedHashMap | Map | Y | N | N | N (key) | One null key |
TreeMap | Map | Y | Y | N | N (key) | N (key) |
ConcurrentHashMap | Map | N | N | Y | N (key) | N |
ArrayDeque | Deque | Y | N | N | Y | N |
PriorityQueue | Queue | Y | N | N | Y | N |
ConcurrentLinkedQueue | Queue | Y | N | Y | Y | N |
ArrayBlockingQueue | Queue | Y | N | Y | Y | N |
PriorityBlockingQueue | Queue | Y | N | Y | Y | N |
Dynamic Resizable List
ArrayList is a resizable array backed by a plain array. It maintains insertion order, allows duplicates, and supports random access by index in O(1). Adding/removing at the end is O(1) amortized; removing from the middle is O(n) because it shifts elements.
import java.util.ArrayList; import java.util.List; List<Integer> nums = new ArrayList<>(); // Adding elements nums.add(2); nums.add(5); nums.add(8); // Retrieving by index — O(1) System.out.println(nums.get(0)); // => 2 // Indexed for loop for (int i = 0; i < nums.size(); i++) { System.out.println(nums.get(i)); } // Remove last element (fast) — remove first is VERY slow nums.remove(nums.size() - 1); // Enhanced for loop for (Integer value : nums) { System.out.println(value); }
Key-Value Store
HashMap stores key-value pairs. Keys must be unique — adding a key that already exists overwrites the old value. Retrieval, insertion, and deletion are O(1) average. Order of iteration is not guaranteed. Use forEach() with a lambda for clean iteration.
import java.util.HashMap; import java.util.Map; Map<Integer, String> m = new HashMap<>(); m.put(5, "Five"); m.put(8, "Eight"); m.put(6, "Six"); m.put(4, "Four"); m.put(2, "Two"); // Retrieve by key — O(1) average System.out.println(m.get(6)); // => "Six" // Lambda forEach — key and value together m.forEach((key, value) -> { String msg = key + ": " + value; System.out.println(msg); });
Unique Elements, No Order
HashSet stores only unique elements — adding a duplicate is silently ignored. Backed by a HashMap internally. Offers O(1) add, remove, and contains operations. Iteration order is not defined. Allows one null value.
import java.util.HashSet; import java.util.Set; Set<String> set = new HashSet<>(); if (set.isEmpty()) { System.out.println("Empty!"); } set.add("dog"); set.add("cat"); set.add("mouse"); set.add("snake"); set.add("bear"); if (set.contains("cat")) { System.out.println("Contains cat"); } set.remove("cat"); // removes "cat" from the set for (String element : set) { System.out.println(element); // order not guaranteed }
Double-Ended Queue
ArrayDeque is a resizable double-ended queue — you can efficiently add and remove from both ends. It can be used as a Stack (LIFO: push/pop from the front) or a Queue (FIFO: add to back, remove from front). Does not allow null elements. Generally faster than Stack and LinkedList for these use cases.
import java.util.ArrayDeque; import java.util.Deque; Deque<String> a = new ArrayDeque<>(); a.add("Dog"); // add to back (tail) a.addFirst("Cat"); // add to front (head) a.addLast("Horse"); // add to back (tail) // Deque is now: [Cat, Dog, Horse] System.out.println(a); // => [Cat, Dog, Horse] // peek() — view head without removing System.out.println(a.peek()); // => "Cat" // pop() — remove and return head System.out.println(a.pop()); // => "Cat" // Deque is now: [Dog, Horse]
Controlling Visibility
Access modifiers control which classes can access a class, method, or field. They are the foundation of encapsulation in OOP — hiding internals and exposing only what is necessary. public is the widest scope; private is the narrowest.
| Modifier | Same Class | Same Package | Subclass | World (any class) |
|---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
no modifier | Y | Y | N | N |
private | Y | N | N | N |
Default best practice: make fields private, expose them through public getter/setter methods. Use protected for members intended for subclass access. Reserve public for the intended API surface.
Pattern Matching in Strings
Java regex is available through java.util.regex. String.replaceAll(regex, replacement) replaces all matches. String.split(regex) splits on pattern. Use Pattern.quote(str) to treat a string as a literal pattern (escaping special regex characters).
import java.util.regex.Pattern; String text = "I am learning Java"; // Remove all whitespace (\s+ matches one or more whitespace chars) text.replaceAll("\\s+", ""); // => "IamlearningJava" // Split on pipe | (must escape in regex, or use Pattern.quote) text.split("\\|"); text.split(Pattern.quote("|")); // safer — treats | as literal
Three Comment Styles in Java
Java supports single-line, multi-line, and documentation (Javadoc) comments. Javadoc comments (/** ... */) are special — tools like javadoc parse them to auto-generate API documentation HTML pages.
// Single-line comment /* Multi-line comment — can span many lines */ /** * Javadoc comment — used to generate API documentation. * This * is * documentation * comment */
Reserved Words You Cannot Use as Identifiers
Java has 51 reserved keywords. These cannot be used as variable names, class names, or method names. Understanding what each does is essential for reading and writing Java code correctly.
java.lang.Math — Built-in Math Functions
The Math class is in java.lang and is imported automatically — no import needed. All methods are static, so call them as Math.methodName(). Arguments and return values are generally double.
| Method | Description |
|---|---|
Math.max(a, b) | Maximum of a and b |
Math.min(a, b) | Minimum of a and b |
Math.abs(a) | Absolute value of a |
Math.sqrt(a) | Square root of a |
Math.pow(a, b) | a raised to the power of b |
Math.round(a) | Closest integer (returns long for double) |
Math.sin(ang) | Sine of angle (in radians) |
Math.cos(ang) | Cosine of angle (in radians) |
Math.tan(ang) | Tangent of angle (in radians) |
Math.asin(ang) | Inverse sine of angle |
Math.log(a) | Natural logarithm of a (base e) |
Math.toDegrees(rad) | Convert radians to degrees |
Math.toRadians(deg) | Convert degrees to radians |
Handling Runtime Errors Gracefully
Java uses checked exceptions (must be declared or caught) and unchecked exceptions (runtime errors). The try block contains code that might throw. catch handles specific exception types. finally always runs — used for cleanup (closing files, releasing connections) regardless of whether an exception occurred.
try { // Code that might throw an exception String s = null; s.length(); // throws NullPointerException } catch (Exception e) { e.printStackTrace(); // print stack trace to stderr } finally { System.out.println("always printed"); // Runs whether exception happened or not // Perfect for closing resources }
Catch specific exception types before general ones: catch(NullPointerException e) before catch(Exception e). For resource management (files, database connections), prefer Java 7+ try-with-resources: try (Resource r = new Resource()) { ... } — it calls close() automatically.