Keentune

Java curriculum

26 chapters
·
223 concepts
·
free
Everything the adaptive question bank can teach and test in Java, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the Java guide
A free 16-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. Types, values and variables
a variable holds either a value or a reference; there is no object-typed value semantics
byte, short, int, long, float, double, char, boolean, each a fixed width on every platform
two's complement makes the range asymmetric, so Math.abs(Integer.MIN_VALUE) is still negative
char is an unsigned 16-bit UTF-16 code unit, not a character
no implicit conversion between boolean and int, unlike C
fields default to 0/false/null; locals get nothing and must be assigned first
var infers a static type at compile time; it is not dynamic typing and is illegal for fields
a final field can still point at a mutable object
Integer.valueOf caches −128..127, so == on small boxed values works by accident
unboxing a null wrapper throws NPE at the arithmetic, not at the assignment
B. Conversions, promotion and casting
byte→short→int→long→float→double needs no cast
a narrowing cast drops high bits rather than clamping
int→float and long→double lose precision with no cast and no warning
both operands promote to at least int before any arithmetic runs
byte + byte is an int, so plain assignment back to a byte will not compile
i *= 1.5 on an int compiles because += carries an implicit narrowing cast
integer overflow wraps without an exception; Math.addExact throws instead
truncates toward zero and saturates at MIN/MAX; NaN becomes 0
any + operand beside a String converts via String.valueOf, so a null reference prints "null"
C. Operators and expression evaluation
operands are fully evaluated left to right before the operator applies
/ on ints truncates toward zero, so -7 / 2 is -3
% takes the sign of the dividend, so -7 % 3 is -1, unlike Python
integer division throws ArithmeticException while floating point yields Infinity or NaN
NaN equals nothing including itself, so x != x is the NaN test
0.1 + 0.2 != 0.3; compare with a tolerance or use BigDecimal
&& and || skip the right operand; & and | always evaluate it
a conditional mixing Integer and int unboxes both branches and can throw
i++ yields the old value, so i = i++ leaves i unchanged
the shift count is taken mod 32 for int and mod 64 for long, and >>> differs from >> on negatives
D. Statements, control flow and definite assignment
a case without break falls into the next one
the arrow form never falls through, and an expression switch returns via yield
a switch expression over an enum or sealed type must cover every case or name a default
the for-each variable cannot remove an element or report its index
the only direct way out of an outer loop
resources close in reverse declaration order, even when the body throws
a failing close() is attached to the primary exception rather than replacing it
a return inside finally discards the pending exception or return value
the compiler rejects a read of a local it cannot prove was assigned on every path
statements after a return or an infinite loop are a compile error, not a warning
E. Classes, construction and initialization
this(...) or super(...) must come first, and an implicit super() is inserted otherwise
static initializers once at class init, then instance initializers and field initializers in textual order, then the constructor body
the subclass override runs before the subclass fields are initialized
fields resolve by static type; only methods dispatch dynamically
a static method is hidden, not overridden, and dispatch uses the reference type
public, protected, package-private and private, and what protected actually grants across packages
an inner class holds a hidden reference to its enclosing instance; a static nested class does not
and an overload that also matches without varargs wins resolution
the overload is chosen from compile-time types, so a bare null argument can be ambiguous
F. Interfaces, inheritance and polymorphism
multiple inheritance of type, single inheritance of state
a body on an interface, added so interfaces could grow without breaking implementors
inheriting two default methods with one signature forces an explicit override
callable on the interface, never inherited by implementors
exactly one abstract method; @FunctionalInterface only enforces what is already true
the runtime class picks the implementation, the static type picks the signature
an override may narrow the return type but never change a parameter type
changing a parameter type silently creates a new method instead of overriding
an override may drop or narrow checked exceptions, never widen them
G. The Object contract
reflexive, symmetric, transitive, consistent, and false for null
equal objects must share a hash code; unequal objects need not differ
the object becomes unfindable in a HashSet or HashMap
mutating a field used by hashCode strands the entry in the wrong bucket
symmetry breaks when a subclass adds state to the comparison
class name plus the hash in hex, which is not a memory address
compareTo inconsistent with equals makes TreeSet disagree with HashSet
a - b in a comparator overflows for large values; use Integer.compare
shallow, requires Cloneable, bypasses constructors; a copy constructor is preferred
deprecated for removal; use Cleaner or a closeable resource instead
H. Strings and text
every "mutation" allocates a new String and the old one is unchanged
literals are interned, so new String("a") != "a" while "a" == "a"
== compares references and only appears to work on interned literals
StringBuffer is synchronized and StringBuilder is the default choice
repeated + is quadratic; a StringBuilder makes it linear
an astral character is two chars, so length() is not the number of characters
"." must be escaped, and trailing empty strings are dropped by default
incidental leading whitespace is stripped relative to the closing delimiter
toUpperCase() uses the default locale; the Turkish dotless i breaks identifiers
not locale collation, so ordering differs from a human alphabet
I. Arrays
length is a field, not a method, and cannot change after creation
Object[] a = new String[1] compiles and throws ArrayStoreException on write
a fresh array is filled with 0, false or null
an array of arrays; rows may differ in length or be null
Arrays.equals compares contents; equals on an array does not
Arrays.asList writes through to the array and throws on add
object arrays sort stably by TimSort, primitives unstably by dual-pivot quicksort
J. Records, enums, sealed types and pattern matching
a canonical constructor, accessors, equals, hashCode and toString come for free
validates and normalizes parameters before the implicit field assignment
a record holding a List still hands out a mutable list
the accessor is x(), not getX(), which matters to bean-based frameworks
the declared constants are the only instances, preserved across serialization
per-constant method implementations, including an abstract method on the enum
persisting ordinal() breaks the moment someone reorders the constants
a sealed type names its subtypes, each of which must be final, sealed or non-sealed
sealing lets the compiler prove a switch covers every subtype without a default
destructuring in instanceof and switch, including nested patterns and the binding's scope
K. Exceptions
Exception is checked; RuntimeException and Error are not
a subclass catch must precede its superclass or the code will not compile
one block for several unrelated types; the parameter is implicitly final
wrapping without a cause throws away the original stack trace
an empty catch destroys the only diagnostic you will get
also swallows Error, including OutOfMemoryError and StackOverflowError
the expense is filling in the trace, not the throw itself
catching InterruptedException without re-interrupting breaks cancellation
L. Generics
type arguments vanish at runtime, so List<String> and List<Integer> are one class
new T[] is illegal because arrays are reified and generics are not
using a raw List turns off generic checks for the whole expression
List<String> is not a List<Object>, unlike arrays, and that is the sound choice
List<?> can be read as Object and written only with null
List<? extends Number> can be read from but not added to
List<? super Integer> accepts writes and reads back only as Object
producer extends, consumer super, as the rule for choosing a parameter's variance
<T extends Comparable<T>> and why the bound refers to T itself
a generic varargs parameter is unsound; @SafeVarargs asserts you checked
M. Collections
List, Set, Map and Queue, and the guarantee each one actually makes
contiguous random access beats pointer chasing for almost every real workload
buckets, the load-factor resize, and treeification of a long collision chain
LinkedHashMap is the implementation that preserves it
ordering, not equals, decides key identity in a sorted map
structural modification during iteration throws ConcurrentModificationException
Iterator.remove or removeIf, never collection.remove inside a for-each
Collections.unmodifiableList still reflects changes to the backing list
List.of and Map.of throw on null elements and duplicate keys
Java 21 unifies first, last and reversed-order operations across collections with a defined encounter order
N. Lambdas, method references and functional interfaces
this inside a lambda means the enclosing instance, not the lambda
a captured local may not be reassigned, because capture copies the value
static, bound instance, unbound instance and constructor references
String::length takes the receiver as its first parameter
the two orders of function composition, and which runs first
and, or, negate and Predicate.not
deferring work that may never be needed
IntFunction and friends exist purely to avoid boxing in hot code
a lambda cannot throw a checked exception the target interface does not declare
O. Streams and collectors
no intermediate operation runs until a terminal operation demands elements
reusing a consumed stream throws IllegalStateException
findFirst, anyMatch and limit can terminate an infinite source
a side-effecting map gives wrong answers under parallelism
one-to-one transformation versus flattening one-to-many
the runtime may skip peek when it can answer without traversing
the identity must be neutral and the operator associative, or parallel results differ
the right form for building a container, unlike reduce
throws unless you supply a merge function
a classifier plus a downstream collector such as counting or mapping
the shared common ForkJoinPool, splitting cost, and the workloads where it loses
P. Optional and null discipline
designed for return values, not fields, parameters or collection elements
the anti-pattern; orElseThrow states the intent honestly
orElse evaluates its argument even when a value is present
of throws on null; ofNullable is the bridge from legacy APIs
chaining lookups without nesting Optional<Optional<T>>
Q. Threads, the memory model and synchronization
a bad interleaving versus unsynchronized conflicting access to one location
the ordering relation that makes one thread's write visible to another
without a synchronization action a thread may never observe another's write
visibility and ordering, but no atomicity for a read-modify-write
count++ is three operations and loses updates under contention
mutual exclusion plus the happens-before edge on release and acquire
a thread may re-enter a monitor it already holds
the cycle of held-and-wanted locks, and the global order that prevents it
wait releases the monitor and can wake spuriously, so it belongs in a while loop
final fields of a properly constructed object are visible without synchronization, and double-checked locking needs volatile
R. java.util.concurrent and virtual threads
separating the task from the thread, plus the shutdown and awaitTermination protocol
CPU-bound work wants roughly core count; blocking IO wants far more
and a task failure surfaces wrapped in ExecutionException
thenApply, thenCompose and thenCombine, and which thread runs the callback
without exceptionally or handle a failed stage is simply never observed
striped writes, weakly consistent iteration, and atomic computeIfAbsent
compare-and-swap in a retry loop, and the ABA problem behind it
the producer-consumer handoff that bounds memory
tryLock, fairness, interruptibility and multiple Conditions
cheap JVM-scheduled threads that unmount their carrier while blocked on IO
create one per task; pooling reintroduces the limit they remove
since JDK 24, blocking inside synchronized no longer pins a virtual thread to its carrier
immutable dynamic-scope context replaces mutable, leak-prone ThreadLocal state for request data
a task scope makes sibling failure, cancellation and lifetime one bounded unit of work
an immutable scoped binding instead of a mutable ThreadLocal, and a scope that ties subtask lifetimes to the caller
S. Dates and times
Date and Calendar are mutable and SimpleDateFormat is not thread-safe
a point on the timeline versus a wall-clock reading with no zone attached
an offset is a fixed number; a zone carries the rules that change it
31 January plus one month is 28 or 29 February, not 3 March
calendar units versus exact seconds, because a day is not always 24 hours
a local time that never happens, and one that happens twice
every operation returns a new value, so ignoring the result is a no-op bug
passing a Clock is what makes time-dependent logic testable
T. I/O, files and serialization
Path plus Files is the modern API; java.io.File is the legacy one
readAllLines loads the whole file into memory; a reader does not
a Files.lines stream must be closed or the descriptor leaks
InputStream carries bytes, Reader carries decoded characters
UTF-8 is the default since Java 18; older code silently depended on the platform charset
an unbuffered per-byte read is orders of magnitude slower than a buffered one
resolved against the process working directory, not the location of the class file
deserializing untrusted bytes lets an attacker choose which code runs
an absent UID changes with the class shape, and transient fields come back as defaults
U. Class loading, linking and initialization
loading, linking and initialization, and that initialization is lazy
first instantiation, static member access or reflection, but never a mere reference to the type
a static final compile-time constant is copied into the caller's class file and survives recompiling only the source
the standard loader asks its parent before searching itself
two loaders produce two distinct classes, and the ClassCastException that follows
a lookup that failed versus a class whose initialization already failed
a throwing static initializer permanently poisons the class
the JVM refuses a class it cannot prove type-safe, independent of the compiler
V. The JVM: memory, garbage collection and performance
frames and locals per thread, objects shared on the heap
recursion depth versus heap exhaustion, and what each tells you
an object is collected when unreachable from a GC root, not when a variable goes out of scope
most objects die young, which is why a young collection is cheap
a static collection, a listener never unregistered, an unbounded cache
soft, weak and phantom references, and what a WeakHashMap actually keys on
throughput versus pause time, and why there is no single best collector
a benchmark that measures the interpreter measures nothing; hot code is compiled and can be deoptimized
W. Packages, modules and the toolchain
the declared package must match the path under the source root
an import loads nothing at runtime and costs nothing
two packages exporting the same simple name force an explicit single-type import
requires, exports and opens in module-info.java
a package that is not exported is unreachable even by reflection unless it is opened
the unnamed module is what keeps non-modular jars working
--release checks against that JDK's API; -source/-target alone can produce a jar that fails at runtime
X. Annotations and reflection
SOURCE, CLASS and RUNTIME, and that only RUNTIME is visible to reflection
@Target constrains where an annotation may legally appear
turns a mistyped override into a compile error instead of a silent overload
lookup and access checks are slow; cache the Method or use a MethodHandle
getClass() cannot recover a collection's element type
getDeclaredMethods is this class only; getMethods is public and inherited
an interface-based interceptor, and the mechanism most frameworks are built on
Y. Java 25 language frontier
a small program may use an instance void main() without declaring a class, with java.lang.IO for console I/O
import module M; imports every package exported by a module without making the importing code modular
a constructor may validate or compute from arguments before super(...), but cannot use the new instance yet
Z. Release cadence and support
feature releases arrive every six months while the current LTS cadence selects every fourth release
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about Java practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC