Declaring Methods  «Prev  Next»


Lesson 5Declaring Methods
ObjectiveDeclare, overload, and document methods using modern Java best practices

Declaring Methods in Modern Java

Critique of the legacy page. The original article correctly introduced method syntax and scope, but it: (1) mixed basics with scattered examples, (2) used StringBuffer (overly synchronized for single-threaded code), and (3) didn’t connect signatures, overloading, exceptions, and immutability into a coherent model. This rewrite focuses on intent, correctness, and current conventions.

Method anatomy

A method declaration consists of modifiers, a return type, a name, a parameter list, optional throws clauses, and a body (or a semicolon for abstract/interface methods).

[annotations] [modifiers] returnType methodName([parameterList]) [throws ExceptionTypes] {
  // method body
}

Overloading and signatures

The method signature is the method name plus its parameter types (order and arity). Overloading means multiple methods share a name but differ by signature.

int  parse(String s)         { ... }
long parse(String s, int r)  { ... }        // overloaded (different params)

Access, static vs. instance, and immutability


Varargs and defaults

Java has no default arguments; use overloads or varargs for convenience.

static int sum(int... xs) {
  int s = 0;
  for (int x : xs) s += x;
  return s;
}

Checked vs. unchecked exceptions

Declare checked exceptions with throws. Unchecked exceptions (RuntimeException and subclasses) need not be declared. Keep public method contracts clear with precise exception types; avoid broad throws Exception.

public Path mustExist(Path p) throws java.io.FileNotFoundException {
  if (!java.nio.file.Files.exists(p)) throw new java.io.FileNotFoundException(p.toString());
  return p;
}

Annotations that matter

Argument passing: primitives vs. references

Java is pass-by-value. For objects, the value passed is a reference copy. Mutating the object’s state is visible to the caller; rebinding the parameter is not.

class Demo {
  static void reassign(StringBuilder sb) { sb = new StringBuilder("xyz"); }   // rebinding (caller unaffected)
  static void mutate  (StringBuilder sb) { sb.append("!"); }                   // state change (caller sees it)
}

Modernized “Argument” example (use StringBuilder, not StringBuffer)

class Argument {
  public static void main(String[] args) {
    int i = 10;
    increment(i, 100);
    System.out.println(i);                // 10 (primitive copied)

    StringBuilder s = new StringBuilder("abc");
    append(s, "def");
    System.out.println(s);                // "abcdef" (object mutated)
  }

  static void increment(int n, int m) {   // n is a copy
    n += m;
  }

  static void append(StringBuilder sb, String suffix) {
    sb.append(suffix);                    // mutate original object
    sb = new StringBuilder("xyz");        // rebind local only (caller unchanged)
  }
}

Returning values and early exits

Return a value that fully represents the result. Prefer returning optional or a small record rather than using output parameters.

record Range(int min, int max) {}

static java.util.Optional<Range> parseRange(String s) {
  var parts = s.split("-");
  if (parts.length != 2) return java.util.Optional.empty();
  return java.util.Optional.of(new Range(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])));
}

Thread-safety note

Use StringBuilder in single-threaded code; StringBuffer is synchronized and usually slower. For shared mutable state, don’t rely on synchronized strings—design explicit synchronization or use thread-safe structures.

Best-practice checklist

Quick reference: common modifiers

ModifierEffect
public/protected/privateControls visibility (API surface). Prefer the narrowest needed.
staticBelongs to the class, not instances; no access to instance fields (unless via a reference).
finalMethod cannot be overridden.
abstractNo body; subclass must implement.
synchronizedAcquires the receiver/class monitor for the call; use judiciously.
nativeImplemented in non-Java code.

Putting it together

public class BankAccount {
  private long cents;

  public synchronized void deposit(long delta) {
    if (delta <= 0) throw new IllegalArgumentException("delta > 0");
    cents += delta;
  }

  public synchronized long balance() { return cents; }

  public static BankAccount ofDollars(long dollars) {
    var a = new BankAccount();
    a.cents = Math.multiplyExact(dollars, 100);
    return a;
  }
}

Summary: A clear method declaration communicates contract (visibility, parameters, return type, exceptions) and intent. Prefer immutability, small focused methods, and modern utilities over legacy patterns like StringBuffer and output parameters.


SEMrush Software