| Lesson 5 | Declaring Methods |
| Objective | Declare, overload, and document methods using modern Java best practices |
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.
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
}
public / protected / private, static, final, abstract, synchronized, native, strictfp.void.final or ... (varargs).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)
private/package-private helpers.this.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;
}
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;
}
@Override â ensure you are truly overriding.@Deprecated â document alternatives with since/forRemoval.@FunctionalInterface â for single-abstract-method interfaces used with lambdas.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)
}
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)
}
}
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])));
}
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.
computeTotal, open, toJson), keep them short, and do one thing.| Modifier | Effect |
|---|---|
public/protected/private | Controls visibility (API surface). Prefer the narrowest needed. |
static | Belongs to the class, not instances; no access to instance fields (unless via a reference). |
final | Method cannot be overridden. |
abstract | No body; subclass must implement. |
synchronized | Acquires the receiver/class monitor for the call; use judiciously. |
native | Implemented in non-Java code. |
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.