Cheatsheets

📋 NPBlue Cheatsheets 11 sheets

Condensed, scannable reference cards — commands, flags, and syntax you can copy-paste mid-task, distilled from NPBlue's full guides.

Java Cheatsheet

The Collections/Streams APIs, the equals/hashCode contract, and the concurrency primitives you actually reach for — with the “why it behaves this way” that most reference pages leave out. For the full explanations, see the Java guides.

Collections — which one, and why

InterfaceImplementationUse it for
ListArrayListIndex access, iteration — the default choice
ListLinkedListFrequent insert/remove at the ends — rarely actually faster in practice due to poor cache locality
SetHashSetUniqueness, no order guarantee, O(1) average lookup
SetLinkedHashSetUniqueness + insertion order preserved
SetTreeSetUniqueness + sorted order, O(log n) operations
MapHashMapKey-value lookup, no order guarantee
MapLinkedHashMapKey-value lookup + insertion (or access) order
MapTreeMapKey-value lookup + sorted by key
List<String> names = new ArrayList<>(List.of("bob", "alice", "carol"));
Map<String, Integer> ages = new HashMap<>();
ages.put("alice", 30);
ages.getOrDefault("dave", 0); // 0 — no NullPointerException, no missing-key check needed
ages.computeIfAbsent("bob", k -> 0); // insert 0 only if "bob" isn't already present
ages.merge("alice", 1, Integer::sum); // increment-or-insert in one call

computeIfAbsent and merge exist specifically to collapse the “check if key exists, then either insert or update” pattern into one call — before Java 8, this was a containsKey check followed by a conditional put, done manually, every single time you built a frequency map or grouped values by key.

equals() and hashCode() — the contract that breaks silently

public class Point {
private final int x, y;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point)) return false;
Point p = (Point) o;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}

If you override equals() without overriding hashCode() (or vice versa), you break the documented contract: two objects that are .equals() must produce the same hashCode(). Violate it and a HashSet/HashMap will silently treat two “equal” objects as different — the object gets inserted twice, or a lookup that should find an existing entry returns nothing, and there’s no exception to point you at the cause. This is one of the most common real Java bugs precisely because the code compiles and runs fine until the collection behavior looks wrong.

Streams — the functional pipeline

List<String> names = List.of("Alice", "Bob", "Carol", "Dave");
List<String> result = names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
long count = names.stream().filter(n -> n.startsWith("A")).count();
Optional<String> longest = names.stream()
.max(Comparator.comparingInt(String::length));
// Grouping and aggregation — the collector patterns that come up constantly
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));
Map<Boolean, List<String>> partitioned = names.stream()
.collect(Collectors.partitioningBy(n -> n.length() > 3));
String joined = names.stream().collect(Collectors.joining(", ", "[", "]"));
double avgLength = names.stream()
.collect(Collectors.averagingInt(String::length));

Streams are lazy in the same sense Spark transformations are: .filter() and .map() just build a pipeline description — nothing executes until a terminal operation (.collect(), .count(), .forEach()) runs it. This means a stream can only be consumed once; calling a terminal operation twice on the same stream throws IllegalStateException, which surprises people coming from collections where iterating twice is normal.

// Parallel streams — real parallelism, not free
names.parallelStream()
.filter(n -> n.length() > 3)
.forEach(System.out::println); // order is NOT guaranteed here

parallelStream() splits work across the common ForkJoinPool — genuinely useful for CPU-heavy transformations over large collections, actively harmful for small collections (thread coordination overhead exceeds the work saved) or when the stream does I/O (shared thread pool starvation affects unrelated code using the same pool).

Generics

public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
// Bounded type parameter — only Number and its subclasses
public static <T extends Number> double sum(List<T> nums) {
double total = 0;
for (T n : nums) total += n.doubleValue();
return total;
}
// Wildcards
public static void printAll(List<? extends Number> list) { ... } // read-only, any Number subtype
public static void addIntegers(List<? super Integer> list) { ... } // write-only, Integer or a supertype

The wildcard rule that trips people up: ? extends T means you can safely read T out of the list (every element is at least a T), but you can’t safely add to it (the compiler doesn’t know the list’s exact runtime type, so it can’t guarantee what you add is compatible). ? super T is the mirror image — safe to write T into it, unsafe to assume what you read back out is exactly T. This is the PECS rule: Producer extends, Consumer super.

Records (Java 16+) — data carriers without the boilerplate

public record Point(int x, int y) {
// Compact constructor for validation — no need to restate the fields
public Point {
if (x < 0 || y < 0) throw new IllegalArgumentException("Coordinates must be non-negative");
}
}
Point p = new Point(3, 4);
p.x(); // accessor, not getX() — records use the field name directly
p.equals(new Point(3, 4)); // true — equals/hashCode/toString are generated automatically

A record replaces the ~40 lines of constructor, getters, equals(), hashCode(), and toString() that a plain immutable data class required — and generates them consistently, so the equals/hashCode contract described above can’t accidentally drift out of sync the way hand-written implementations sometimes do.

Optional — for return types, not fields

public Optional<User> findUser(String id) {
User u = database.lookup(id);
return Optional.ofNullable(u);
}
findUser("123")
.map(User::getEmail)
.filter(email -> email.contains("@"))
.orElse("no-email@example.com");
findUser("123").ifPresentOrElse(
user -> System.out.println("Found: " + user),
() -> System.out.println("Not found")
);

Optional is meant for method return values to make “this might not have a result” explicit in the type signature — using it as a class field or a method parameter is a well-documented anti-pattern (it adds an extra wrapper allocation and doesn’t compose with serialization frameworks the way a plain nullable field does). If a caller needs to check for absence, return Optional; if a field might be absent, a plain nullable reference with a @Nullable annotation is the more idiomatic choice.

Switch expressions (Java 14+) and var

// Old-style switch statement — falls through unless you remember `break`
switch (day) {
case MONDAY:
case TUESDAY:
System.out.println("Early week");
break;
default:
System.out.println("Later");
}
// Switch expression — no fall-through, returns a value, arrow syntax
String label = switch (day) {
case MONDAY, TUESDAY -> "Early week";
case WEDNESDAY -> "Midweek";
default -> "Later";
};
// Pattern matching in switch (Java 21+)
String describe(Object obj) {
return switch (obj) {
case Integer i when i > 0 -> "positive int";
case Integer i -> "non-positive int";
case String s -> "string of length " + s.length();
default -> "something else";
};
}

Switch statements fall through by default, which is why forgetting a break is a decades-old class of Java bug. Switch expressions (the arrow form) don’t fall through at all and force every branch to produce a value, which eliminates that failure mode structurally rather than relying on remembering break.

var names = new ArrayList<String>(); // type inferred as ArrayList<String> at compile time
var count = 0; // int
for (var entry : ages.entrySet()) { // very common, genuinely improves readability
System.out.println(entry.getKey());
}

var is compile-time type inference, not dynamic typing — the compiler still assigns and checks a concrete type, it just doesn’t require you to write it out when the right-hand side already makes it obvious. var result = someMethodThatReturnsWhat() is the case to avoid — if the type isn’t obvious from the line itself, var makes the code harder to read, not easier.

String handling

String s = "hello";
s.substring(1, 3); // "el"
s.replace("l", "L"); // "heLLo"
String.join(", ", "a", "b", "c"); // "a, b, c"
" trim me ".strip(); // "trim me" — Unicode-aware, prefer over the older .trim()
// StringBuilder for anything built in a loop
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i).append(",");
}
String result = sb.toString();

String in Java is immutable — every + concatenation creates a brand-new String object rather than modifying one in place. In a tight loop, s += i for 1000 iterations allocates roughly 1000 intermediate strings; StringBuilder mutates a single internal buffer instead, which is why it’s the standard recommendation for any string built incrementally rather than in one expression.

Interfaces with default methods

public interface PaymentProcessor {
void process(double amount);
default void processWithLogging(double amount) {
System.out.println("Processing: " + amount);
process(amount);
}
}

Default methods (Java 8+) let an interface ship a working implementation, so adding a new method to a widely-implemented interface no longer breaks every existing implementer — this is literally why they were added: Collection gained .stream() in Java 8 without forcing every pre-existing List/Set implementation in the wild to be recompiled with a new method.

Exception handling

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
logger.error("Failed to read file", e);
} finally {
System.out.println("Always runs");
}

Try-with-resources (anything implementing AutoCloseable) guarantees .close() runs even if the try block throws — before Java 7 this required a manual finally { reader.close(); } block that people routinely forgot, or wrote incorrectly (closing in the wrong order, or letting a close() exception mask the original one).

Checked vs uncheckedBehavior
Checked (IOException, SQLException)Must be declared (throws) or caught — the compiler enforces handling
Unchecked (RuntimeException and subclasses)No compiler enforcement — NullPointerException, IllegalArgumentException, your own custom runtime exceptions

Concurrency essentials

ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Integer> future = pool.submit(() -> computeExpensiveValue());
Integer result = future.get(); // blocks until done
pool.shutdown();
// CompletableFuture — composable async without blocking
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> fetchUserFromApi())
.thenApply(user -> user.toUpperCase())
.exceptionally(ex -> "fallback-value");
// Thread-safe counters without manual locking
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
// synchronized — the classic, coarser-grained lock
public synchronized void deposit(double amount) {
balance += amount;
}

ExecutorService exists so you stop calling new Thread(...) manually — unmanaged threads have no pooling, no backpressure, and nothing stopping a burst of requests from spinning up thousands of OS threads. A fixed thread pool caps concurrency to a known number and reuses threads instead of paying OS thread-creation cost per task. AtomicInteger gives lock-free thread safety for a single counter using CAS (compare-and-swap) at the hardware level — cheaper than synchronized for the narrow case of “one variable, simple update,” but it doesn’t generalize to protecting multiple related fields the way a lock does.

Common patterns

Builder pattern for objects with many optional fields

public class Pizza {
private final String size;
private final List<String> toppings;
private Pizza(Builder b) {
this.size = b.size;
this.toppings = b.toppings;
}
public static class Builder {
private String size = "medium";
private List<String> toppings = new ArrayList<>();
public Builder size(String size) { this.size = size; return this; }
public Builder addTopping(String t) { toppings.add(t); return this; }
public Pizza build() { return new Pizza(this); }
}
}
Pizza p = new Pizza.Builder().size("large").addTopping("mushroom").build();

Constructors with five-plus optional parameters force callers to either memorize positional order or pass a wall of nulls for the fields they don’t care about — a builder trades that for named, chainable calls, at the cost of one extra class.

Comparator chaining for multi-field sort

List<Person> people = getPeople();
people.sort(
Comparator.comparing(Person::getLastName)
.thenComparing(Person::getFirstName)
.thenComparingInt(Person::getAge)
);

Reading a file line by line without loading it all into memory

try (Stream<String> lines = Files.lines(Path.of("large-log.txt"))) {
long errorCount = lines.filter(line -> line.contains("ERROR")).count();
}

Files.lines() returns a lazily-read stream backed by a BufferedReader — it’s the Java equivalent of Python’s for line in file, and the reason to prefer it over Files.readAllLines() for anything larger than a config file.


Not covered: The full module system (JPMS/module-info.java) and JVM tuning flags aren’t covered here — those deserve their own reference rather than a footnote. For deeper coverage of any section above, see the Java guides.