Chapter 8 Flashcards — General Programming

flashcards effective-java general-programming java performance


What is the key rule for local variable scope (Item 57)?
?
Declare every local variable as close to its first use as possible, always with an initializer. A variable declared without an initializer is a code smell — it means you cannot yet compute its value, which signals that the variable’s scope is too broad. Tight scope = smaller mental model for readers, fewer accidental reuse bugs.


How does a traditional for loop help scope iterator variables, and why is for-each even better (Item 57)?
?
A for loop’s init section (for (int i = 0; ...)) scopes the variable to the loop only — i is inaccessible after the closing brace. For-each is even better: the element variable is scoped to the loop body and no iterator variable is created at all, eliminating iterator reuse bugs. For-each = smallest possible scope for iteration variables.


What does var (Java 10+) do for local variable scope, and what is its risk with Item 64?
?
var infers the local variable’s type from the initializer, allowing more concise declarations. It supports Item 57 (tightest scope). Risk with Item 64: var names = new LinkedList<String>() infers LinkedList<String>, not List<String> — the interface type is lost. Prefer explicit interface types when the inferred concrete type would violate the “refer by interface” principle.


What are the three situations where for-each CANNOT be used (Item 58)?
?

  1. Destructive filtering: removing elements during iteration requires iterator.remove() or Collection.removeIf(Predicate) (Java 8+).
  2. In-place transformation: replacing elements by index requires a ListIterator.set().
  3. Parallel index traversal: walking two collections at the same index requires an explicit index variable.
    For all other cases, for-each is always preferred.

Is there a performance difference between a for-each loop and an explicit Iterator loop (Item 58)?
?
No — the compiler generates identical bytecode. Both compile to Iterator.hasNext() / Iterator.next() calls. For arrays, both compile to indexed access. For-each is purely a syntactic convenience with no runtime cost.


What Java 8+ method eliminates the need for an explicit iterator for destructive filtering (Item 58)?
?
Collection.removeIf(Predicate<? super E> filter) — removes all elements for which the predicate returns true. Example: list.removeIf(e -> e.isExpired()). Cleaner than a manual iterator loop; uses the collection’s native removal mechanism.


What is the problem with reimplementing random(int n) manually (Item 59)?
?
Naive implementations like Math.abs(random.nextInt()) % n are biased: they do not produce a uniform distribution for all values of n. Additionally, Math.abs(Integer.MIN_VALUE) is negative (overflow), causing occasional negative results. The correct solution already exists: ThreadLocalRandom.current().nextInt(n) (Java 7+) or Random.nextInt(n).


Name five classes/APIs added to the Java standard library between Java 9 and 17 that you should know (Item 59).
?

  1. java.net.http.HttpClient (Java 11) — built-in HTTP/1.1, HTTP/2, WebSocket client.
  2. Files.readString() / Files.writeString() (Java 11) — one-call file I/O.
  3. String.strip() / String.isBlank() / String.lines() (Java 11) — Unicode-aware string utilities.
  4. String.repeat(int) (Java 11) — repeat a string n times.
  5. List.copyOf(), Map.copyOf(), Set.copyOf() (Java 10) — defensive immutable copies.

Why can float and double not represent 0.1 exactly (Item 60)?
?
float and double use binary (base-2) floating-point. 0.1 in binary is an infinitely repeating fraction (like 1/3 in decimal), so it cannot be represented exactly in any finite number of bits. The closest representable double is approximately 0.1000000000000000055511151231257827.... Arithmetic on such approximations compounds errors.


Why must you use new BigDecimal("0.1") (String constructor) and not new BigDecimal(0.1) (double constructor) (Item 60)?
?
new BigDecimal(0.1) takes the double value 0.1, which is already the binary approximation (0.1000000000000000055511...), and creates a BigDecimal that represents that approximation exactly — not the decimal value 0.1. new BigDecimal("0.1") parses the decimal string "0.1" and represents exactly 0.1 in decimal.


When should you use int/long instead of BigDecimal for exact arithmetic (Item 60)?
?
When you can represent the values as integers with an implicit scale — for example, storing monetary amounts in cents (int dollars = 100 for 92 quadrillion in cents with long). You must track the scale manually throughout the code.


What are the three key differences between primitive types and boxed primitives (Item 61)?
?

  1. Identity vs. value: == on boxed types compares object references, not values. Two new Integer(42) instances are not ==. Use .equals() or Integer.compare().
  2. Nullability: boxed types can be null; primitives cannot. Unboxing a null boxed type throws NullPointerException.
  3. Performance: boxed types allocate objects on the heap; primitives do not. Autoboxing in loops generates massive GC pressure.

What specific bug does == on Integer cause in a Comparator (Item 61)?
?

Comparator<Integer> c = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
c.compare(new Integer(42), new Integer(42)); // returns 1, not 0!

The < comparison auto-unboxes correctly, but i == j compares references — two new Integer(42) objects have different identities. The fix: Comparator.comparingInt(Integer::intValue) or Integer::compare.


What happens when you unbox a null Integer (Item 61)?
?
A NullPointerException is thrown at the point of unboxing. Example: Integer i = null; if (i == 42) throws NPE when the JVM tries to unbox i for the == comparison. This is a silent bug when mixed with conditionals. Rule: if a boxed type might be null, always null-check before using it in arithmetic or comparison operations.


What Stream types exist for primitive types, and why do they exist (Item 61)?
?
IntStream, LongStream, and DoubleStream — these perform stream operations on primitive values without boxing. Stream<Integer> would box every int into an Integer object; IntStream operates on int values directly. Key methods: IntStream.rangeClosed(1, 100).sum() — no boxing, fast. mapToInt(), mapToLong(), mapToDouble() convert from Stream<T>.


What are four cases where Strings should NOT be used as a substitute for a better type (Item 62)?
?

  1. Enums: use enum for fixed sets of constants — type-safe, IDE-completable, not a typo-prone String.
  2. Numeric data: use int, long, double — operations are type-safe and fast; no parsing required.
  3. Structured aggregate data: use a class or record — fields are individually accessible and typed.
  4. Capability/access tokens: use a new Object() reference — each instance is guaranteed unique (unlike String literals which may be interned and shared).

Why is a compound key like className + "#" + delimiter a bad use of String (Item 62)?
?
It is fragile — if className or delimiter contain the separator character "#", the key is ambiguous and parsing breaks. There is no compile-time enforcement that the format is correct. A proper class or record models the key with typed fields and a well-defined equals/hashCode, eliminating parsing and the separator problem entirely.


Why is string concatenation with + in a loop O(n²) (Item 63)?
?
Each + creates a new String object containing all previous characters plus the new ones. In a loop over n items, the k-th concatenation copies all k-1 previous characters plus the new ones — total character copies = 1 + 2 + … + n = n(n+1)/2 = O(n²). For 1000 items this is ~500,000 character copies; for 10,000 items it’s ~50,000,000.


What is the correct replacement for string concatenation in a loop (Item 63)?
?
Use StringBuilder. Pre-size it if you know the approximate final length: new StringBuilder(numItems * avgLineLength). Call sb.append(...) in the loop, then sb.toString() at the end. Total character copies: O(n) — each character is written once.


When is + string concatenation acceptable (Item 63)?
?
In a single expression with a fixed number of operands — e.g., "Hello, " + name + "!". The compiler transforms this to a single StringBuilder chain at compile time, so no intermediate String objects are created. The rule applies only to loops or recursive concatenation patterns where new String objects accumulate.


What Java 15+ features reduce the need for StringBuilder or String.format() (Item 63)?
?

  • String.formatted(args...) (Java 15+) — instance method equivalent of String.format(this, args). More readable for short templates: "Hello, %s!".formatted(name).
  • Text blocks (Java 15+) — triple-quoted multi-line string literals. Eliminate concatenation for SQL, JSON, HTML, and other structured string constants. Indentation is handled automatically.

What is the rule in Item 64, and what is the exception?
?
Rule: Use interface types for local variables, fields, parameters, and return types. List<String> not ArrayList<String>; Map<K,V> not HashMap<K,V>. Exception: use the concrete type when you need methods specific to that class that are not part of any interface (e.g., TreeMap.floorKey()), or when no appropriate interface exists (e.g., PriorityQueue, String, BigDecimal).


How does var (Java 10+) interact with Item 64’s “refer to objects by their interfaces” rule?
?
var infers the concrete type from the initializer: var names = new LinkedList<String>() gives names the type LinkedList<String>, not List<String>. This violates Item 64. Prefer: List<String> names = new LinkedList<>() — explicit interface type on the left, concrete implementation on the right.


What are the three legitimate uses of reflection (Item 65)?
?

  1. Framework infrastructure: dependency injection (Spring), serialization (Jackson), testing (JUnit) — frameworks instantiate your classes reflectively at startup.
  2. Plugin systems: class names from configuration files — the class to instantiate is unknown at compile time.
  3. Optional dependencies: reflect to check if a class is available on the classpath; if present, use it via an interface.
    In all three, the pattern is: reflect only to instantiate; use an interface for all subsequent operations.

What are the costs of reflection compared to direct method invocation (Item 65)?
?

  1. No compile-time type checking — type errors are ClassNotFoundException/NoSuchMethodException at runtime.
  2. No IDE support — no autocomplete, no refactoring, no “find usages.”
  3. Verbose exception handlingMethod.invoke() declares multiple checked exceptions.
  4. Performance overhead — reflective invocation is 5-100x slower than direct invocation; JIT cannot optimize across reflective call boundaries.

What did Java 9+ strong encapsulation do to reflective access, and how do you grant it explicitly (Item 65)?
?
Java 9+ modules deny reflective access to non-exported packages by default. In Java 17, the --illegal-access flag that softened this was removed. Code that reflects into JDK internals (sun.misc, com.sun.) now throws InaccessibleObjectException. To explicitly grant reflective access: add opens com.example.impl to other.module; in module-info.java. For JVM flags: --add-opens java.base/java.lang=ALL-UNNAMED.


What are three reasons to avoid JNI (Item 66)?
?

  1. Not portable — native code must be compiled per OS/CPU architecture; Java’s “write once, run anywhere” is lost.
  2. Not memory-safe — native code bypasses GC and type safety; bugs cause JVM crashes, buffer overflows, undefined behavior.
  3. Hard to debug — crashes in native code produce core dumps requiring native debuggers; debugging across the JNI boundary is painful and tool support is weak.

What Java feature is intended to replace JNI (Item 66)?
?
Project Panama’s Foreign Function & Memory API — incubator in Java 17, preview in Java 19/20, stable in Java 22. It provides type-safe access to native functions and native memory without writing C code or using JNI. MethodHandles-like MethodHandle objects call native functions; MemorySegment manages native memory with Java lifetime control. Much safer and easier than raw JNI.


What is Knuth’s rule about optimization (Item 67), and what are the three rules of optimization?
?
Knuth (quoting Hoare): “Premature optimization is the root of all evil.” The three rules:

  1. Don’t do it.
  2. (For experts only) Don’t do it yet.
  3. Profile to find the actual bottleneck; optimize only what is proven slow; measure before and after.
    Algorithmic improvements (better Big-O) beat micro-optimizations every time. The JIT compiler already performs many micro-optimizations automatically.

Why is System.currentTimeMillis() unreliable for Java micro-benchmarks (Item 67)?
?
JVM warm-up: early iterations are interpreted (not JIT-compiled). If you time those iterations, you measure interpreter performance, not optimized JIT performance. System.currentTimeMillis() also has ~10ms resolution on many systems — too coarse for micro-benchmarks. JMH (Java Microbenchmark Harness) handles warm-up, dead code elimination prevention, and statistical analysis. Always use JMH for method-level performance measurements.


What is the naming convention for record component accessors, and how does it differ from JavaBean convention (Item 68)?
?
Record accessors are named after the component with no prefix: record Point(int x, int y)point.x(), not point.getX(). JavaBean convention requires a get prefix: getX(). Records intentionally break from JavaBean naming to signal that they are value/data carriers, not mutable beans. Frameworks expecting getX() names may need configuration to use records correctly.


Fill in the naming conventions table:

TypeConvention
Package?
Class/Interface/Record?
Enum type?
Enum constant?
Method?
Boolean method?
Constant field (static final)?
Local variable?
Type parameter?
?
TypeConvention
------
Packagelowercase.dotted.reverse.domain — e.g., com.example.util
Class/Interface/RecordUpperCamelCase noun phrase — e.g., UserAccount, ArrayList
Enum typeUpperCamelCase — e.g., DayOfWeek, RoundingMode
Enum constantSCREAMING_SNAKE_CASE — e.g., MONDAY, HALF_UP
MethodlowerCamelCase verb phrase — e.g., computeValue(), getUserName()
Boolean methodlowerCamelCase with is, has, can prefix — e.g., isActive(), hasNext()
Constant fieldSCREAMING_SNAKE_CASE — e.g., MAX_RETRIES, DEFAULT_TIMEOUT_MS
Local variablelowerCamelCase, brief — e.g., i, result, userName
Type parameterSingle uppercase letter or short descriptor — T, E, K, V, R, N extends Number

What are the conventional single-letter type parameter names and what does each represent (Item 68)?
?

  • TType (generic type, catch-all)
  • EElement (element type in collections: List<E>, Set<E>)
  • KKey (in map: Map<K, V>)
  • VValue (in map: Map<K, V>)
  • RResult (return type of functions: Function<T, R>)
  • NNumeric (bounded: N extends Number)
  • X — eXception type (catch blocks, throws bounds)
    Multi-letter descriptors like T1, T2 or KeyType are used when a single letter is ambiguous.

What naming prefix/suffix conventions apply to static factory methods (Item 68)?
?

  • of — aggregate, takes arguments of the type’s constituents: EnumSet.of(...), List.of(...)
  • from — type conversion from a single parameter: Date.from(instant)
  • valueOf — value conversion: Integer.valueOf(42), BigInteger.valueOf(n)
  • getInstance — returns an instance, possibly shared: Calendar.getInstance()
  • newInstance — always a new instance: Array.newInstance(class, length)
  • create / newType — factory returning a new instance of a specific type
  • getType — factory where the type differs from the factory class: Files.getFileStore(path)


Total Cards: 35
Review Time: ~22 minutes
Priority: HIGH
Last Updated: 2026-05-10