Memra

Upcasting & the final keyword

Why upcasting is always safe, plus final on data, methods, classes, and blank finals.

Upcasting: treating a subtype as its supertype

Upcasting is assigning a subclass reference to a superclass variable — moving *up* the inheritance tree toward the root. It is always safe and needs no cast syntax, because a Circle genuinely *is* a Shape:

Shape s = new Circle();           // upcast: implicit, always safe

Upcasting is what lets one method accept many types. A parameter typed to the supertype works for every subtype — the foundation of polymorphism, which the next lesson explores:

void render(Shape shape) {        // accepts Circle, Square, any Shape
    shape.draw();
}

After upcasting you can only call members declared on the *static* type (Shape). The object is still a Circle underneath — but to call Circle-specific methods you would have to downcast (covered in 1.6).

final: "this won't change"

final forbids further change, in three places:

- final variable / field — assign once, never reassign. For a *reference*, the reference is frozen but the object it points to can still mutate. - final method — cannot be overridden by a subclass. Useful to lock behavior others depend on. - final class — cannot be extended at all (e.g. String, Integer).

Worked example: a blank final

A blank final is a final field declared without an initializer; it must be assigned exactly once, in *every* constructor (or an instance initializer). This lets the value depend on constructor arguments yet stay immutable afterward:

class Sensor {
    private final int id;         // blank final: not set here
    Sensor(int id) {
        this.id = id;             // assigned exactly once
    }
    // id = 5;  would be a COMPILE ERROR: already final-assigned
}

The compiler guarantees id is set before the constructor returns and refuses any later assignment.

NORMAL ~/memra/learn/comp-308/upcasting-and-final utf-8 LF