---
title: "Java: Core Language & JVM Fundamentals"
description: "Test your knowledge of Java fundamentals covering OOP, the JVM, collections, generics, streams, and core language features."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/quizzes/post/java-fundamentals-quiz
---

# Java: Core Language & JVM Fundamentals

Java has quietly powered banks, Android, and countless backend systems for decades, and the language keeps evolving with records, sealed classes, and virtual threads. This quiz walks through the core language, the JVM, collections, generics, streams, and modern features. Read each question carefully, pick the single best answer, and use the explanations to sharpen your mental model.

## Questions

### 1. Which OOP principle is best described as hiding internal state and exposing behavior through methods?

- Inheritance, because a subclass hides its parent.
  - Inheritance is about reusing and extending behavior, not hiding state.
- **Encapsulation, by keeping fields private and using accessors.** ✅
  - Private fields plus public methods bundle data with the code that governs it.
- Polymorphism, because one method has many forms.
  - Polymorphism is about many implementations behind one interface, not hiding state.
- Abstraction, because it removes all implementation.
  - Abstraction models the essential shape; encapsulation is what actually hides the fields.

**Explanation:** Encapsulation keeps fields private and mediates access through methods, protecting invariants.

**Hint:** Think about the keyword you put in front of a field to protect it.

### 2. What does the `@Override` annotation give you when redefining a method from a superclass?

- It makes the method run faster at the JVM level.
  - It has no effect on runtime performance; it is a compile-time check.
- It forces the method to be called before the parent method.
  - Call order is controlled by your code with `super`, not by the annotation.
- **It makes the compiler verify the method actually overrides something.** ✅
  - If the signature does not match a superclass method, compilation fails, catching typos.
- It automatically calls the superclass version for you.
  - You must call `super.method()` yourself; the annotation does not do it.

**Explanation:** `@Override` is a safety net: the compiler rejects the code if no matching method exists to override.

**Hint:** It is a compile-time contract, not a runtime behavior.

### 3. Which statement about `abstract` classes versus interfaces in modern Java is correct?

- A class can extend multiple abstract classes.
  - Java allows single class inheritance only; you can extend one class.
- Interfaces can never contain any method body.
  - Since Java 8 interfaces can have `default` and `static` methods with bodies.
- **A class can implement multiple interfaces but extend only one class.** ✅
  - Multiple interface implementation is how Java approximates multiple inheritance of type.
- Abstract classes cannot have constructors.
  - Abstract classes can and often do define constructors for subclasses to call.

**Explanation:** Java permits one superclass but many interfaces, which is why interfaces are the tool for mixing in capabilities.

**Hint:** Count how many of each a single class is allowed to inherit from.

### 4. What is autoboxing in Java?

- **The automatic conversion between a primitive and its wrapper class.** ✅
  - For example `int` to `Integer` happens automatically when a wrapper is expected.
- The JVM wrapping every object in a try-catch block.
  - That describes exception handling, not autoboxing.
- Packaging classes into a JAR at build time.
  - Bundling into a JAR is an archiving step, unrelated to boxing.
- Copying a primitive onto the heap for garbage collection.
  - Autoboxing does create a wrapper object, but the term names the automatic conversion itself.

**Explanation:** Autoboxing and unboxing let primitives and their wrapper types be used interchangeably where the compiler can convert.

**Hint:** Think about assigning an `int` into an `Integer` variable.

### 5. Why does `new String("hi") == "hi"` evaluate to `false`?

- The two strings contain different characters.
  - Both hold the same characters; the difference is identity, not content.
- **`==` compares object references, and `new` creates a distinct object outside the pool.** ✅
  - The literal is interned in the string pool; `new` forces a separate heap object.
- String literals are always null until assigned.
  - The literal `"hi"` is a valid non-null String object.
- `==` on strings always returns false in Java.
  - Two references to the same interned literal compare equal with `==`.

**Explanation:** `==` tests reference identity; use `.equals()` to compare String content. The string pool interns literals, but `new` bypasses it.

**Hint:** Ask whether `==` compares what the objects hold or where they live.

### 6. Which type is a reference type rather than a primitive?

- boolean
  - `boolean` is one of the eight primitive types.
- double
  - `double` is a primitive floating-point type.
- char
  - `char` is a primitive 16-bit type.
- **Integer** ✅
  - `Integer` is a wrapper class, so it is a reference type stored on the heap.

**Explanation:** The eight primitives (byte, short, int, long, float, double, boolean, char) are value types; their capitalized wrappers are reference types.

**Hint:** Capitalization is a strong clue here.

### 7. Which collection guarantees no duplicate elements and, by default, no defined iteration order?

- ArrayList
  - ArrayList allows duplicates and keeps insertion order.
- **HashSet** ✅
  - A HashSet rejects duplicates and gives no ordering guarantee.
- LinkedList
  - LinkedList is an ordered list that permits duplicates.
- TreeMap
  - TreeMap is a sorted map of key-value pairs, not a set of elements.

**Explanation:** HashSet is backed by a hash table: elements are unique and iteration order is unspecified. Use LinkedHashSet for insertion order or TreeSet for sorted order.

**Hint:** The name tells you both the backing structure and the uniqueness guarantee.

### 8. What is the average time complexity of `get(key)` on a well-distributed `HashMap`?

- **O(1) on average.** ✅
  - Hashing takes you near-directly to the bucket, giving amortized constant time.
- O(log n) always.
  - That is TreeMap, which is backed by a balanced tree.
- O(n) because it scans every entry.
  - A linear scan happens only in a pathological all-collision bucket.
- O(n log n) due to sorting on access.
  - HashMap does not sort its keys at all.

**Explanation:** A HashMap resolves keys through hashing for average O(1) access; heavy collisions can degrade a bucket, which modern Java mitigates by treeifying long buckets to O(log n).

**Hint:** Think about what hashing buys you versus a sorted tree.

### 9. Which interface should a class implement so its instances can be stored as keys in a `HashMap` and found reliably?

- **It must override both `equals()` and `hashCode()`.** ✅
  - HashMap uses `hashCode()` to find the bucket and `equals()` to match within it.
- It must implement `Comparable` only.
  - `Comparable` matters for sorted structures like TreeMap, not HashMap lookups.
- It must implement `Serializable`.
  - Serialization is about persistence, not map key lookup.
- It must implement `Iterable`.
  - `Iterable` lets you loop over a collection, not act as a key.

**Explanation:** Consistent `equals()` and `hashCode()` are the contract that makes hash-based collections work; overriding one without the other breaks lookups.

**Hint:** Two methods must agree with each other for hashing to work.

### 10. What does the bounded wildcard `List<? extends Number>` allow?

- **Reading elements as `Number`, but not adding elements (except null).** ✅
  - The exact subtype is unknown, so the compiler blocks additions to keep it type-safe.
- Adding any `Number` subtype freely.
  - You cannot add, because the list might be `List<Integer>` and you might add a `Double`.
- Storing only `String` values.
  - String is not a Number, so it is excluded entirely.
- Changing the list to hold any type at runtime.
  - Generics are compile-time; the wildcard does not loosen types at runtime.

**Explanation:** An upper-bounded wildcard is a producer: you can read `Number` out, but the unknown subtype means you cannot safely put anything in. This is the "PECS" rule.

**Hint:** Producer-extends: think about whether you are reading or writing.

### 11. What is type erasure in Java generics?

- **Generic type parameters are removed at compile time and not present in bytecode.** ✅
  - The compiler enforces types then erases them, so `List<String>` and `List<Integer>` share one runtime class.
- The JVM deletes unused classes to save memory.
  - That is closer to class unloading, not type erasure.
- Generics are checked at runtime through reflection.
  - Because of erasure, the parameter type is generally not available at runtime.
- The compiler erases the whole class if it is never instantiated.
  - Erasure removes type parameters, not entire classes.

**Explanation:** Type erasure keeps generics backward compatible: type checks happen at compile time, then the parameters vanish, which is why you cannot do `new T()` or check `instanceof List<String>`.

**Hint:** Ask what survives from a generic type into the compiled bytecode.

### 12. What is the key difference between a checked and an unchecked exception?

- **Checked exceptions must be declared or handled; unchecked ones need not be.** ✅
  - Checked exceptions extend `Exception`; the compiler enforces catch-or-declare.
- Unchecked exceptions cannot be caught.
  - You can catch a `RuntimeException` like any other; you just are not forced to.
- Checked exceptions only occur in the JVM, not user code.
  - User code routinely throws checked exceptions such as `IOException`.
- There is no real difference; the terms are interchangeable.
  - The catch-or-declare requirement is a concrete compiler-enforced distinction.

**Explanation:** Checked exceptions (subclasses of `Exception` but not `RuntimeException`) force handling at compile time; unchecked exceptions (subclasses of `RuntimeException`) do not.

**Hint:** One kind makes the compiler nag you; the other slips past it.

### 13. In a `try`-with-resources statement, when is a resource closed?

- **Automatically at the end of the try block, in reverse order of creation.** ✅
  - Any `AutoCloseable` opened in the header is closed before the block exits, even on exception.
- Only if you call `.close()` in a `finally` block yourself.
  - The whole point is that you no longer write the `finally` close.
- When the garbage collector runs.
  - GC timing is nondeterministic; try-with-resources is deterministic.
- Never; the resource stays open until the program exits.
  - It is closed promptly and reliably at the end of the block.

**Explanation:** Resources declared in the try header are closed automatically in reverse order, which eliminates leaked file handles and connections.

**Hint:** The resource must implement `AutoCloseable` for this to work.

### 14. What does the Java compiler (`javac`) produce from a `.java` source file?

- Native machine code for the current CPU.
  - Native code is produced later by the JIT, not by `javac`.
- **Platform-independent bytecode in a `.class` file.** ✅
  - Bytecode runs on any JVM, which is the basis of "write once, run anywhere".
- An executable binary you can run directly on the OS.
  - You still need a JVM to run the `.class` bytecode.
- Optimized assembly tuned for the local machine.
  - That is the JIT compiler at runtime, not `javac`.

**Explanation:** `javac` emits portable bytecode; the JVM then interprets it and the JIT compiles hot paths to native code at runtime.

**Hint:** The output runs on the JVM, not the bare CPU.

### 15. What is the primary job of the JIT compiler in the JVM?

- **To compile frequently executed bytecode into native code at runtime.** ✅
  - Hot methods get compiled and optimized so they run at near-native speed.
- To translate Java source into bytecode.
  - That is `javac`, which runs before the JVM starts.
- To free unused objects from the heap.
  - That is the garbage collector, a separate subsystem.
- To load `.class` files from disk.
  - That is the class loader, not the JIT.

**Explanation:** The Just-In-Time compiler profiles running code and compiles hot bytecode to optimized native instructions, closing much of the gap with ahead-of-time languages.

**Hint:** Just-In-Time hints that the work happens while the program runs.

### 16. Which statement about garbage collection in the JVM is accurate?

- **The JVM reclaims objects that are no longer reachable from any live reference.** ✅
  - Reachability, not scope, decides eligibility for collection.
- You must manually free every object with a `delete` call.
  - Java has no `delete`; memory management is automatic.
- Calling `System.gc()` guarantees immediate collection.
  - `System.gc()` is only a hint the JVM may ignore.
- Objects are collected the instant they leave scope.
  - Collection timing is decided by the GC, not by scope exit.

**Explanation:** The garbage collector frees objects once they become unreachable; generational collectors exploit the fact that most objects die young.

**Hint:** The question is whether something can still be reached, not whether it is in scope.

### 17. What is a functional interface in Java?

- **An interface with exactly one abstract method.** ✅
  - A single abstract method lets a lambda or method reference implement it.
- Any interface annotated with `@Override`.
  - `@Override` marks methods, not functional interfaces.
- An interface that only contains static methods.
  - A functional interface must have one abstract method, not zero.
- An interface that extends `Function` by name.
  - The single-abstract-method rule is what counts, regardless of ancestry.

**Explanation:** A functional interface has one abstract method (optionally many default/static ones), which is what makes it a valid lambda target. `@FunctionalInterface` documents and enforces this.

**Hint:** Count the abstract methods a lambda would need to implement.

### 18. What does the intermediate `Stream` operation `map` do?

- **Transforms each element into a new value, producing a new stream.** ✅
  - `map` applies a function element-by-element, one output per input.
- Removes elements that fail a predicate.
  - That is `filter`, not `map`.
- Collects the stream into a `Map`.
  - Collecting into a Map is a terminal `collect(Collectors.toMap(...))`.
- Immediately runs the pipeline and returns a list.
  - `map` is intermediate and lazy; a terminal operation triggers execution.

**Explanation:** `map` is a lazy, intermediate transformation: it returns a new stream where each element has been passed through the mapping function.

**Hint:** One element in, one transformed element out.

### 19. Why are most Stream intermediate operations described as "lazy"?

- **They do no work until a terminal operation is invoked.** ✅
  - The pipeline is only executed when a terminal operation like `collect` or `forEach` pulls data through.
- They always run on a background thread.
  - Streams are sequential unless you call `parallel()`; laziness is unrelated to threading.
- They cache every result forever.
  - A stream is single-use, not a cache.
- They skip elements at random to save time.
  - Laziness means deferred execution, not skipping data.

**Explanation:** Intermediate operations build a description of the pipeline; nothing runs until a terminal operation triggers it, which enables fusion and short-circuiting.

**Hint:** Nothing happens until you ask for a result.

### 20. What does the `synchronized` keyword guarantee for a block of code?

- **Mutual exclusion on the monitor lock plus visibility of changes across threads.** ✅
  - Only one thread holds the monitor at a time, and exiting flushes changes to main memory.
- That the method runs faster under load.
  - Locking adds overhead; it trades speed for correctness.
- That exceptions inside the block are swallowed.
  - Exceptions still propagate; the lock is simply released.
- That the block runs on a dedicated thread.
  - It runs on the calling thread, just under a lock.

**Explanation:** `synchronized` provides both mutual exclusion (one thread in the critical section) and a happens-before memory guarantee so updates are visible to the next thread.

**Hint:** It solves two problems at once: races and stale reads.

### 21. What does declaring a field `volatile` guarantee?

- **Reads and writes go to main memory, so other threads see the latest value.** ✅
  - It guarantees visibility and ordering, but not atomicity of compound actions.
- Compound operations like `count++` become atomic.
  - `volatile` does not make read-modify-write atomic; use an atomic class or a lock.
- The field can never be modified after initialization.
  - That describes `final`, not `volatile`.
- The field is stored on disk instead of memory.
  - `volatile` is a memory-visibility modifier, not persistence.

**Explanation:** `volatile` ensures visibility and prevents reordering for that field, but it does not make compound operations atomic, which is a common source of bugs.

**Hint:** It fixes stale reads but not `x++`.

### 22. Why is an `ExecutorService` preferred over creating a `new Thread()` for each task?

- **It reuses a managed pool of threads instead of spawning unbounded new ones.** ✅
  - Pooling caps resource use and amortizes thread-creation cost.
- It runs tasks without using any threads at all.
  - It still uses threads; it just manages them for you.
- It makes every task run in a guaranteed order.
  - Task ordering depends on the pool and scheduling, not the executor by itself.
- It disables the garbage collector during execution.
  - It has no effect on garbage collection.

**Explanation:** An `ExecutorService` decouples task submission from thread management, reusing a bounded pool so you avoid the cost and risk of unbounded manual thread creation.

**Hint:** Think about what happens if a busy server creates a new thread per request.

### 23. What does a Java `record` primarily give you?

- **A concise immutable data carrier with generated constructor, accessors, `equals`, `hashCode`, and `toString`.** ✅
  - Records collapse boilerplate for classes that just hold data.
- A mutable class with public fields and setters.
  - Record components are final; records are shallowly immutable.
- A logging utility for writing to disk.
  - The name refers to a data record, not log records.
- An interface for database rows.
  - A record is a class, not an interface, and is unrelated to persistence.

**Explanation:** A record declares its state in the header and the compiler generates the constructor, accessors, and value-based `equals`/`hashCode`/`toString`, making it ideal for immutable DTOs.

**Hint:** It exists to kill boilerplate for plain data holders.

### 24. What does a `sealed` class or interface control?

- **Exactly which classes are permitted to extend or implement it.** ✅
  - A `permits` clause lists the allowed subtypes, enabling exhaustive handling.
- Whether the class can be garbage collected.
  - Sealing is about the type hierarchy, not memory.
- Whether fields are encrypted at runtime.
  - The keyword has nothing to do with encryption.
- That the class cannot be instantiated at all.
  - That would be `abstract`; sealed classes can be instantiated if concrete.

**Explanation:** A sealed type restricts its subtypes to a known set via `permits`, which lets the compiler verify exhaustive `switch` handling over the hierarchy.

**Hint:** It is about closing a hierarchy to a fixed list of subtypes.

### 25. What does the local variable `var` keyword do in `var list = new ArrayList<String>();`?

- **Infers the static type from the initializer at compile time.** ✅
  - The variable is statically typed as `ArrayList<String>`; `var` is not dynamic typing.
- Makes the variable dynamically typed like in JavaScript.
  - Java stays statically typed; the type is fixed at compile time.
- Declares a field usable across the whole class.
  - `var` is only for local variables, not fields.
- Forces the variable to be `final`.
  - `var` variables can be reassigned unless you also add `final`.

**Explanation:** `var` triggers local-variable type inference: the compiler fixes the static type from the right-hand side, keeping full type safety with less noise.

**Hint:** Static typing is preserved; only the annotation is omitted.

### 26. How does a Java `switch` expression (arrow form) differ from a classic `switch` statement?

- **It returns a value and does not fall through between arms.** ✅
  - Arrow labels yield a value and each case is isolated, eliminating accidental fall-through.
- It can only switch on `int` values.
  - Switch expressions support strings, enums, and patterns, not just ints.
- It requires a `break` after every case.
  - Arrow form removes the need for `break` entirely.
- It runs every matching case in sequence.
  - Only the matched arm runs; there is no fall-through.

**Explanation:** A switch expression uses `->` arms, produces a value, avoids fall-through, and can require exhaustiveness, making it safer and more concise than the statement form.

**Hint:** Think about fall-through and whether the construct yields a result.
