# From C++ — five pointer shapes collapse into one, and one leak survives

In C++, writing the loan card's field is a design decision. `Copy`, `Copy&`, `Copy*`,
`std::unique_ptr<Copy>`, `std::shared_ptr<Copy>` — five spellings, and picking one commits you
to a lifetime story. Java offers one spelling. That is a smaller decision to
make. The trap is that Java writes it with the syntax of the one C++ option it does not have.

## The declaration

**C++ — the shape is yours to choose**

```cpp
class LoanCard {
    Copy copy_;                       // a whole book, copied in
    Copy& copy_;                      // borrowed, must outlive the card
    Copy* copy_;                      // borrowed, may dangle, may be null
    std::unique_ptr<Copy> copy_;      // the card owns it, shelf must not
    std::shared_ptr<Copy> copy_;      // shared, refcounted, cycles leak
};
```

**Java — one shape**

```java
public final class LoanCard {

    private final Copy copy;
}
```

| C++ | Java |
|---|---|
| `Copy c` is a value; sizeof is the object | `Copy c` is a reference; the object is always on the heap |
| `Copy* p` and `p->f()` | `Copy c` and `c.f()`. Same indirection, different punctuation |
| `Copy& r` — cannot be null, cannot be rebound | No equivalent. Every reference can be null and can be rebound |
| `const Copy& r` — a read-only view | No equivalent. `final` binds the variable, never the object |
| `unique_ptr` / `shared_ptr` in a signature announces ownership | A signature announces nothing about lifetime |
| Stack or heap is your call | Only primitives (`int`, `long`, `boolean`, `char`, `double`) are values |

**`final` is the row worth re-reading.** `private final Copy copy` says the field is assigned
once. It says nothing about the book. `copy.markDamaged()` through a `final` field compiles and
mutates, which is the opposite of what `const Copy&` gives you. There is no qualifier that
buys back the missing half.

## Assignment and passing

**C++ — the reader can tell from the declaration**

```cpp
Copy onTheShelf{"978-0132350884#1", "978-0132350884"};
Copy sameBook = onTheShelf;      // a second book. Two objects.
Copy& alias = onTheShelf;        // one book, two names, and the & says so
```

**Java — one line, and the declaration does not say**

```java
Copy onTheShelf = new Copy("978-0132350884#1", "978-0132350884");
Copy sameBook = onTheShelf;      // one book, two names
sameBook.markDamaged();
```

Real output from `worked/`:

```
onTheShelf.damaged()   : true
onTheShelf == sameBook : true
onTheShelf             : Copy[978-0132350884#1, damaged]
```

The C++ line that behaves this way needs an `&` in it. The Java line does not, and there is no
spelling of the assignment that would give you the copying version. `sameBook = new Copy(...)`
builds a different book; nothing in the language duplicates the one you have unless you write a
method that does it.

Parameters follow the same rule, and the two halves of it come apart in a way worth seeing:

```java
private static void inspect(Copy copy) {
    copy.markDamaged();                                  // the caller sees this
    copy = new Copy("978-0132350884#99", CLEAN_CODE);    // the caller sees nothing
    copy.markDamaged();
}
```

```
before             : Copy[978-0132350884#2, intact]
after inspect()    : Copy[978-0132350884#2, damaged]
barcode unchanged  : 978-0132350884#2
```

| C++ | Java |
|---|---|
| `void f(Copy c)` copies the object | `void f(Copy c)` copies the reference |
| `void f(Copy& c)` — callee can reassign the caller's variable | Impossible. There is no reference-to-reference |
| `void f(const Copy& c)` — callee cannot mutate | Impossible. Any non-private method is available to the callee |
| Out-parameters via `&` or `*` | Return a value, or a record of several |

## What genuinely disappears

**C++ — the shelf and the card both refer to one book, so somebody has to decide**

```cpp
class Shelf {
    std::vector<std::shared_ptr<Copy>> copies_;
    std::unordered_map<std::string, std::shared_ptr<LoanCard>> out_;
};

class LoanCard {
    std::shared_ptr<Copy> copy_;      // and Copy holds a weak_ptr back, or this cycle leaks
};
```

**Java — no decision, and the cycle is fine**

```java
Copy copy = new Copy("978-0198611868#1", DICTIONARY);
LoanCard card = new LoanCard(copy, "M-3", DUE);
copy.lentOn(card);                    // copy -> card -> copy
```

`worked/` drops both locals and asks the collector:

```
--- 6. a reference cycle, unreachable
collected after gc     : true
```

Four things that were part of the design in C++ are not design decisions here:

- **Ownership.** Nobody owns the book. Two collections hold references and neither is special.
- **`unique_ptr` versus `shared_ptr` in an interface.** A Java signature cannot express it, so
  reviewers never argue about it.
- **Dangling references.** A reference you can still read refers to a live object. Always.
- **Cycles.** Reachability is computed from the roots, not by counting. `weak_ptr` has no
  routine Java counterpart, and `WeakReference` exists for caches rather than for cycles.

Two things do **not** disappear. Files, sockets and locks still need release, and that is
`try`-with-resources, which is J9. And reachability is now your problem, which is the last
section.

## `null` against `nullptr`

The consequence differs, not the concept. `nullptr` is one legal value of a pointer, and C++
gives you `Copy&` when you want to rule it out. Java has no such type, so `null` is a legal
value of every reference type you will ever declare.

**C++ — the type system can refuse**

```cpp
Copy& find(const std::string& barcode);      // cannot return null. Throws instead
Copy* find(const std::string& barcode);      // may return null, and the * warns the reader
int copiesHeld = nullptr;                    // error: cannot initialize 'int' with 'nullptr_t'
```

**Java — the declaration cannot refuse**

```java
public Copy copyWithBarcode(String barcode) {   // may return null, and nothing says so
    ...
}
```

```java
Copy copy = null;                 // legal for every reference type
int copiesHeld = null;            // the one place it is rejected
```

Only the primitive is caught, and this is the whole of the compiler's help:

```
Prim.java:3: error: incompatible types: <null> cannot be converted to int
        int copiesHeld = null;
                         ^
1 error
```

Now the part that matters. This file compiles with `javac -Xlint:all`, **zero warnings**, and
`javac` exits 0:

```java
public class NullDesk {
    public static void main(String[] args) {
        Copy copy = null;
        System.out.println(copy.barcode());
    }
}
```

The failure is at the first `.`, at run time. Java 21 names the expression that was null, and
reading the message carefully saves you the debugger:

```
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Copy.barcode()" because the return value of "Shelf.copyWithBarcode(String)" is null
	at Desk.main(Desk.java:8)
```

Two shapes of that message, both from `worked/`:

```
through a local        : Cannot invoke "Copy.barcode()" because "<local1>" is null
through a call         : Cannot invoke "Copy.barcode()" because the return value of "Shelf.copyWithBarcode(String)" is null
```

`<local1>` is a slot number rather than a name, because the class file carries no local variable
table by default. Recompile with `javac -g` and the same line reads `because "missing" is null`.
Worth knowing on day one: if a message names a `<localN>` you cannot place, `-g` turns it into
the identifier you wrote.

| C++ | Java |
|---|---|
| `Copy&` cannot be null; the type rules it out | No non-nullable reference type exists |
| Dereferencing a null pointer is undefined behaviour | `NullPointerException`, at a defined point, with a message |
| `nullptr` into an `int` — compile error | Same. The only compile-time null check you get |
| A null check is a habit you can drop when the type is `&` | A null check is a habit or a documented contract |
| Crash location is wherever the corruption surfaces | The stack trace's top frame is the dereference |

The Java replacements for `Copy&`, in the order to reach for them:

- **`Objects.requireNonNull(x, "x")` at the boundary.** Fails at the constructor or the method
  entry, naming the parameter, instead of at some later `.`. The corpus does this in every
  constructor — see `Shelf`, `Member`, `Library`.
- **Throw a named exception for an absent thing.** `Shelf.require(isbn)` in the corpus throws
  `UnknownIsbnException`, so no caller has a null to forget about.
- **`Optional<Copy>` on a query that can legitimately find nothing.** `Catalogue.find` returns
  `Optional<Title>`, which makes the empty case unignorable at the call site. J11 covers it.

The corpus reference solution returns `null` from no method at all. That is not a coincidence
and it is worth copying.

## The leak that garbage collection does not prevent

This is the part a C++ developer under-weights, because "no memory management" gets heard as
"no memory bugs". A reachable object is never collected, and a reference in a long-lived
collection is reachability.

`Shelf.takeBack` removes two references. `Shelf.takeBackLeavingTheCard` removes one, leaves the
card in the pocket, and reports the same numbers at the desk:

```java
public void takeBack(String barcode) {
    LoanCard card = requireOut(barcode);
    out.remove(barcode);
    card.copy().backOnShelf();          // the line the second version omits
}
```

```
--- 7. the leak garbage collection cannot prevent
collected while out    : false
collected after takeBack : true
copies out, both shelves : 0 and 0
collected after takeBackLeavingTheCard : false
```

Both shelves report zero copies out. One of them still holds a finished loan, reachable through
`Copy.onLoan` for the life of the shelf, and it will hold one per return forever.

| C++ leak | Java equivalent |
|---|---|
| `new` with no `delete` | Cannot happen |
| `shared_ptr` cycle | Cannot happen |
| Forgot to `erase` from a cache or registry | Identical, and now the only kind |
| Valgrind finds it | Nothing finds it; a heap dump shows it as growth, not as an error |

An interviewer notices this one, and it is graded as a design bug rather than a memory bug. The
question to ask about any collection you add is what removes from it. A `Map` with a `put` and
no `remove` on any code path is the tell, and listeners and observer registrations are where it
shows up most.

## Ownership becomes a question about mutation

Once sharing is free, "who owns this" stops being interesting and "who may change this" starts
being the question you are graded on. Two mechanisms, and the second one has a catch.

**A defensive copy on the way out.** `Shelf.copies()` returns `List.copyOf(copies)`, so no
caller can add a book to the shelf. Real output, and read both lines:

```
handedOut.add(...)     : UnsupportedOperationException
shelf's second copy    : Copy[978-0132350884#2, damaged]
```

`List.copyOf` is **shallow**. The list is frozen and the books in it are not, so
`handedOut.get(1).markDamaged()` reached the shelf's own copy. Returning an unmodifiable
collection of mutable objects protects the collection only, and that half-measure reads worse
than no protection because it looks like protection.

**Immutability, which removes the decision.** The corpus makes `Title` and `Loan` records and
`Member` a final class with no setters. Handing out a `Title` cannot let anyone edit the
catalogue, so `InMemoryCatalogue.titles()` needs no thought beyond the list wrapper. That is
why the reference solution has no defensive-copy problem to solve.

**The threshold.** Copy at the boundary when all three hold: the type is mutable, a caller
outside the class can reach the object, and one of your invariants depends on its state. Two of
the three is not enough. Miss the first and you are allocating for nothing; miss the third and
you are copying data nobody's correctness depends on, which an interviewer reads as ceremony.

When all three do hold, prefer making the type immutable over copying it. A copy is a rule
every future caller has to remember; an immutable type is a rule the compiler remembers. Reach
for the copy when the type is not yours to change.
