PrepZone Logo
PrepZone

Methods and Constructors

Overloading rules, constructor chaining, covariant returns, and why reflection needs a different call.

Why this matters

  • Overload resolution follows strict rules, and when two candidates look equally good the compiler's choice is often not the one you expected.
  • Constructor chaining is how you avoid duplicating validation across four constructors.
  • An object that can be constructed in an invalid state will eventually be constructed in an invalid state.
Caller
cartholds reference #A1
Method parameter
inputcopy of reference #A1
Cart object #A1items = [book]

input.add(pen) is visible to the caller. input = new Cart() is not — it only repoints the copy.

Java copies the value in the variable. For an object that value is the reference, so the method can change the object's fields but cannot repoint the caller's variable.

Method signatures

A signature is the method name plus its parameter types, in order. The return type is not part of it, and neither are parameter names.

Java
int calculate(int a, int b)          // signature: calculate(int, int)
double calculate(int a, int b)       // same signature — will not compile
int calculate(int a, long b)         // different signature — fine

Two methods differing only in return type cannot coexist, because the compiler would have no way to tell them apart at a call site.

Overloading

Overloading means several methods sharing a name but differing in parameters. The compiler chooses at compile time, in a fixed order of preference.

Resolution order

  • Exact match on the declared types.
  • Widening a primitive — int to long to float to double.
  • Boxing a primitive into its wrapper.
  • Varargs, considered last of all.
Java
static void show(long value)      { System.out.println("long"); }
static void show(Integer value)   { System.out.println("Integer"); }
static void show(int... values)   { System.out.println("varargs"); }

show(5);   // prints "long"

Widening beats boxing, and boxing beats varargs. An int argument therefore widens to long rather than boxing to Integer, which surprises most people the first time.

Varargs

Varargs let a method accept any number of arguments, which arrive as an array.

Java
static int sum(int... numbers) {
    int total = 0;
    for (int number : numbers) total += number;
    return total;
}

sum();              // 0  — an empty array, never null
sum(1, 2, 3);       // 6
sum(new int[]{4, 5});  // 9 — an array is accepted directly

Rules

  • Only one varargs parameter, and it must be last.
  • Inside the method it behaves exactly like an array.
  • An overload with a fixed parameter list always wins over the varargs version.

Constructors

A constructor has the class's name, no return type, and runs once per object. If you declare none, the compiler supplies a no-argument default — but the moment you declare any constructor, that default disappears.

Java
public class Account {
    private final String id;
    private final String owner;
    private double balance;

    public Account(String id, String owner, double balance) {
        if (id == null || id.isBlank()) {
            throw new IllegalArgumentException("id is required");
        }
        if (balance < 0) {
            throw new IllegalArgumentException("balance cannot be negative");
        }
        this.id = id;
        this.owner = owner;
        this.balance = balance;
    }

    public Account(String id, String owner) {
        this(id, owner, 0.0);        // chaining: must be the first statement
    }
}

Chaining rules

  • this(...) calls another constructor in the same class. super(...) calls the parent's.
  • Either one must be the first statement, and you cannot use both in the same constructor.
  • With no explicit call, the compiler inserts super(). If the parent has no no-argument constructor, that is a compile error.
  • Put the real validation in the most complete constructor and have the others delegate to it, as above.

static versus instance

Aspectstatic memberinstance member
Belongs toThe classEach object
Copies in memoryExactly oneOne per object
Called viaClassName.member()reference.member()
Can use thisNo — there is no instanceYes
Can access the other kindOnly statics directlyBoth
OverridableNo — it is hidden, not overriddenYes
  • Belongs to

    static memberThe class
    instance memberEach object
  • Copies in memory

    static memberExactly one
    instance memberOne per object
  • Called via

    static memberClassName.member()
    instance memberreference.member()
  • Can use this

    static memberNo — there is no instance
    instance memberYes
  • Can access the other kind

    static memberOnly statics directly
    instance memberBoth
  • Overridable

    static memberNo — it is hidden, not overridden
    instance memberYes

A static method cannot touch instance state, which is why main() needs an object to do anything.

Immutable parameters with final

Marking a parameter final prevents reassigning the parameter itself. It does not make the object it points at immutable.

Java
void process(final List<String> items) {
    items.add("allowed");          // fine — mutating the object
    // items = new ArrayList<>();  // compile error — reassigning the parameter
}

This is a useful habit in longer methods because it removes a whole class of confusion about which value a name currently holds.

Covariant return types

An override may narrow its return type to a subtype of the original. This matters for fluent APIs and for clone().

Java
class Shape {
    Shape copy() { return new Shape(); }
}

class Circle extends Shape {
    @Override
    Circle copy() { return new Circle(); }   // narrower return — legal
}

Callers holding a Circle now get a Circle back with no cast, while callers holding a Shape still see a Shape. Widening the return type, by contrast, would break those callers and is rejected.

Common misreadings

  • "Overloading is resolved at run time." It is purely compile-time, based on declared types. Only overriding is resolved at run time.
  • "A class always has a no-argument constructor." Declaring any constructor removes the compiler-generated default.
  • "Return type can distinguish overloads." It cannot. Signatures are name plus parameter types only.
  • "static methods can be overridden." A subclass declaring the same static signature hides it. The call is resolved by the reference type, not the object.
  • "final parameters make the argument immutable." Only the binding is fixed; the object stays as mutable as it ever was.

Quick recall

Everything you need if you only revisit this box.

  • A signature is name plus parameter types. Return type is excluded, so it cannot distinguish overloads.
  • Overload resolution prefers exact match → widening → boxing → varargs, in that order.
  • null arguments are ambiguous across unrelated reference overloads; cast to disambiguate.
  • Declaring any constructor removes the default no-argument one.
  • this(...) and super(...) must be the first statement, and only one of them may appear.
  • Validate in the fullest constructor and have the shorter ones delegate to it.
  • static members are hidden, not overridden; the reference type decides which one runs.
  • Overrides may narrow the return type (covariant returns) but never widen it.

Test yourself

Answer these before moving on — recall is what makes it stick.