Open-source libraries used

1 libraries are bundled into this tool's code.

Java Cheatsheet — Quick Reference

A concise cheatsheet for Java 17+ syntax, OOP, collections, and the most commonly used standard library APIs, covering roughly 80% of daily scenarios.

J

Java Java 17 (LTS)

JDK (OpenJDK / Oracle) · OOP, generics, functional · static, strong, nominal typing

Recommended Learning Path

Start by getting Hello World and the build environment running (javac/java or Maven) → get comfortable with variables, types, and control flow → process data with collections and the Stream API → dive into OOP (inheritance, interfaces, generics) → master exception handling and lambda → finally look up files, network, time, and build/debug on demand. The FAQ section is best revisited to avoid pitfalls.

1.Hello World and Build Environment

Write, compile, and run a Java program from scratch: the main method, javac/java, packages, and build tools.

Minimal Program

Every Java program starts with the main method: public static void main(String[] args). System.out.println prints a line.

1
2
3
4
5
6
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
// File name must match the public class: Hello.java

Compile and Run

javac compiles .java into .class bytecode, and java launches the JVM to run it. Use javap to disassemble and inspect bytecode.

1
2
3
4
// $ javac Hello.java # generates Hello.class
// $ java Hello # run (no .class suffix)
// $ javap -c Hello # decompile to view bytecode
// $ java -version # check JDK version

Command-Line Arguments

main(String[] args) receives command-line arguments; args[0] is the first user argument. Check args.length for the count.

1
2
3
4
5
6
7
8
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("No args");
return;
}
System.out.println("Hello, " + args[0] + "!");
}
// $ java Hello Nick

Exit Code

System.exit(code) terminates the JVM and returns an exit code: 0 for success, non-zero for failure. It force-exits even if non-main threads are still running.

1
2
3
4
5
if (error) {
System.err.println("failed");
System.exit(1); // non-zero means failure
}
// Normal exit without calling also returns 0

Package Declaration

package declares the package the type belongs to, mapped to a directory layout, and is used in fully qualified names. Omitting it puts the type in the unnamed package.

1
2
3
4
5
package com.example.app;
// Place the file under the com/example/app/ directory
public class Main {
// Fully qualified name: com.example.app.Main
}

import Statement

import brings in classes or static members from other packages so you can skip fully qualified names. java.lang is imported by default.

1
2
3
4
5
import java.util.List;
import java.util.ArrayList;
import static java.lang.Math.PI; // static import
import java.util.*; // wildcard import (avoid overusing)
List<String> list = new ArrayList<>();

Class and File Name

The public class name must match the file name. A single .java file may contain multiple non-public classes, but at most one public class.

1
2
3
4
5
// In FileName.java:
public class FileName { // must match the file name
public static void main(String[] args) { }
}
class Helper { } // non-public, may share a file

Maven / Gradle

Larger projects use build tools: Maven (pom.xml) and Gradle (build.gradle) manage dependencies, compilation, testing, and packaging.

1
2
3
4
5
6
7
8
// pom.xml (Maven)
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
// $ mvn compile / mvn test / mvn package
// $ gradle build / gradle run

2.Variables and Constants

Variable declarations, type inference, final constants, scope, and type conversion.

Variable Declaration

A declaration = type + name + optional initializer. Local variables must be initialized before use; fields have default values.

1
2
3
4
5
6
int count = 42;
double price = 19.99;
String name = "Nick";
boolean ok = true;
char c = 'A';
// Declare multiple at once: int a = 1, b = 2;

var Type Inference

var (Java 10+) lets the compiler infer the type. It is limited to local variables. Use explicit types when readability suffers.

1
2
3
4
5
var count = 42; // int
var name = "Nick"; // String
var list = new ArrayList<String>(); // ArrayList<String>
// var cannot be used for fields, method parameters, or return types
// var only infers; runtime type stays the same

final Constants

A final local variable can be assigned only once; a final field must be initialized at declaration or in a constructor; static final is a constant.

1
2
3
4
5
6
final int MAX = 100; // read-only local variable
class Config {
final String name = "app"; // final field
static final double PI = 3.14; // constant
}
// Reassigning a final variable causes a compile error

Scope

Block scope: variables declared inside a block are visible only within it. Nested blocks can shadow an outer variable with the same name (not recommended).

1
2
3
4
5
6
7
int x = 1;
{
int y = 2; // visible only inside this block
System.out.println(x + y);
}
// y is not accessible outside the block
// System.out.println(y); // compile error

Naming Conventions

Class names use UpperCamelCase, variables and methods use lowerCamelCase, constants use UPPER_SNAKE_CASE, and package names are all lowercase.

1
2
3
4
5
class UserProfile { }
int userId = 1;
String getUserName() { return "nick"; }
static final int MAX_RETRY = 3;
package com.example.app;

Type Conversion

Implicit conversion widens (int to long to double). Explicit casts may lose precision (truncation).

1
2
3
4
5
6
int i = 42;
long l = i; // implicit: int → long
int back = (int) l; // explicit cast
long big = 3000000000L;
int overflow = (int) big; // overflow: truncated to negative
String s = String.valueOf(i); // number → string

null and NPE

null is the empty value of a reference type. Calling a method or accessing a field on null throws NullPointerException (NPE). Use Objects utilities to handle null safely.

1
2
3
4
5
6
7
8
String s = null;
// s.length() // NPE!
if (s != null) {
System.out.println(s.length());
}
// Java 8+ uses Optional to express nullability:
Optional<String> opt = Optional.ofNullable(s);
opt.ifPresent(System.out::println);

Literals

Integer literals can use _ separators for readability, 0x for hex, 0b for binary. Append L to long literals and f to float literals.

1
2
3
4
5
6
7
int million = 1_000_000; // underscore separator
int hex = 0xFF; // 255
int bin = 0b1101; // 13
long big = 42L;
float f = 3.14f;
double d = 3.14;
char ch = '\u0041'; // 'A'

3.Data Types

Primitives and wrappers, strings, arrays, enums, generics, and record.

Primitive Types

Java has 8 primitive types: byte/short/int/long (integers), float/double (floating point), char, and boolean. Values are stored directly.

1
2
3
4
5
6
7
8
byte b = 1;
short s = 2;
int i = 42;
long l = 42L;
float f = 3.14f;
double d = 3.14;
char c = 'A';
boolean flag = true;

Wrapper Classes

Wrapper classes such as Integer, Double, and Boolean box primitives into objects. Autoboxing/unboxing happens implicitly when needed.

1
2
3
4
5
Integer num = 42; // autoboxing int → Integer
int value = num; // autounboxing
Integer parsed = Integer.parseInt("42");
// Boxing comparison note:
// new Integer(42) == new Integer(42) is false

String

String is an immutable sequence of characters. equals compares content; == compares references. Every modification produces a new object.

1
2
3
4
5
String s = "hello";
String t = new String("hello");
boolean eq = s.equals(t); // true (by content)
boolean sameRef = s == t; // false (by reference)
String upper = s.toUpperCase(); // "HELLO"

Arrays

Arrays have a fixed length, accessed by index, with a length property. Declare int[], create with new int[n], or initialize with {}.

1
2
3
4
5
6
int[] arr = new int[5]; // default 0
int[] nums = { 1, 2, 3 };
int first = nums[0];
nums[2] = 99;
int len = nums.length; // 3 (field, not method)
int[][] grid = new int[3][3]; // two-dimensional

Enum

enum defines a set of constants, which may carry fields and methods. switch can use enum values directly, with compile-time type safety.

1
2
3
4
5
6
enum Color { RED, GREEN, BLUE }
Color c = Color.RED;
String name = c.name(); // "RED"
int ord = c.ordinal(); // 0 (declaration order)
Color parsed = Color.valueOf("BLUE");
// enum can carry constructors and fields: enum Status { OK(200), ERR(500); ... }

Generics

Generics parameterize types: List<T>, Map<K,V>. The compiler enforces type checks; at runtime types are erased (type erasure).

1
2
3
4
5
6
7
List<String> names = new ArrayList<>();
Map<String, Integer> ages = new HashMap<>();
// Generic method:
static <T> T first(List<T> list) {
return list.get(0);
}
// Wildcard: List<? extends Number>

record Type

record (preview in Java 14, finalized in 16) declares an immutable data carrier in one line: the constructor, equals, hashCode, and toString are auto-generated.

1
2
3
4
5
record Person(String name, int age) { }
var p = new Person("Nick", 30);
String n = p.name(); // accessor (not getName)
String s = p.toString(); // Person[name=Nick, age=30]
// Immutable: fields are implicitly final

Autoboxing Pitfall

Boxing/unboxing hides pitfalls in == and arithmetic: Integer values from -128 to 127 are cached, so == outside that range may be false.

1
2
3
4
5
Integer a = 100, b = 100;
boolean same = a == b; // true (cache pool)
Integer c = 200, d = 200;
boolean diff = c == d; // false! use equals
boolean ok = c.equals(d); // true

Object Root Class

All classes implicitly extend Object: toString, equals, hashCode, clone, finalize. Whenever you override equals, also override hashCode.

1
2
3
4
5
6
7
8
9
10
11
class User {
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User u)) return false;
return name.equals(u.name);
}
@Override
public int hashCode() { return name.hashCode(); }
}

4.References and Memory

Reference semantics, null, object copies, heap/stack, GC, and the string pool.

Reference Semantics

Java has no pointers; object variables hold references (pointer-like). Assignment shares the same object, so mutations are visible to all aliases.

1
2
3
4
5
StringBuilder a = new StringBuilder("a");
StringBuilder b = a; // reference copy, shared object
b.append("b");
System.out.println(a); // "ab" (a changed too)
// Primitives are value-copied: int x = y do not affect each other

null Reference

null means the reference points to no object. Compare with == / !=; passing null into a method can produce an NPE. Use defensive null checks.

1
2
3
4
5
6
String s = getMaybe();
if (s != null) {
System.out.println(s.length());
}
// Objects.requireNonNull(s, "s must not be null");
// Objects.toString(s, ""); safe conversion

Object Methods

toString describes the object, equals compares content, and hashCode provides a hash. The defaults are reference comparison; override for value semantics.

1
2
3
4
5
6
7
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public String toString() { return "(" + x + "," + y + ")"; }
}
System.out.println(new Point(1, 2)); // "(1,2)"

Heap and Stack

Objects are allocated on the heap (managed by GC); local variables and references live on the stack. The reference of a new object lives on the stack; the body lives on the heap.

1
2
3
4
void method() {
int x = 1; // stack: primitive
Person p = new Person(); // p reference on stack, object on heap
} // method ends, stack is freed, object awaits GC

GC Garbage Collection

The JVM automatically reclaims unreachable objects, so you never call free. System.gc() is only a hint and is not guaranteed to run immediately.

1
2
3
4
5
// Objects with no more references can be reclaimed
Person p = new Person();
p = null; // original object loses reference, awaits GC
// System.gc(); // only a hint; don't call in production
// Generational GC: young / old generation

Copy Semantics

Assignment of arrays or objects shares the reference. For an independent copy use clone, Arrays.copyOf, or manual copying. Deep copies must be done layer by layer.

1
2
3
4
5
6
int[] a = { 1, 2, 3 };
int[] shallow = a; // shared
int[] copy = a.clone(); // independent copy
copy[0] = 99;
System.out.println(a[0]); // 1 (copy is independent)
// clone of an object array is shallow; elements are still shared

String Constant Pool

Literal strings are cached in the constant pool: identical literals share the same object. new String(...) creates a new object outside the pool. Use intern() to put it in.

1
2
3
4
5
6
String a = "hello";
String b = "hello";
boolean same = a == b; // true (same object in the pool)
String c = new String("hello");
boolean diff = a == c; // false (new object on heap)
String d = c.intern(); // after interning, a == d is true

finalize and Cleaner

finalize runs before GC but its timing is not guaranteed (deprecated). To release external resources use AutoCloseable with try-with-resources.

1
2
3
4
5
6
7
@Override
protected void finalize() { } // deprecated, don't rely on it
// Correct approach:
class Conn implements AutoCloseable {
public void close() { /* release resource */ }
}
try (var c = new Conn()) { } // auto close

5.Control Flow

if/else, switch, for/while loops, break/continue, and the ternary operator.

if / else

if/else branches on a condition. The condition must be a boolean expression. Chain else-if for multiple branches.

1
2
3
4
5
6
7
8
9
10
int score = 85;
String grade;
if (score >= 90) {
grade = "A";
} else if (score >= 60) {
grade = "B";
} else {
grade = "F";
}
System.out.println(grade);

switch Expression

switch (Java 14+) can use -> to return a value and merge cases. The classic switch statement also accepts arrow syntax.

1
2
3
4
5
6
7
8
int day = 3;
String name = switch (day) {
case 1 -> "Monday";
case 2, 3 -> "Tue/Wed"; // multiple values merged
default -> "other";
};
// Traditional switch also supports -> :
// switch (day) { case 1 -> System.out.println("Mon"); }

for Loop

Classic for: initializer, condition, step. Use it when you need indices or reverse iteration. Prefer enhanced for to iterate collections.

1
2
3
4
5
6
for (int i = 0; i < 10; i++) {
System.out.println(i); // 0..9
}
for (int i = arr.length - 1; i >= 0; i--) {
System.out.println(arr[i]); // reverse order
}

Enhanced for

for-each iterates arrays and Iterable collections without indices. Do not structurally modify the collection (it throws an exception).

1
2
3
4
5
for (String name : names) {
System.out.println(name);
}
for (int n : nums) { /* read-only iteration */ }
// When you need the index or removal, use Iterator or a regular for

while / do-while

while checks the condition before each iteration; do-while runs at least once. Use while when the iteration count is unknown (reading streams, polling).

1
2
3
4
5
6
int i = 0;
while (i < 5) { i++; } // check first
int x = 0;
do {
x++; // runs at least once
} while (x < 3);

break and continue

continue skips the current iteration, break exits the loop, and labelled break/continue controls nested loops.

1
2
3
4
5
6
7
8
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) continue; // skip this iteration
if (i == 2) break outer; // break all loops
}
}
// Label scope: outer: tags the outer loop

Ternary Operator

cond ? a : b expresses an if/else in one line. The two branches must have compatible types. Avoid nested ternaries: they hurt readability.

1
2
3
4
5
int age = 20;
String type = age >= 18 ? "adult" : "minor";
// Equivalent to:
String t2;
if (age >= 18) t2 = "adult"; else t2 = "minor";

return and Early Return

return ends the method and returns a value (use plain return; for void). Early returns keep methods readable (guard clauses).

1
2
3
4
5
6
boolean isValid(String s) {
if (s == null || s.isEmpty()) return false;
if (s.length() > 10) return false;
return true;
}
// Early return avoids deep nesting

6.Methods and Lambda

Method signatures, parameters, overloading, recursion, Lambda, and functional programming.

Method Definition

A method = access modifier + return type + name + parameter list + body. void means no return value.

1
2
3
4
5
6
7
public int add(int a, int b) {
return a + b;
}
public void say(String msg) {
System.out.println(msg); // void has no return
}
static int timesTwo(int x) { return x * 2; }

Parameter Passing

Java is always pass-by-value: primitives pass a copy; reference types pass a copy of the reference (mutating members affects the caller, reassigning the parameter does not).

1
2
3
4
5
6
void set(int x) { x = 99; } // primitive passed by copy
void mutate(List<String> l) { l.add("x"); }
int n = 1;
set(n); // n is still 1
var list = new ArrayList<String>();
mutate(list); // "x" is added to list

Varargs

... (varargs) accepts any number of arguments of the same type; it is essentially an array. It must be last and you can have only one.

1
2
3
4
5
6
7
8
int sum(int... nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
int s = sum(1, 2, 3); // 6
int s2 = sum(); // 0 (empty array)
int s3 = sum(new int[]{1,2}); // can also pass an array

Method Overloading

Methods with the same name but different parameter lists (count or types) are overloads. The compiler picks the best match by argument types. Return type does not participate.

1
2
3
4
5
int parse(String s) { return Integer.parseInt(s); }
int parse(int x) { return x; } // different parameter type
int parse(String s, int radix) { // different parameter count
return Integer.parseInt(s, radix);
}

Recursion

A method that calls itself is recursion. Without a base case the call stack overflows.

1
2
3
4
5
int factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1);
}
// Watch for stack overflow with deep recursion: each call uses a stack frame

Lambda Expression

A Lambda is (params) -> expression and is an instance of a functional interface. You can omit type inference and the parentheses for a single parameter.

1
2
3
4
5
6
7
8
Runnable r = () -> System.out.println("run");
BiFunction<Integer, Integer, Integer> add =
(a, b) -> a + b;
// Single parameter can drop parentheses: x -> x * 2
// Multi-line body uses braces + return:
Comparator<Integer> c = (x, y) -> {
return x.compareTo(y);
};

Method Reference

:: method references are shorthand for Lambdas: ClassName::staticMethod, obj::instanceMethod, ClassName::new.

1
2
3
4
5
6
7
List<String> names = List.of("b", "a");
names.stream().sorted(String::compareTo);
// Class::staticMethod
names.forEach(System.out::println);
// obj::instanceMethod
names.forEach(s -> System.out.println(s));
// Class::new constructor reference: Supplier<Person> s = Person::new;

Functional Interface

An interface with exactly one abstract method can be used as a Lambda target. Common ones: Runnable, Function, Consumer, Predicate, Supplier.

1
2
3
4
5
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> len = String::length;
Consumer<String> print = System.out::println;
Supplier<Double> rand = Math::random;
// @FunctionalInterface annotation declares a functional interface

Stream API

Stream chains process collections: filter, map, collect. Streams are lazy and do not mutate the source.

1
2
3
4
5
6
7
List<Integer> nums = List.of(1, 2, 3, 4);
var evens = nums.stream()
.filter(n -> n % 2 == 0) // [2, 4]
.map(n -> n * 10) // [20, 40]
.toList(); // Java 16+
// Aggregations: sum/max/count/anyMatch/allMatch
int sum = nums.stream().mapToInt(Integer::intValue).sum();

7.Strings

String literals, concatenation, StringBuilder, common methods, and formatting.

String Literals

Double-quoted strings, \n escape sequences, and text blocks (Java 15+) wrapped in triple double quotes preserve multi-line formatting.

1
2
3
4
5
6
7
8
String s = "Line\nTab\tindent";
String path = "C:\\Program Files\\";
// Text blocks (Java 15+): three double quotes wrap multi-line text, auto-indent
// String html = """
// <div>
// <p>Hello</p>
// </div>
// """.stripIndent();

Concatenation

+ concatenates strings; concatenating with another type implicitly converts it to a string. It is fine for a few concatenations; use StringBuilder inside loops.

1
2
3
4
5
String a = "foo" + "bar"; // "foobar"
int age = 30;
String msg = "Age: " + age; // auto-converts to string
String joined = String.join(", ", "a", "b", "c");
// For loops use StringBuilder instead (see memory section)

Common Methods

Common methods: length, substring, indexOf, replace, split, trim, and case conversion.

1
2
3
4
5
6
7
String s = " Hello, World ";
int len = s.length(); // 14
char c = s.charAt(0); // ' '
String sub = s.substring(7, 12); // "World"
boolean has = s.contains("World"); // true
String rep = s.replace("World", "Java");
String[] words = s.trim().split(",");

StringBuilder

For heavy concatenation use StringBuilder (not thread-safe) or StringBuffer (thread-safe): append then toString.

1
2
3
4
5
6
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i).append(",");
}
String result = sb.toString(); // convert once at the end
// Mutable: delete/insert via sb.deleteCharAt / sb.insert

Formatting

String.format: %d for integers, %s for strings, %.2f for decimals, %n for line breaks. Width and flags are supported.

1
2
3
4
5
double d = 1234.567;
String s = String.format("%.2f", d); // "1234.57"
String t = String.format("%d%%", 50); // "50%"
String pad = String.format("%5d", 42); // " 42"
String n = String.format("%,d", 1234567); // "1,234,567"

Regex Match

String.matches matches the whole string, replaceAll replaces via regex, split splits via regex. Remember to escape regex meta-characters.

1
2
3
boolean m = "123-456".matches("\\d{3}-\\d{3}"); // true
String masked = "123456".replaceAll("(\\d{3})(\\d+)", "$1***");
String[] parts = "a,b;c".split("[,;]"); // [a, b, c]

Character Handling

charAt retrieves a character; Character.isDigit/isLetter/isWhitespace classify characters. Strings are immutable, but you can iterate their characters.

1
2
3
4
5
6
String s = "Java 17";
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isUpperCase(c)) { }
}
// Also: for (char c : s.toCharArray())

String Conversion

Integer.parseInt parses a string into a number; valueOf/toString convert a number into a string. Parsing throws NumberFormatException on failure.

1
2
3
4
5
6
7
int n = Integer.parseInt("42");
long l = Long.parseLong("42");
double d = Double.parseDouble("3.14");
String s = String.valueOf(42);
String hex = Integer.toHexString(255); // "ff"
// Safe parsing:
// try { ... } catch (NumberFormatException e) { }

8.Collections and Stream

Common List/Set/Map/Queue implementations, iteration, Stream, and sorting.

List

List is an ordered collection. ArrayList is backed by an array (fast reads); LinkedList is a linked list (fast inserts/removes). List.of creates immutable lists.

1
2
3
4
5
6
7
8
List<String> list = new ArrayList<>();
list.add("a");
list.add(0, "first"); // insert at specific position
String x = list.get(1);
list.remove("a");
int size = list.size();
boolean has = list.contains("b");
var fixed = List.of("a", "b"); // immutable

Set

Set deduplicates. HashSet is unordered with O(1) operations; LinkedHashSet preserves insertion order; TreeSet is sorted. Adding a duplicate returns false.

1
2
3
4
5
6
Set<Integer> set = new HashSet<>();
set.add(1);
boolean added = set.add(1); // false (already present)
boolean has = set.contains(1);
var ordered = new LinkedHashSet<String>(); // preserves insertion order
var sorted = new TreeSet<Integer>(); // ascending order

Map

Map is a key/value mapping. HashMap is O(1); LinkedHashMap preserves insertion order; TreeMap sorts by key. getOrDefault safely handles missing keys.

1
2
3
4
5
6
7
8
Map<String, Integer> map = new HashMap<>();
map.put("Nick", 30);
Integer age = map.get("Nick");
int safe = map.getOrDefault("X", 0);
map.computeIfAbsent("k", k -> 1); // compute if absent
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}

Queue / Deque

Queue is FIFO (offer/poll/peek); Deque is a double-ended queue (addFirst/addLast). ArrayDeque is faster than LinkedList.

1
2
3
4
5
6
7
Queue<Integer> q = new ArrayDeque<>();
q.offer(1); q.offer(2);
int head = q.peek(); // 1 (no removal)
int out = q.poll(); // 1 (removed)
Deque<Integer> d = new ArrayDeque<>();
d.addFirst(1); d.addLast(2);
int first = d.pollFirst();

Iteration and Traversal

Use enhanced for, Iterator, or forEach to iterate a collection. To remove during iteration use Iterator.remove or collect first and remove later.

1
2
3
4
5
6
7
8
for (String s : list) { }
list.forEach(System.out::println);
// Remove during iteration:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) it.remove();
}
// Cannot remove directly in enhanced for (throws exception)

Sorting

List.sort or Collections.sort to sort; Comparator customizes the order; Comparator.comparing enables chaining.

1
2
3
4
5
6
7
List<Integer> nums = new ArrayList<>(List.of(3, 1, 2));
nums.sort(null); // natural ascending
nums.sort(Comparator.reverseOrder()); // descending
// Custom object sorting:
people.sort(Comparator
.comparing(Person::age)
.thenComparing(Person::name));

Stream Chained Operations

filter/map/sorted/distinct/limit form a chain; a terminal operation triggers execution. collect gathers results back into a collection.

1
2
3
4
5
6
7
8
var result = people.stream()
.filter(p -> p.age() >= 18)
.sorted(Comparator.comparing(Person::age).reversed())
.map(Person::name)
.distinct()
.limit(10)
.toList();
// Lazy: without a terminal operation, nothing actually runs

Grouping and Aggregation

Collectors.groupingBy groups by key, partitioningBy splits by a boolean, summarizingInt computes statistics.

1
2
3
4
5
6
7
Map<String, List<Order>> byRegion = orders.stream()
.collect(Collectors.groupingBy(Order::region));
Map<Boolean, List<Integer>> part = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// Statistics: Collectors.summingInt / averagingInt
int total = orders.stream()
.collect(Collectors.summingInt(Order::amount));

Immutable Collections

List.of, Set.of, Map.of create immutable collections. Collections.unmodifiableList wraps a read-only view.

1
2
3
4
5
var fixed = List.of(1, 2, 3); // immutable
// fixed.add(4); // UnsupportedOperationException
var mutable = new ArrayList<>(fixed); // mutable copy
var view = Collections.unmodifiableList(mutable);
// Any modification through the wrapper throws an exception

9.Memory and Performance

GC, weak references, memory leaks, string concatenation performance, and buffers.

String Concatenation Performance

Using + inside a loop repeatedly creates new (immutable) Strings. Use StringBuilder to assemble once for a big performance win.

1
2
3
4
5
6
7
// Slow: loop concatenation creates many intermediate objects
String s = "";
for (int i = 0; i < 1000; i++) s += i;
// Fast: StringBuilder appends in place
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.append(i);
String r = sb.toString();

Weak and Soft References

WeakReference does not prevent GC (good for caches); SoftReference is collected only under memory pressure; PhantomReference fires after collection.

1
2
3
4
5
WeakReference<BigObj> weak = new WeakReference<>(new BigObj());
System.gc();
BigObj obj = weak.get(); // may already be null
// Soft reference: reclaimed only under memory pressure (good for image cache)
SoftReference<Image> soft = new SoftReference<>(image);

Memory Leak

Common leaks: static collections holding objects, unclosed resources, unregistered listeners, ThreadLocal not cleaned up. Avoid long-lived holders around short-lived objects.

1
2
3
4
5
// A static Map that keeps growing = leak
static Map<String, Session> sessions = new HashMap<>();
// Must remove when done: sessions.remove(id)
// Or switch to WeakHashMap<String, Session>
// Close resources when done: try (var c = open()) { }

ThreadLocal

ThreadLocal gives each thread its own copy. In web containers with thread pools, forgetting to remove() leaks state across requests.

1
2
3
4
5
private static final ThreadLocal<SimpleDateFormat> fmt =
ThreadLocal.withInitial(SimpleDateFormat::new);
String d = fmt.get().format(date);
// Important: must call remove() when done, especially in thread pools
// fmt.remove(); otherwise thread reuse causes object leaks

ByteBuffer

ByteBuffer on direct memory (allocateDirect) reduces GC pressure and suits NIO and large transfers. It must be managed manually.

1
2
3
4
5
6
7
ByteBuffer buf = ByteBuffer.allocate(1024); // heap
ByteBuffer direct = ByteBuffer.allocateDirect(1024); // direct memory
buf.putInt(42).putDouble(3.14);
buf.flip(); // switch to read mode
int i = buf.getInt();
double d = buf.getDouble();
buf.clear(); // reuse the buffer

OutOfMemoryError

OOM means memory is exhausted: heap full, metaspace full of classes, or too many direct buffers. Tune JVM flags or find the leak.

1
2
3
4
5
// JVM options:
// $ java -Xms512m -Xmx2g -XX:+HeapDumpOnOutOfMemoryError App
// -Xmx max heap, -Xms initial heap
// Heap dump on overflow: generate .hprof and analyze with tools
// Investigate: unbounded collection growth / static retention / resource leaks

Array Copy

System.arraycopy efficiently copies array ranges. Arrays.copyOf copies and may grow the array. Manual copy loops are slow.

1
2
3
4
5
int[] src = { 1, 2, 3, 4 };
int[] dst = new int[4];
System.arraycopy(src, 1, dst, 0, 2); // [2, 3, 0, 0]
int[] copy = Arrays.copyOf(src, src.length);
int[] grown = Arrays.copyOf(src, 6); // grow, padded with 0

Object Pool Reuse

Frequently creating large objects increases GC pressure. An object pool caches reusable instances but must be thread-safe and track return paths.

1
2
3
4
5
6
7
8
9
10
11
// Simple object pool: avoid frequent new of large objects
final class ConnectionPool {
private final ArrayDeque<Connection> idle = new ArrayDeque<>();
synchronized Connection acquire() {
return idle.isEmpty() ? new Connection() : idle.poll();
}
synchronized void release(Connection c) {
idle.push(c); // return for reuse
}
}
// Larger pools use more memory; weigh against reuse benefit

10.Object-Oriented Programming

Classes, encapsulation, inheritance, polymorphism, abstract classes, interfaces, and access modifiers.

Class and Object

class defines a data type: fields hold state, methods define behavior, constructors initialize, and new creates instances.

1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String greet() { return "Hi, " + name; }
}
var p = new Person("Nick", 30);

Encapsulation

Private fields with public methods control access. Add validation to getters/setters to protect invariants.

1
2
3
4
5
6
7
8
public class Account {
private double balance;
public double getBalance() { return balance; }
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
}

Inheritance

extends inherits from a base class. Java uses single inheritance. super calls into the parent's constructor or methods; a subclass is-a parent.

1
2
3
4
5
6
7
8
9
public class Animal {
public void speak() { System.out.println("..."); }
}
public class Dog extends Animal {
@Override
public void speak() { System.out.println("Woof"); }
}
Animal a = new Dog();
a.speak(); // "Woof" (polymorphism)

Polymorphism

A parent-class or interface reference can point to a subclass instance; virtual methods dispatch on the actual type. Mark overrides with @Override.

1
2
3
4
5
6
7
8
9
10
11
public class Shape {
public double area() { return 0; }
}
public class Circle extends Shape {
private final double r;
public Circle(double r) { this.r = r; }
@Override
public double area() { return Math.PI * r * r; }
}
Shape s = new Circle(2);
double a = s.area(); // 12.57 (by actual type)

Abstract Class

An abstract class cannot be instantiated and may declare abstract methods (subclasses must implement them). Use it as a template base that shares state.

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Shape {
public abstract double area(); // no implementation
public void describe() {
System.out.println("Area: " + area());
}
}
public class Square extends Shape {
private final double side;
public Square(double s) { this.side = s; }
@Override
public double area() { return side * side; }
}

Interface

interface defines a contract: methods are implicitly public abstract. Java 8+ added default and static methods. A class can implement multiple interfaces.

1
2
3
4
5
6
7
8
9
10
11
public interface Logger {
void log(String msg); // abstract method
default void warn(String m) { // default implementation
log("[WARN] " + m);
}
}
public class ConsoleLogger implements Logger {
@Override
public void log(String msg) { System.out.println(msg); }
}
Logger l = new ConsoleLogger(); // program to interface

Access Modifiers

public opens everywhere; protected means package + subclasses; default (no modifier) means package; private means the same class only. Class members default to package-private.

1
2
3
4
5
6
7
public class Demo {
public int pub; // anywhere
protected int prot; // within package + subclasses
int def; // within package
private int priv; // this class only
}
// Top-level classes can only be public or package-private (default)

static Members

static fields and methods belong to the class, not instances. static methods cannot access instance members. Use static blocks for initialization.

1
2
3
4
5
6
7
8
public class Counter {
private static int count; // class-level
public static void inc() { count++; }
public static int get() { return count; }
}
Counter.inc(); // accessed via the class
Counter.inc();
System.out.println(Counter.get()); // 2

Inner and Anonymous Classes

Inner classes, anonymous classes, and lambdas simplify callbacks. A static inner class does not hold an outer reference, avoiding leaks.

1
2
3
4
5
6
7
8
// Anonymous class (pre-Java 8 style):
Runnable r = new Runnable() {
@Override public void run() { }
};
// Lambda is preferred:
Runnable r2 = () -> System.out.println("hi");
// Static nested class: static class Builder { }
// Inner class implicitly holds a reference to the outer instance (watch for leaks)

11.Exception Handling

try/catch/finally, exception hierarchy, checked/unchecked, try-with-resources, and custom exceptions.

try / catch / finally

try holds code that may fail, catch handles errors, and finally always runs (for cleanup). Exceptions propagate upward.

1
2
3
4
5
6
7
try {
int n = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("bad number: " + e.getMessage());
} finally {
System.out.println("cleanup"); // always runs
}

Multi-catch

Multiple catch clauses match by type; put more specific exceptions first, broader ones last. Use multi-catch with | to combine unrelated types.

1
2
3
4
5
6
7
8
9
try {
process();
} catch (FileNotFoundException e) {
// specific exception
} catch (IOException e) {
// general exception
} catch (IllegalArgumentException | IllegalStateException e) {
// multi-catch: combine with | (no inheritance relation)
}

Throw and Rethrow

throw new throws an exception. throw; inside a catch (Java 7+) rethrows the original, preserving the stack. Declare throws to mark checked exceptions a method may throw.

1
2
3
4
5
6
7
throw new IllegalArgumentException("invalid value");
public void read() throws IOException {
throw new IOException("io error");
}
try { read(); } catch (IOException e) {
throw new RuntimeException(e); // wrap and rethrow
}

Custom Exception

Custom exceptions extend Exception (checked) or RuntimeException (unchecked). By convention, end the class name with Exception.

1
2
3
4
5
6
7
8
9
public class ConfigException extends RuntimeException {
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable cause) {
super(message, cause);
}
}
throw new ConfigException("bad config");

Checked and Unchecked Exceptions

Checked exceptions (e.g. IOException) must be caught or declared; unchecked exceptions (RuntimeException subclasses) are not enforced at compile time.

1
2
3
4
5
6
7
// Checked exception: must catch or declare throws
public void read() throws IOException {
Files.readString(Path.of("a.txt"));
}
// Unchecked exception: no handling required
int x = Integer.parseInt("abc"); // compiles, throws at runtime
// Custom exceptions usually extend RuntimeException

try-with-resources

try (resource) { } automatically calls AutoCloseable.close(), even on exception. It replaces manual finally cleanup.

1
2
3
4
5
try (var reader = new BufferedReader(
Files.newBufferedReader(Path.of("a.txt")))) {
String line = reader.readLine();
} // reader is auto-closed
// Multiple resources: try (var a = ...; var b = ...) { }

Cost of Exceptions

Catching exceptions is expensive (stack walking); do not use it for control flow. Use return values, null checks, or Optional for predictable errors.

1
2
3
4
5
6
// Slow: using exceptions for flow control
boolean ok1;
try { Integer.parseInt(s); ok1 = true; }
catch (NumberFormatException e) { ok1 = false; }
// Fast: check null/regex first, then parse
boolean ok2 = s != null && s.matches("\\d+");

Logging and Exceptions

Log exceptions (with stack traces); do not just System.out and continue. Use a logging framework (SLF4J + Logback) for level-based output.

1
2
3
4
5
6
7
// SLF4J + Logback:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log =
LoggerFactory.getLogger(App.class);
// Log exception with stack trace:
// log.error("failed", e); // e preserves the stack trace

12.Files and IO

Modern Files/Path IO, Scanner, Reader/Writer, and streaming reads.

Files Read/Write

java.nio.file.Files is the modern API: readString/writeString, readAllLines, copy/move/delete.

1
2
3
4
5
String content = Files.readString(Path.of("in.txt"));
Files.writeString(Path.of("out.txt"), content);
List<String> lines = Files.readAllLines(Path.of("in.txt"));
Files.copy(Path.of("a"), Path.of("b"), StandardCopyOption.REPLACE_EXISTING);
Files.deleteIfExists(Path.of("tmp"));

Scanner Input

Scanner reads from the console or files: nextLine for a line, nextInt/nextDouble for typed values. Use hasNext to check for more input.

1
2
3
4
5
6
7
Scanner sc = new Scanner(System.in);
System.out.print("Name: ");
String name = sc.nextLine();
int age = sc.nextInt();
// Read from a file:
var file = new Scanner(Path.of("a.txt"));
while (file.hasNextLine()) System.out.println(file.nextLine());

Reader / Writer

Character streams read and write text: BufferedReader for line-by-line, BufferedWriter for writing, and try-with-resources for auto-close.

1
2
3
4
5
6
7
8
9
10
try (var reader = new BufferedReader(
new FileReader("a.txt"));
var writer = new BufferedWriter(
new FileWriter("b.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} // both streams auto-close

Byte Streams

InputStream/OutputStream read and write bytes: FileInputStream, BufferedInputStream for buffering. Use transferTo for bulk data movement.

1
2
3
4
5
6
7
8
9
try (var in = new FileInputStream("a.bin");
var out = new FileOutputStream("b.bin")) {
byte[] buf = new byte[8192];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
}
// Simple version: Files.copy(Path.of("a"), Path.of("b"))

Console IO

System.out for output, System.in for input, System.err for errors. printf formats output.

1
2
3
4
5
System.out.print("No newline");
System.out.println("With newline");
System.out.printf("%s is %d years old%n", "Nick", 30);
System.err.println("Error message"); // standard error
// Formatting: %n is newline (cross-platform), don't use \n

Path

Path represents a path. resolve joins, getFileName reads the name, toAbsolutePath resolves, exists checks, Files.walk recursively iterates.

1
2
3
4
5
6
7
8
9
Path dir = Path.of("data");
Path file = dir.resolve("a.txt"); // data/a.txt
String name = file.getFileName().toString();
boolean exists = Files.exists(file);
// Recursive listing:
try (var stream = Files.walk(dir)) {
stream.filter(p -> p.toString().endsWith(".txt"))
.forEach(System.out::println);
}

Serialization

ObjectOutputStream/ObjectInputStream serialize objects (the class must implement Serializable). JSON is more common in modern code.

1
2
3
4
5
6
7
8
9
10
class User implements Serializable {
private static final long serialVersionUID = 1L;
String name;
}
// Write:
try (var out = new ObjectOutputStream(
new FileOutputStream("u.bin"))) {
out.writeObject(new User());
}
// Read: ObjectInputStream in = ...; User u = (User) in.readObject();

Temporary Files

Files.createTempFile creates a file in the system temp directory. Delete it with Files.deleteIfExists when done to avoid residue.

1
2
3
4
5
6
Path tmp = Files.createTempFile("app-", ".log");
System.out.println(tmp); // system temp directory
Files.writeString(tmp, "temp data");
// Delete when done to avoid leftovers:
Files.deleteIfExists(tmp);
// Temp directory: Files.createTempDirectory("app");

13.Common Pitfalls (FAQ)

The most common Java pitfalls: == vs equals, boxing cache, concurrency, exceptions, and collection mutation.

== vs equals

Compare string/object content with equals; == compares references. The literal pool can make == accidentally true - never rely on it.

1
2
3
4
5
6
7
// BAD: == compares references; the literal pool hides the issue
String a = "nick";
String b = new String("nick");
boolean bad = a == b; // false
// GOOD: equals compares content
boolean good = a.equals(b); // true

Integer Cache

Autoboxed values from -128 to 127 are cached, so == can be true; outside that range it is false. Always use equals for object comparison.

1
2
3
4
5
6
7
// BAD: comparing boxed values with ==
Integer a = 200, b = 200;
boolean bad = a == b; // false
// GOOD: use equals
Integer c = 200, d = 200;
boolean good = c.equals(d); // true

Null Check and NPE

Calling a method on null throws an NPE. Use Optional or check null up front; do not sprinkle try-catch NPE everywhere.

1
2
3
4
5
6
7
8
// BAD: no null check
String s = find();
System.out.println(s.length()); // possible NPE
// GOOD: null check or Optional
String t = find();
System.out.println(t == null ? 0 : t.length());
// or Optional.ofNullable(find()).map(String::length).orElse(0)

Swallowing Exceptions

An empty catch silently swallows the exception and makes debugging hard. At least log it, or rethrow (wrapped in a suitable exception).

1
2
3
4
5
6
7
8
// BAD: silently swallowed
catch (IOException e) { }
// GOOD: log or rethrow to the caller
catch (IOException e) {
log.error("read failed", e);
throw new RuntimeException("read failed", e);
}

equals and hashCode

When you override equals you must also override hashCode, otherwise HashSet/HashMap will treat equal objects as different keys.

1
2
3
4
5
6
7
8
// BAD: only override equals
class A { public boolean equals(Object o) { ... } } // hashCode inconsistent
// GOOD: override both
class B {
public boolean equals(Object o) { /* same logic */ }
public int hashCode() { return Objects.hash(fields); }
}

Removing During Iteration

Calling List.remove inside an enhanced for throws ConcurrentModificationException. Use Iterator.remove or collect-then-remove.

1
2
3
4
5
6
7
8
9
10
// BAD: removing in an enhanced for loop
for (String s : list) {
if (s.isEmpty()) list.remove(s); // throws exception
}
// GOOD: remove via Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) it.remove();
}

Date Mutability

The legacy Date/Calendar are mutable and error-prone. Use java.time (LocalDate, LocalDateTime) - immutable and safe.

1
2
3
4
5
6
7
// BAD: mutable Date
Date d = new Date();
d.setTime(0); // state can be mutated externally
// GOOD: immutable java.time
LocalDate today = LocalDate.now();
LocalDate next = today.plusDays(1); // returns a new object

Thread Safety

HashMap and ArrayList are not thread-safe and break under concurrent writes. Use ConcurrentHashMap or a synchronized wrapper.

1
2
3
4
5
6
7
8
// BAD: writing to HashMap from multiple threads
Map<String, Integer> map = new HashMap<>();
// concurrent put can corrupt internal structure
// GOOD: concurrent collections
Map<String, Integer> safe = new ConcurrentHashMap<>();
safe.put("k", 1);
// or Collections.synchronizedMap(new HashMap<>())

Stream Null Values

When a Stream contains null elements, filter/map may NPE. Filter first with filter(Objects::nonNull).

1
2
3
4
5
6
7
8
// BAD: null elements go into map
list.stream().map(String::toUpperCase) // NPE if any null present
// GOOD: filter out nulls first
list.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.toList();

Loop Concatenation

Using += in a loop creates many intermediate Strings and is slow. Use StringBuilder.

1
2
3
4
5
6
7
8
// BAD: concatenation in a loop
String s = "";
for (int i = 0; i < 10000; i++) s += i;
// GOOD: StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i);
String r = sb.toString();

14.Concurrency and Threads

Thread, synchronized, thread pools (ExecutorService), and CompletableFuture.

Thread

Use Thread or Runnable to create a thread; start to launch, join to wait, sleep to pause. A thread pool is usually preferable.

1
2
3
4
5
6
7
8
9
10
// Approach 1: extend Thread
Thread t = new Thread(() -> {
System.out.println("worker");
});
// Approach 2: Runnable
Runnable task = () -> System.out.println("run");
Thread t2 = new Thread(task);
t2.start(); // start
// t2.join(); // wait for completion
// Thread.sleep(100); // pause for milliseconds

synchronized

synchronized locks for mutual exclusion. An instance method locks this; a static method locks the Class; a block can lock any object.

1
2
3
4
5
6
7
8
private int count;
public synchronized void increment() { count++; }
// Equivalent: synchronized(this) { count++; }
// Static synchronization:
public static synchronized void inc2() { }
// Prefer a dedicated final field as the lock object:
private final Object lock = new Object();
synchronized (lock) { /* critical section */ }

volatile

volatile guarantees visibility (writes are immediately visible to other threads) but not atomicity. Use volatile for flags and AtomicInteger for counters.

1
2
3
4
5
6
7
private volatile boolean running = true;
// reads by other threads see the latest value immediately
// but volatile does not make operations atomic:
// volatile int n; n++; // not atomic!
// Use AtomicInteger instead:
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();

Thread Pool

ExecutorService manages thread reuse. newFixedThreadPool / newCachedThreadPool; call shutdown when done.

1
2
3
4
5
6
7
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
pool.submit(() -> System.out.println(Thread.currentThread().getName()));
}
pool.shutdown(); // no new tasks accepted
// pool.awaitTermination(5, TimeUnit.SECONDS);
// Recommended: Executors.newVirtualThreadPerTaskExecutor() (Java 21)

Future and Callable

Callable is a task with a return value; Future.get blocks for the result. FutureTask can be controlled manually.

1
2
3
4
5
6
7
8
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f = pool.submit(() -> {
return compute(); // Callable returns a value
});
int result = f.get(); // blocks waiting for the result
// f.get(5, TimeUnit.SECONDS); // timeout
// f.cancel(true); // cancel
pool.shutdown();

CompletableFuture

Async composition: thenApply to transform, thenCombine to merge, allOf to wait for many. Callbacks chain without blocking the caller.

1
2
3
4
5
6
7
8
CompletableFuture.supplyAsync(() -> fetch())
.thenApply(data -> parse(data))
.thenAccept(result -> System.out.println(result))
.exceptionally(ex -> { log(ex); return null; });
// Wait for multiple:
var all = CompletableFuture.allOf(f1, f2);
all.join();
// join() blocks until complete; fetch results via get()

Concurrent Collections

ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue are thread-safe. Prefer them over manual locking on ordinary collections.

1
2
3
4
5
6
7
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("k", 1);
map.compute("k", (k, v) -> v == null ? 1 : v + 1);
Queue<Task> q = new ArrayBlockingQueue<>(100);
q.offer(task); // returns false if full
Task t = q.poll(); // returns null if empty
// CopyOnWriteArrayList for read-heavy, write-rare scenarios

Lock Interface

ReentrantLock is more flexible than synchronized: timeout, interruptible, fair. You must unlock it manually (in finally).

1
2
3
4
5
6
7
8
9
10
11
private final ReentrantLock lock = new ReentrantLock();
void work() {
lock.lock();
try {
/* critical section */
} finally {
lock.unlock(); // must release in finally
}
}
// lock.tryLock(1, TimeUnit.SECONDS) for timeout
// ReadWriteLock separates reads and writes to boost concurrency

15.Network and HTTP

HttpClient, URL requests, JSON serialization, and WebSocket.

HttpClient Basics

java.net.http.HttpClient (Java 11+) sends HTTP requests. send is synchronous; sendAsync is asynchronous. Configure with a builder.

1
2
3
4
5
6
7
8
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
HttpResponse<String> resp =
client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());

POST and JSON

POST requests with a JSON body. Use Jackson or Gson to (de)serialize JSON.

1
2
3
4
5
6
7
8
9
String json = "{\"name\":\"Nick\"}";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
// Parse: Jackson ObjectMapper om = new ObjectMapper();
// User u = om.readValue(resp.body(), User.class);

Async Request

sendAsync returns a CompletableFuture and does not block the caller. Chain thenApply for concurrent requests.

1
2
3
4
5
6
7
HttpClient client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://example.com")).build();
client.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join(); // block waiting (for demo)
// In a truly async context, don't call join

URL and URLConnection

The legacy URL/HttpURLConnection works for simple requests. HttpClient is cleaner for most cases. URL-encode query parameters.

1
2
3
4
5
6
7
URL url = new URL("https://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
String body = new String(conn.getInputStream().readAllBytes());
// Parameter encoding:
String q = URLEncoder.encode("Chinese", StandardCharsets.UTF_8);

JSON Parsing

Jackson and Gson are the mainstream JSON libraries. Use @JsonProperty to map field names; combine with Java 17 records.

1
2
3
4
5
6
7
8
9
// Gson:
Gson gson = new Gson();
User u = gson.fromJson(json, User.class);
String out = gson.toJson(u);
// Jackson:
ObjectMapper om = new ObjectMapper();
User u2 = om.readValue(json, User.class);
String s2 = om.writeValueAsString(u2);
// Add Jackson dependency in pom

Calling Web APIs

Compose: build the request, check the status code, then deserialize. Handle non-success codes like 404 / 500.

1
2
3
4
5
6
7
8
9
10
var req = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/users/" + login))
.header("User-Agent", "MyApp")
.GET().build();
var resp = client.send(req, BodyHandlers.ofString());
if (resp.statusCode() == 200) {
var user = om.readValue(resp.body(), User.class);
} else {
System.err.println("status: " + resp.statusCode());
}

WebSocket

java.net.http.WebSocket is a bidirectional long-lived connection. A Listener receives callbacks; onOpen/onText handle events.

1
2
3
4
5
6
7
8
9
10
11
var ws = HttpClient.newHttpClient()
.newWebSocketBuilder()
.buildAsync(URI.create("wss://example.com/ws"),
new WebSocket.Listener() {
public void onOpen(WebSocket webSocket) {
webSocket.sendText("hello", true);
}
// onText/onError/onClose callbacks
})
.join();
ws.sendText("msg", true);

Timeout and Retry

HttpRequest.timeout sets the request timeout; HttpClient.connectTimeout sets the connect timeout. On timeout an HttpTimeoutException is thrown.

1
2
3
4
5
6
7
8
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
var req = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(10))
.build();
// Timeout throws HttpTimeoutException; catch and handle
// Simple retry: loop with catch and retry (add backoff)

16.Date and Time

java.time local date/time, Instant, Duration/Period, and formatting.

LocalDate

LocalDate is a date without time: now, of, plusDays, minusMonths. It is immutable and thread-safe.

1
2
3
4
5
6
7
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2024, 1, 1);
LocalDate next = date.plusDays(7);
LocalDate prev = date.minusMonths(1);
int year = date.getYear();
boolean leap = date.isLeapYear();
DayOfWeek dow = date.getDayOfWeek();

LocalTime

LocalTime is a time without a date: now, of, plusMinutes. Combine with LocalDate to form LocalDateTime.

1
2
3
4
5
6
LocalTime time = LocalTime.now();
LocalTime t = LocalTime.of(14, 30, 0);
LocalTime later = t.plusHours(1);
int hour = t.getHour();
// Combine with a date:
LocalDateTime dt = LocalDateTime.of(date, t);

Instant Timestamp

Instant is a UTC point in time (epoch seconds/nanoseconds), suitable for cross-time-zone storage. Use System.currentTimeMillis for system time.

1
2
3
4
5
6
7
Instant now = Instant.now();
long epochMilli = now.toEpochMilli();
Instant fromEpoch = Instant.ofEpochMilli(1_700_000_000_000L);
// From system time:
long nowMs = System.currentTimeMillis();
// Converting between Instant and LocalDateTime requires a timezone:
LocalDateTime ldt = LocalDateTime.ofInstant(now, ZoneId.systemDefault());

Duration and Period

Duration is a time-based amount (seconds/nanoseconds); Period is a date-based amount (years, months, days).

1
2
3
4
5
6
Duration d = Duration.ofMinutes(90);
long seconds = d.toSeconds(); // 5400
Duration between = Duration.between(t1, t2);
Period p = Period.of(1, 2, 3); // 1 year 2 months 3 days
Period since = Period.between(birthday, today);
int years = since.getYears();

Formatting and Parsing

DateTimeFormatter formats and parses. Built-in ISO formatters exist; custom patterns like yyyy-MM-dd HH:mm:ss are supported.

1
2
3
4
5
6
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String s = LocalDateTime.now().format(fmt);
LocalDateTime parsed = LocalDateTime.parse("2024-01-01 12:30", fmt);
// Built-in formats:
String iso = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
// Watch for parsing exceptions: DateTimeParseException

Time Zone

ZonedDateTime carries a time zone; ZoneOffset is a fixed offset. ZoneId.systemDefault is the local zone. Store in UTC.

1
2
3
4
5
6
7
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime zdt = ZonedDateTime.now(shanghai);
Instant utc = zdt.toInstant();
ZonedDateTime back = utc.atZone(ZoneId.of("UTC"));
// Fixed offset:
ZoneOffset offset = ZoneOffset.ofHours(8);
OffsetDateTime odt = OffsetDateTime.of(ldt, offset);

Old API Conversion

Convert between java.util.Date/Calendar and the new API. Date is mutable and error-prone; use java.time for new code.

1
2
3
4
5
6
7
8
// Date → Instant:
Instant inst = new Date().toInstant();
// Instant → Date:
Date d = Date.from(Instant.now());
// Convert:
LocalDateTime ldt = LocalDateTime.ofInstant(
inst, ZoneId.systemDefault());
// Calendar is long deprecated; avoid it

Timestamp Utilities

System.currentTimeMillis gives milliseconds; nanoTime gives nanosecond deltas (intervals only, not wall time). Convert between timestamps and formats.

1
2
3
4
5
6
7
long start = System.nanoTime();
compute();
long elapsedNs = System.nanoTime() - start; // interval
// Millisecond timestamp → LocalDateTime:
LocalDateTime t = LocalDateTime.ofInstant(
Instant.ofEpochMilli(System.currentTimeMillis()),
ZoneId.systemDefault());

17.Processes and System

ProcessBuilder to launch processes, system properties, environment variables, and runtime information.

ProcessBuilder

ProcessBuilder launches external programs. Pass arguments as a list to avoid injection. redirectErrorStream merges stderr into stdout.

1
2
3
4
5
6
ProcessBuilder pb = new ProcessBuilder("git", "log", "-1");
pb.redirectErrorStream(true); // merge error into output
Process proc = pb.start();
String out = new String(proc.getInputStream().readAllBytes());
int code = proc.waitFor(); // wait for exit
System.out.println(out);

System Properties

System.getProperty reads JVM system properties: user.home, java.version, os.name. Use setProperty to set them.

1
2
3
4
5
6
String home = System.getProperty("user.home");
String ver = System.getProperty("java.version");
String os = System.getProperty("os.name");
System.setProperty("my.prop", "value");
// All: System.getProperties().forEach(...)
// JVM argument: -Dmy.prop=value

Environment Variables

System.getenv reads environment variables (read-only); getenv() returns them all. Distinguish system properties from environment variables.

1
2
3
4
5
6
String path = System.getenv("PATH");
String home = System.getenv("HOME");
Map<String, String> all = System.getenv();
// Environment variables are read-only;
// Use -D or System.setProperty for system properties
// For config, check both: prefer getenv or properties, depending on policy

Runtime Information

Runtime exposes JVM info: availableProcessors, maxMemory, totalMemory. gc() is a hint.

1
2
3
4
5
6
7
Runtime rt = Runtime.getRuntime();
int cores = rt.availableProcessors();
long maxMem = rt.maxMemory(); // max heap
long used = rt.totalMemory() - rt.freeMemory();
// rt.gc() hints collection (avoid in production)
System.out.printf("cores=%d used=%dMB%n",
cores, used / 1024 / 1024);

Command-Line Argument Parsing

Parse options from main args: simple manual loops for simple cases, picocli/JCommander for complex ones.

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
String file = null;
boolean verbose = false;
for (String a : args) {
if (a.equals("-v")) verbose = true;
else if (a.equals("-f")) { /* take the next one */ }
else file = a;
}
}
// For complex CLIs use picocli: @Command/@Option annotations

Shutdown Hook

Runtime.addShutdownHook registers cleanup that runs on JVM exit. System.exit triggers it. Avoid long-running work in the hook.

1
2
3
4
5
6
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("shutting down...");
saveState();
}));
// Runs on normal JVM exit / System.exit
// Hook threads run concurrently; don't do heavy work here

Working Directory

The user.dir system property is the process's current working directory. ProcessBuilder.directory sets the starting directory for a child process.

1
2
3
4
5
6
7
String cwd = System.getProperty("user.dir");
System.out.println(cwd); // current working directory
// Subprocess start directory (run in a different folder):
Path target = Path.of(System.getProperty("user.home"), "work");
ProcessBuilder pb = new ProcessBuilder("git", "status");
pb.directory(target.toFile());
Process p = pb.start();

System Platform Info

System properties expose platform info: os.name (OS), os.arch (architecture), file.separator (path separator), line.separator (newline).

1
2
3
4
5
6
String os = System.getProperty("os.name"); // Windows 11 / Linux
String arch = System.getProperty("os.arch"); // amd64
String sep = System.getProperty("file.separator"); // \ or /
String eol = System.getProperty("line.separator"); // newline
String pathSep = System.getProperty("path.separator"); // ; or :
System.out.println(os + " " + arch);

18.Regular Expressions

Pattern/Matcher, matching, capture groups, replacement, and common patterns.

Pattern and Matcher

Pattern.compile compiles a regex (cache and reuse), matcher matches against text. find, matches, and lookingAt are three match modes.

1
2
3
4
5
6
Pattern p = Pattern.compile("\\d{3}-\\d{4}");
Matcher m = p.matcher("call 123-4567");
boolean found = m.find(); // partial match
Matcher m2 = p.matcher("123-4567");
boolean full = m2.matches(); // full match
// Direct check: "123-4567".matches("\\d{3}-\\d{4}")

Capture Groups

Parentheses capture substrings; group(1) returns the first group, named groups (?<name>...) use group("name"). Loop find to get all matches.

1
2
3
4
5
6
7
8
9
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})");
Matcher m = p.matcher("date: 2024-01");
if (m.find()) {
String year = m.group(1); // 2024
String month = m.group(2); // 01
}
// Named groups:
Pattern p2 = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})");
// m.group("year") yields 2024

Find All

Loop find or use matcher.results to iterate all matches. Use replaceAll for all replacements (supports $1 group references).

1
2
3
4
5
6
7
8
Matcher m = Pattern.compile("\\w+").matcher(text);
while (m.find()) {
System.out.println(m.group()); // each word
}
// Java 9+:
m.results().forEach(r -> System.out.println(r.group()));
// Replace:
String masked = "123456".replaceAll("(\\d{3})(\\d+)", "$1***");

Replace

replaceAll / replaceFirst replace via regex with $1/$2 group references. Matcher.appendReplacement processes segments one by one.

1
2
3
4
5
6
7
8
9
String s = "a1b2c3";
String all = s.replaceAll("\\d", "#"); // a#b#c#
String first = s.replaceFirst("\\d", "#"); // a#b2c3
// Callback-based replace:
Matcher m = Pattern.compile("\\d").matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) m.appendReplacement(sb, "{" + m.group() + "}");
m.appendTail(sb);
// sb = "a{1}b{2}c{3}"

Pattern Flags

Pattern.CASE_INSENSITIVE ignores case; MULTILINE makes ^ $ match per line; DOTALL makes . match newlines.

1
2
3
4
5
6
Pattern p = Pattern.compile("^java",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher("Java\nnot java\n");
// Inline flags:
Pattern p2 = Pattern.compile("(?i)java"); // case-insensitive
// (?s) DOTALL, (?m) MULTILINE

Common Patterns

Common regexes for email, URL, IP, and phone numbers. For production-grade validation (e.g. real email format) use a dedicated library.

1
2
3
4
5
String email = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";
String ip = "^((?:\\d{1,3}\\.){3}\\d{1,3})$";
String phone = "^1[3-9]\\d{9}$"; // China mainland mobile phone
boolean ok = input.matches(phone);
// Note: \\d in Java strings is the regex \d

Quantifiers and Anchors

* zero or more, + one or more, ? zero or one, {n,m} a specific count. ^ line start, $ line end, \b word boundary.

1
2
3
4
5
6
"\\d+" // one or more digits
"colou?r" // color or colour
"\\d{2,4}" // 2 to 4 digits
"^start" // start at line beginning
"end$" // end at line end
"\\bword\\b" // standalone word

Lookahead and Lookbehind

Lookahead (?=...) checks what follows, negative lookahead (?!...) excludes it, lookbehind (?<=...) checks what precedes. None consume characters.

1
2
3
4
5
6
7
Pattern p = Pattern.compile("\\d+(?=\\s*yuan)"); // match only if followed by yuan
Matcher m = p.matcher("price 100 yuan");
if (m.find()) System.out.println(m.group()); // 100
// Negative lookahead: digits not followed by a letter
Pattern p2 = Pattern.compile("\\d+(?!\\p{L})");
// Lookbehind requires fixed length:
Pattern p3 = Pattern.compile("(?<=price is)\\d+");

19.Build and Debug

Maven/Gradle, javac/jar, JUnit, and JVM debugging.

Maven Lifecycle

mvn compile/test/package/install are Maven lifecycle phases. The target directory holds classes and jars.

1
2
3
4
5
6
// $ mvn clean compile # clean and compile
// $ mvn test # run tests
// $ mvn package # package into jar
// $ mvn install # install to local repo
// $ mvn dependency:tree # view dependency tree
// Output goes to the target/ directory

pom.xml

pom.xml defines the project: groupId/artifactId/version coordinates, dependencies, and properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>app</artifactId>
<version>1.0.0</version>
<properties><maven.compiler.release>17</maven.compiler.release></properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</project>

Gradle

Gradle is based on Groovy/Kotlin DSL. Tasks define build steps; dependencies resolve from a central repo.

1
2
3
4
5
6
7
8
9
// build.gradle:
plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
}
test { useJUnitPlatform() }
// $ gradle build / gradle test / gradle run

JUnit Tests

JUnit 5: @Test for test methods, @BeforeEach for setup, assert* for assertions. Run with mvn test.

1
2
3
4
5
6
7
8
9
10
11
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void add_returns_sum() {
var calc = new Calculator();
assertEquals(5, calc.add(2, 3));
assertTrue(calc.add(1, 1) == 2);
}
}
// Failed assertions clearly report expected vs actual

jar Packaging

jar packages classes into a jar. An executable jar needs a Main-Class entry in the manifest. Run with java -jar.

1
2
3
4
5
// $ jar cf app.jar com/ # package a directory
// $ jar cfe app.jar Main com/ # specify the main class
// $ java -jar app.jar # run
// Inspect contents: jar tf app.jar
// MANIFEST.MF includes Main-Class: Main

JVM Flags

-Xmx max heap, -Xms initial heap, -XX:+PrintGCDetails GC logs, -D system properties.

1
2
3
4
5
// $ java -Xms256m -Xmx2g -jar app.jar
// $ java -Dserver.port=8080 -jar app.jar
// $ java -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError app
// View defaults: java -XX:+PrintFlagsFinal -version
// Debug: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

Logging Configuration

SLF4J as the facade with Logback as the implementation. Configure level and outputs in logback.xml. Avoid System.out in production.

1
2
3
4
5
6
7
8
9
10
11
// logback.xml:
<configuration>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" />
</configuration>
// Code:
private static final Logger log =
LoggerFactory.getLogger(App.class);
log.info("user {} logged in", id);

Dependency Management

Maven dependencies are scoped: compile/test/runtime. Use exclusions to drop transitive deps; manage version conflicts in dependencyManagement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope> <!-- available only during tests -->
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
<exclusions> <!-- exclude transitive deps -->
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
// Resolve version conflicts centrally in dependencyManagement

Official Links

Direct links to the official docs and resources.

About this Cheatsheet

This page is a self-contained cheatsheet for Java 17 (LTS), covering around 80% of the everyday usage of the core language, the commonly used JDK libraries, and the build ecosystem in real projects. The content favors modern idioms: var local variable type inference, switch expressions, text blocks, record, sealed classes, and the Stream API. Java was first released by Sun Microsystems in 1995 and is best known for its JVM ecosystem / "write once, run anywhere" promise; it is one of the most widely used languages for enterprise backends, Android development, and big data. The 19 sections each focus on a single topic: basics, variables, types and references, control flow, functions, strings, collections (List/Set/Map), memory management (GC), object-oriented programming, exception handling, I/O, common pitfalls, concurrency, network, time, processes, regex, and build tools (Maven/Gradle). Each subsection pairs a concept intro with a copy-ready code snippet. All code and text are rendered locally in your browser; no data leaves your device. For authoritative reference, see the official Oracle Java documentation.

Version 2.1.0