← Java: language and application decisions
03 / 10 · 45 MIN

Methods, contracts and control flow

Select the right operation and trace paths that can produce effects, end a search or return a result.

Signature, result and contract

A method associates a name and parameters with behavior. In the simple nongeneric case, its signature distinguishes the name and parameter types, including their order. Changing only a parameter name or the return type does not create a different overload. If int load(String key) already exists, also declaring long load(String key) in the same class conflicts. A name such as loadLong can make the choice explicit, accompanied by conversion and boundary rules.

A value-returning method must supply a result when returning normally. An int method containing only if (enabled) return 8; that can reach its end with enabled false does not compile. Adding return 0 supplies the missing path, but is correct only if zero is the contractually defined result. Failure or absence may require another representation, which should not be invented merely to satisfy the compiler.

Overloading: select from the call expression

If label(Object) and label(String) coexist, calling with Object value = "ready" selects label(Object). The compiler uses expression value’s type; it does not select the overload again after discovering the object’s contents. The situation differs if the argument itself has type String.

Resolution has phases. It first looks for an applicable call without boxing, unboxing or variable-arity invocation. If route(long) is already applicable to an int literal, that selection precedes the phase that would allow route(Integer) through boxing. This is not a guess about which type seems closest to a value.

null can be compatible with several reference types. Between send(String) and send(Integer), send(null) is ambiguous: neither type is more specific than the other. send((String) null) selects String while still passing null. The cast selects a contract; it does not prove that the method accepts absence. Exercises using that call explicitly state that null is accepted.

Overriding: execute the object’s implementation

An instance method can be redefined in a subclass through overriding. Consider Base.label(Object), overridden in Child, which also adds label(String). With Base b = new Child(); Object x = "ready";, b.label(x) selects signature label(Object) at compilation. The implementation invoked is Child.label(Object), because the object is a Child. The String overload is not reconsidered.

This distinction helps when calls pass through an interface or base type: the caller-visible contract selects the operation; the object supplies the applicable implementation of that operation. A review should ask what inputs, results and effects the contract promises. Having an override does not automatically check those business promises. Inheritance and interfaces are developed in the next module; here, keep signature selection separate from instance-method dispatch.

Loops: values, progress and exit

In for (int amount : amounts), amount receives an element value on each iteration. Assigning amount *= 2 does not write to the int array. To modify primitive elements, write array slots. If elements are references to mutable objects, changing the referenced object differs from reassigning the iteration variable.

continue ends the current iteration and seeks the next. In a basic for loop, the header update runs before the next condition. In a while with i++ after a continue guard, that increment can be skipped. If a[0] is negative and the guard continues before advancing i, the same element is examined indefinitely. A replacement using for (int i = 0; i < a.length; i++) must also replace the earlier local declaration of i.

An unlabeled break leaves only the nearest enclosing loop or switch. return leaves the method. None of these commands undoes earlier changes. When tracing a snippet, record the index, accumulated state and next statement; do not confuse "skip this input" with "finish all processing".

switch for selecting a result

A switch expression supplies a value that can be assigned. For example, int n = switch (mode) { case 1 -> 10; default -> 20; }; produces 10 for mode equal to 1 and 20 for remaining int values. An arrow rule executes only the selected alternative; it does not automatically continue into the next rule.

If a rule needs a block, yield supplies that block’s result to the switch expression: case 1 -> { yield 10; }. A standalone literal inside the block is not a valid statement, and break 10 is not result syntax in Java SE 25. Do not confuse yield with returning from the method.

In traditional switch statements with colon labels, execution can continue into statements after later labels unless it exits. When converting a decision table, establish which cases share a result and which used intentional fall-through. This lesson uses int selectors; additional pattern and reference rules are not assumed.

Guided application: validate before effects

A method receives quantities 4, 7 and -1. Every value must lie from 1 through 100; if any is invalid, no apply call may occur. Validating and applying in one loop allows 4 and 7 to be applied before discovering -1. Returning false then reports the error but does not undo earlier work.

In this scenario, the array does not change during the call, validation has no effects and apply completes without failure. Use a first pass solely to validate; if every element is valid, use a second pass to apply. The snippet below sums quantities instead of applying them so its result can be compared by reading. The collection is bounded at 1,000 elements, so its maximum sum of 100,000 fits int.

If concurrent changes or write failures are possible, the first pass does not provide atomicity. An appropriate snapshot or transaction boundary would need to be defined. This case’s conclusion is limited to rejecting invalid input before starting effects under the stated assumptions.

Decision: first match and required summary

A method searches groups and, inside each group, items. Its goal is to record only the first global match and publish a summary after searching, including when no match exists. A break in the inner loop allows progress to the next group and another match. A return after recording leaves the method and skips the summary placed after the loops.

A search label on the outer loop permits break search after the first record. Control continues at the statement after the loops, where publishSummary sits. Another design extracts searching into a method returning the result and leaves summary publication in the caller. In either design, trace both matching and nonmatching paths. The exercise assumes finite loops, no finally and calls without exceptions; failure and resource handling are studied separately.

static int acceptedTotal(int[] quantities) {
    java.util.Objects.requireNonNull(quantities);
    if (quantities.length > 1_000) {
        throw new IllegalArgumentException("batch too large");
    }
    for (int quantity : quantities) {
        if (quantity < 1 || quantity > 100) {
            throw new IllegalArgumentException("quantity outside range");
        }
    }
    int total = 0;
    for (int quantity : quantities) total += quantity;
    return total;
}
IN PRACTICE

Read the method inside a class, assuming the array does not change during the call. {4, 7} returns 11; {4, 7, -1} throws before the summation loop. An empty array returns 0. The size bound and per-element rule justify int capacity.

Common pitfalls

Selecting overloads from the actual object; using return type to distinguish signatures; forgetting progress on continue; treating break as exiting every loop; skipping required work through return.

Related topics: Types, conversions and validation · Objects, interfaces and records · Exceptions, resources and files · Streams, lambdas and absence

Take this idea with you

Select the signature from call types, follow dispatch, and trace each path to its result and effects. Leaving control flow is not a rollback.

Create account

Reference: JLS 25: Method invocation · Java SE 25; no preview features