# Idioms & Common Gotchas - Write It Like a Java Dev, Dodge the Traps

You can write Java that compiles and runs. This phase closes the gap between *that* and code that looks like a seasoned Java developer wrote it - plus the short list of traps that have caught every Java programmer alive. (Genuinely - the `==` versus `.equals()` one alone has cost the industry more debugging hours than anyone wants to count. You're about to skip past it.)

Two halves. First the **idioms** - conventions that make Java code feel coherent instead of arbitrary. The mental model: *Java rewards small contracts and unchanging data*. Depend on interfaces, not concrete classes; make things final and immutable so they can't change behind your back; say "no value" out loud with `Optional` instead of the silent landmine that is `null`.

Then a scannable **gotcha cheat-card** - surprises named *before* they bite, so you recognize one instead of staring at a stack trace. The mental model: *Java does exactly what the rules say, not what you assumed.*

## Idioms - the way it's written today

### Program to interfaces, not implementations

**What it actually is.** When declaring a variable, parameter, or return type, name the *interface* (`List`, `Map`, `Collection`) rather than the concrete class (`ArrayList`, `HashMap`). You still *create* the concrete thing with `new` - just don't let the rest of your code depend on which one.

```java
// Idiomatic: the type is the contract, not the implementation.
List<String> names = new ArrayList<>();

// Not idiomatic: now everything downstream is welded to ArrayList.
ArrayList<String> names2 = new ArrayList<>();
```
*What just happened:* both lines create an `ArrayList`, but the first *types* the variable as `List`. A method taking `List<String>` accepts an `ArrayList`, a `LinkedList`, or `List.of(...)` without changes, and you swap implementations later by editing one line. That's why you'll see `void process(List<Order> orders)` everywhere and almost never `void process(ArrayList<Order> orders)`.

💡 **Key point.** Declare with the most general interface that does the job (`List`, `Map`, `Set`, `Collection`), construct with the concrete class. Flexible outside, specific inside.

### Prefer immutability

**What it actually is.** An *immutable* object's state can't change after it's built. Mark fields `final`, don't expose setters, assign everything in the constructor - once it exists, it's frozen.

```java
public final class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }

    // "Change" returns a NEW Point instead of mutating this one.
    public Point withX(int newX) {
        return new Point(newX, y);
    }
}
```
*What just happened:* `Point` has no setters and every field is `final`, so once constructed it never changes. Need a different `x`? `withX` hands you a brand-new `Point`, leaving the original untouched. Sounds wasteful, but immutable objects are automatically thread-safe, safe to share and cache, and never in a half-updated state. When in doubt, make value objects immutable. (In [Phase 13](13-records-and-modern-java.md), `record` automates this pattern down to one line.)

⚠️ **Gotcha - `final` on a reference freezes the *reference*, not the object.** `final List<String> items = new ArrayList<>();` stops you reassigning `items`, but `items.add(...)` still works all day. `final` means "this variable always points at the same object," not "that object can't change." True immutability needs the object itself unchangeable (`List.copyOf(items)`).

### Use `Optional` instead of returning `null`

**What it actually is.** `Optional<T>` is a small box holding a value or explicitly empty. When a method might legitimately have "no answer" (no user found, no config set), returning `Optional<User>` instead of `User` (which might secretly be `null`) makes the absence *visible in the type*.

```java
import java.util.Optional;

Optional<String> findNickname(String user) {
    if (user.equals("ada")) return Optional.of("Countess");
    return Optional.empty();              // "no value" - said out loud
}

// Caller is forced to deal with the empty case:
String shown = findNickname("bob").orElse("(none)");
System.out.println(shown);
```
```console
(none)
```
*What just happened:* `findNickname` returns an `Optional<String>`, so its signature *announces* "there might be nothing here." The caller used `.orElse("(none)")` for a fallback. Compare that to returning `null`: nothing warns you, and the day you forget a null check, you get a `NullPointerException` in production instead of a compiler nudge.

💡 **Key point.** Use `Optional` for *return types* that may be absent. Skip it for fields or parameters (it adds ceremony without payoff there), and never call `.get()` without checking first - that just reinvents the NPE.

### Loop with the enhanced for and streams

**What it actually is.** The enhanced for-loop (`for (var x : items)`) iterates without a manual index. Streams go further: describe *what* to do to a collection - filter, map, collect - instead of spelling out *how* with a counter.

```java
import java.util.List;

List<String> names = List.of("ada", "bob", "cleo");

// Enhanced for - no index to fat-finger.
for (var name : names) {
    System.out.println(name.toUpperCase());
}

// Stream - describe the transformation as a pipeline.
List<String> longOnes = names.stream()
        .filter(n -> n.length() > 3)
        .toList();
System.out.println(longOnes);
```
```console
ADA
BOB
CLEO
[cleo]
```
*What just happened:* the enhanced for-loop walked the list with no `int i` to manage and no off-by-one risk. The stream expressed "keep names longer than three characters" as a readable pipeline - `filter` describes *intent*, not mechanics. Streams shine when chaining transformations; for a simple walk, the enhanced for is perfectly idiomatic. (Streams and lambdas get their own treatment in [Phase 12](12-streams-api.md).)

### Write meaningful `equals`, `hashCode`, and `toString`

**What it actually is.** By default, `equals` checks *identity*, and `toString` prints something useless like `Point@1b6d3586`. If two objects with the same field values should count as "equal" - especially bound for a `HashMap` or `HashSet` - override `equals` and `hashCode` together.

```java
import java.util.Objects;

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Point p)) return false;
    return x == p.x && y == p.y;
}

@Override
public int hashCode() {
    return Objects.hash(x, y);     // must agree with equals
}

@Override
public String toString() {
    return "Point(" + x + ", " + y + ")";
}
```
*What just happened:* `equals` now compares actual field values, so two `Point(1, 2)` objects are equal despite being separate objects in memory. `hashCode` uses the *same* fields - non-negotiable, since hash-based collections use `hashCode` to find the bucket and `equals` to confirm the match. Disagree, and your objects vanish into a `HashMap` unrecoverably. `toString` makes debugging humane. ([Records](13-records-and-modern-java.md) generate all three; until then, honor this contract.)

> 💡 The umbrella idiom: make intent explicit, illegal states impossible. Interfaces narrow the contract, immutability removes a whole class of "who changed this?" bugs, `Optional` makes absence visible, proper `equals`/`hashCode` makes equality mean what you mean. Favor composition over deep inheritance - clarity over cleverness.

## The gotcha cheat-card

> **Hit something baffling? Find the symptom here.** These trap *everyone* - recognizing them on sight is most of the battle.

| The trap | What bites you | The fix |
|---|---|---|
| `==` vs `.equals()` | `==` compares references, so equal-looking objects/strings can be "not equal" | Compare objects with `.equals()`; reserve `==` for primitives and identity |
| `null` / NPE | Calling a method on `null` throws `NullPointerException` at runtime | `Optional`, explicit null checks, `Objects.requireNonNull` on inputs |
| Autoboxing surprises | `Integer` caches −128..127, so `==` "works" then mysteriously breaks; unboxing a `null Integer` throws NPE | Use `.equals()` for `Integer`; keep arithmetic in primitives |
| Mutating a list while looping it | Removing during a for-each throws `ConcurrentModificationException` | Use `Iterator.remove()`, `removeIf(...)`, or collect-then-remove |
| Integer division | `5 / 2` is `2`, not `2.5` - the fraction is silently discarded | Cast one operand to `double`: `5 / 2.0` |
| Shared mutable state / arrays | Passing a list or array hands over a reference; the callee can mutate your data | Defensive copies (`List.copyOf`) or immutable objects |
| Catching `Exception` too broadly | A blanket `catch (Exception e) {}` swallows bugs and hides real failures | Catch specific types; never leave a catch block empty |

Now the *why* behind the sharpest three.

### `==` vs `.equals()`

The big one. `==` asks "are these the *same object* in memory?" `.equals()` asks "do these represent the *same value*?" For primitives `==` is correct and the only option. For *objects* - including `String` - `==` almost never means what you want.

```java
String a = new String("hello");
String b = new String("hello");

System.out.println(a == b);        // false - two different objects
System.out.println(a.equals(b));   // true  - same characters
```
```console
false
true
```
*What just happened:* `new String("hello")` built two separate `String` objects holding the same characters. `==` compared *references* - different objects, `false`. `.equals()` compared *contents* - same text, `true`. Dangerous because string *literals* (`"hello" == "hello"`) often return `true`, since Java pools identical literals, lulling beginners into thinking `==` works on strings. Then a string comes from user input, the pool doesn't apply, and `==` quietly returns `false`. **Always compare strings and objects with `.equals()`.**

### Autoboxing and the `Integer` cache

Java auto-converts between primitives (`int`) and object wrappers (`Integer`) - *autoboxing*. Convenient, and the source of two classic traps. First, `==` on two `Integer` objects compares references, not values - but the JVM *caches* small `Integer`s from −128 to 127, so `==` accidentally "works" in that range and breaks above it.

```java
Integer a = 100, b = 100;
System.out.println(a == b);    // true  - both from the cache (-128..127)

Integer c = 200, d = 200;
System.out.println(c == d);    // false - outside cache, two real objects
System.out.println(c.equals(d)); // true - value comparison, correct
```
```console
true
false
true
```
*What just happened:* `100` falls inside the cached range, so `a` and `b` point at the *same* cached `Integer` and `==` is `true`. `200` is outside the cache, so `c` and `d` are *separate* objects and `==` is `false` - even though both hold 200. Vicious, since it passes every test with small numbers and fails in production on large ones. Use `.equals()` for wrappers. Second trap: unboxing a `null` `Integer` (`int x = someInteger;` when null) throws a `NullPointerException` from a line that never mentions null. Keep arithmetic in primitives and sidestep both.

### Integer division

Dividing two `int`s gives an `int` - Java discards the remainder instead of producing a fraction. Bites every beginner computing an average or a percentage.

```java
int total = 5, count = 2;

System.out.println(total / count);          // 2   - fraction discarded!
System.out.println((double) total / count); // 2.5 - cast first
```
```console
2
2.5
```
*What just happened:* `5 / 2` is integer arithmetic, so the result is `2` with the `.5` silently dropped - no error, no warning, just a wrong number. Casting one operand to `double` forces *floating-point* division and gets `2.5`. The rule: for a fractional result, make at least one operand a `double` *before* the division happens. `(double)(total / count)` is too late - integer division already ran.

📝 **The other three, in one line each.** **`ConcurrentModificationException`** - removed from a list inside a for-each loop; use `list.removeIf(...)` or `Iterator.remove()`. **Shared mutable state** - handing out your internal list or array lets the receiver mutate it; return `List.copyOf(...)` instead. **Over-broad catch** - `catch (Exception e) {}` swallows the bug you need to see; catch the specific exception, never leave the block empty.

## Recap

1. **Program to interfaces** - declare `List`/`Map`, construct `ArrayList`/`HashMap`: narrow contracts, swappable implementations.
2. **Prefer immutability** - `final` fields, no setters, "change" returns a new object: thread-safe and bug-resistant by construction. (`final` on a reference freezes only the reference.)
3. **Return `Optional`, not `null`** - make "no value" visible in the type so callers can't forget it.
4. **Loop with enhanced for and streams**, and write real **`equals`/`hashCode`/`toString`** (matched pair for the first two) - records automate this later.
5. ⚠️ **The cheat-card** - `==` compares references (use `.equals()` for objects/strings); NPEs come from `null`; `Integer` caching makes `==` lie outside −128..127; `5/2` is `2`; shared references let others mutate your data; don't swallow exceptions.

That's idiomatic Java. You can now read other people's Java and write code that looks like it belongs - and you've met the traps before they meet you. Next: deep on **generics** - how `List<T>` really works, wildcards, and why the compiler sometimes argues about types.

## Quick check

Test yourself on the three traps that catch everyone:

```quiz
[
  {
    "q": "You have two String objects built with `new String(\"hi\")`. What do `a == b` and `a.equals(b)` return?",
    "choices": [
      "`a == b` is false (different objects), `a.equals(b)` is true (same characters)",
      "Both are true - strings always compare by value",
      "Both are false - the strings are stored separately",
      "`a == b` is true, `a.equals(b)` is false"
    ],
    "answer": 0,
    "explain": "`==` compares references, and `new String(...)` makes two distinct objects, so `a == b` is false. `.equals()` compares contents, so it's true. Always use `.equals()` for strings and objects."
  },
  {
    "q": "Why does `Integer a = 100, b = 100; a == b` print `true`, but `Integer c = 200, d = 200; c == d` print `false`?",
    "choices": [
      "Java caches Integer objects from −128 to 127, so 100 reuses one cached object while 200 creates two separate ones",
      "200 is too large to fit in an int, so it overflows",
      "`==` rounds large numbers differently",
      "It's undefined behavior and the result is random"
    ],
    "answer": 0,
    "explain": "The JVM caches small Integer objects (−128..127), so `a` and `b` are the same cached object and `==` is true. 200 is outside the cache, so `c` and `d` are separate objects and `==` is false. Use `.equals()` for wrapper objects."
  },
  {
    "q": "What does `5 / 2` evaluate to in Java, and how do you get `2.5`?",
    "choices": [
      "It's `2` (integer division discards the fraction); cast an operand to double, e.g. `5 / 2.0` or `(double) 5 / 2`",
      "It's `2.5` already - Java promotes to double automatically",
      "It's `3` - Java rounds to the nearest integer",
      "It throws an ArithmeticException for non-divisible numbers"
    ],
    "answer": 0,
    "explain": "Dividing two ints gives an int, dropping the remainder, so `5 / 2` is `2`. Force floating-point division by making at least one operand a double before the division runs."
  }
]
```
