Skip to content

14. Methods, Attributes and Variables

Table of Contents


This chapter introduces fundamental Object-Oriented Programming (OOP) concepts in Java, starting with methods, parameter passing, overloading, local vs. instance variables, and varargs.

14.1 Methods

Methods represent the operations/behaviors that can be performed by a particular data type (a class).

They describe what the object can do and how it interacts with its internal state and the outside world.

A method declaration is composed of mandatory and optional components.

14.1.1 Mandatory Components of a Method

14.1.1.1 Access Modifiers

Access Modifiers control visibility, not behavior.

( Please refer to Paragraph "Access Modifiers" in Chapter: 2. Basic Language Java Building Blocks )

14.1.1.2 Return Type

Appears immediately before the method name.

  • If the method returns a value → the return type specifies the value’s type.
  • If the method does not return a value → the keyword void must be used.
  • A method with a non-void return type must contain at least one return value; statement.
  • A void method may:
  • omit a return statement
  • include return; (with no value)

14.1.1.3 Method Name

Follows the same rules as identifiers ( Please refer to Chapter: 3. Java Naming Rules ).

14.1.1.4 Method Signature

The method signature in Java includes:

  • the method name
  • the parameter type list (types + order)

Note

Parameter names do NOT belong to the signature, only types and order matter.

  • Example of distinct signatures:
void process(int x)
void process(int x, int y)
void process(int x, String y)
  • Example of same signature (illegal overloading):
// ❌ same signature: only parameter names differ
void m(int a)
void m(int b)

14.1.1.5 Method Body

A block { } containing zero or more statements.

If the method is abstract, the body must be omitted.

14.1.2 Optional Modifiers

Optional method modifiers include:

  • static
  • abstract
  • final
  • default (interface methods)
  • synchronized
  • native
  • strictfp

Rules:

  • Optional modifiers may appear in any order.
  • All modifiers must appear before the return type.

  • Example:

public static final int compute() {
    return 10;
}

14.1.3 Declaring Methods

public final synchronized String formatValue(int x, double y) throws IOException {
    return "Result: " + x + ", " + y;
}

Breakdown:

Part Meaning
public access modifier
final cannot be overridden
synchronized thread control modifier
String return type
formatValue method name
(int x, double y) parameter list
throws IOException exception list
method body implementation

14.2 Java Is a “Pass-by-Value” Language

Java uses only pass-by-value, no exceptions.

This means:

  • For primitive types → the method receives a copy of the value.
  • For reference types → the method receives a copy of the reference, meaning:
  • the reference itself cannot be changed by the method
  • the object can be modified through that reference

  • Example:

void modify(int a, StringBuilder b) {
    a = 50;           // modifying the *copy* → no effect outside
    b.append("!");    // modifying the *object* → visible outside
}
public static void main(String[] args) {

    int num1 = 11;
    methodTryModif(num1);
    System.out.println(num1);

}

public static void methodTryModif(int num1){    
    num1 = 10;  // this new assignement affects only the method parameter which, accidentally, has the same name as the external variable.
}

14.3 Overloading Methods

Method overloading means same method name, different signature.

Two methods are considered overloaded if they differ in:

  • number of parameters
  • parameter types
  • parameter order

Overloading does NOT depend on:

  • return type
  • access modifier
  • exceptions

  • Example:

void print(int x)
void print(double x)
void print(int x, int y)

Illegal overloaded method:

// ❌ Return type does not count in overloading
int compute(int x)
double compute(int x)

14.3.1 Calling overloaded methods

When multiple overloaded methods are available, Java applies overload resolution to decide which method to call.

The compiler selects the method whose parameter types are the most specific and compatible with the provided arguments.

Overload resolution happens at compile time (unlike overriding, which is run-time based).

Java follows these rules:

14.3.1.1 Exact match wins

If an argument matches a method parameter exactly, that method is chosen.

void call(int x)    { System.out.println("int"); }
void call(long x)   { System.out.println("long"); }

call(5); // prints: int (exact match for int)

14.3.1.2 — If no exact match exists, Java picks the most specific compatible type

Java prefers:

  1. widening over autoboxing
  2. autoboxing over varargs
  3. wider reference only if more specific type is not available

  4. Example with numeric primitives:

void test(long x)   { System.out.println("long"); }
void test(float x)  { System.out.println("float"); }

test(5);  // int literal: can widen to long or float
          // but long is more specific than float for integer types
          // Output: long

14.3.1.3 — Primitive widening beats boxing

If a primitive argument can either widen or autobox, Java chooses widening.

void m(int x)       { System.out.println("int"); }
void m(Integer x)   { System.out.println("Integer"); }

byte b = 10;
m(b);               // byte → int (widening) wins
                    // Output: int

14.3.1.4 — Boxing beats varargs

void show(Integer x)    { System.out.println("Integer"); }
void show(int... x)     { System.out.println("varargs"); }

show(5);                // int → Integer (boxing) preferred
                        // Output: Integer

14.3.1.5 — For references, Java picks the most specific reference type

void ref(Object o)      { System.out.println("Object"); }
void ref(String s)      { System.out.println("String"); }

ref("abc");             // "abc" is a String → more specific than Object
                        // Output: String

More specific means lower in the inheritance hierarchy.

14.3.1.6 — When there is no unambiguous “most specific”, the call is a compile error

Example with sibling classes:

void check(Number n)      { System.out.println("Number"); }
void check(String s)      { System.out.println("String"); }

check(null);    // Both String and Number can accept null
                // String is more specific because it is a concrete class
                // Output: String

But if two unrelated classes compete:

void run(String s)   { }
void run(Integer i)  { }

run(null);  // ❌ Compile-time error: ambiguous method call

14.3.1.7 — Mixed primitive + wrapper overloads

Java evaluates widening, boxing, and varargs in this order:

widening → boxing → varargs

Example:

void mix(long x)        { System.out.println("long"); }
void mix(Integer x)     { System.out.println("Integer"); }
void mix(int... x)      { System.out.println("varargs"); }

short s = 5;
mix(s);   // short → int → long  (widening)
          // Boxing and varargs ignored
          // Output: long

14.3.1.8 — When primitives mix with reference types

void fun(Object o)     { System.out.println("Object"); }
void fun(int x)        { System.out.println("int"); }

fun(10);                // exact primitive match wins
                        // Output: int

Integer i = 10;
fun(i);                 // reference accepted → Object
                        // Output: Object

14.3.1.9 — When Object wins

void fun(List<String> o)    { System.out.println("O"); }
void fun(CharSequence x)    { System.out.println("X"); }
void fun(Object y)          { System.out.println("Y"); }

fun(LocalDate.now());       // Output: Y

14.3.1.10 Summary Table (Overload Resolution)

Situation Rule
Exact match Always chosen
Primitive widening vs boxing Widening wins
Boxing vs varargs Boxing wins
Most specific reference type Wins
Unrelated reference types Ambiguous → compile error
Mixed primitive + wrapper Widening → boxing → varargs

14.4 Local and Instance Variables

14.4.1 Instance Variables

Instance variables are:

  • declared as members of a class
  • created when an object is instantiated
  • accessible by all methods of the instance

Possible modifiers for instance variables:

  • access modifiers (public, protected, private)
  • final
  • volatile
  • transient

  • Example:

public class Person {
    private String name;         // instance variable
    protected final int age = 0; // final means cannot be reassigned
}

14.4.2 Local Variables

Local variables:

  • are declared inside a method, constructor, or block
  • have no default values → must be explicitly initialized before use
  • only modifier allowed: final

  • Example:

void calculate() {
    int x;        // declared
    x = 10;       // must be initialized before use

    final int y = 5;  // legal
}

Two special cases:

14.4.2.1 Effectively Final Local Variables

A local variable is effectively final if it is assigned once, even without final.

Effectively final variables can be used in:

  • lambda expressions
  • local/anonymous classes

14.4.2.2 Parameters as Effectively Final

Method parameters behave as local variables and follow the same rules.


14.5 Varargs (Variable-Length Argument Lists)

Varargs allow a method to accept zero or more parameters of the same type.

Syntax:

void printNames(String... names)

Rules:

  • A method may have only one varargs parameter.
  • It must be the last parameter in the list.
  • Varargs are treated as an array inside the method.

  • Example:

void show(int x, String... values) {
    System.out.println(values.length);
}

show(10);                     // length = 0
show(10, "A");                // length = 1
show(10, "A", "B", "C");      // length = 3

Important

Varargs and arrays participate in method overloading. Overload resolution may become ambiguous.


14.6 Static Methods, Static Variables, and Static Initializers

In Java, the keyword static marks elements that belong to the class itself, not to individual instances.
This means:

  • They are loaded once into memory when the class is first loaded by the JVM.
  • They are shared among all instances.
  • They can be accessed without creating an object of the class.

Static members are stored in the JVM method area (class-level memory), while instance members live in the heap.

14.6.1 Static Variables (Class Variables)

A static variable is a variable defined at class level and shared by all instances.

Characteristics:

  • Created when the class is loaded.
  • Exists even if no instance of the class is created.
  • All objects see the same value.
  • May be marked final, volatile, or transient.

  • Example:

public class Counter {
    static int count = 0;    // shared by all instances
    int id;                  // instance variable

    public Counter() {
        count++;
        id = count;          // each instance gets a unique id
    }
}

14.6.2 Static Methods

A static method belongs to the class, not to any object instance.

Rules:

  • They can be called using the class name: ClassName.method().
  • They cannot access instance variables or instance methods directly, but only through an instance of the class.
  • They cannot use this or super.
  • They are commonly used for:
  • utility methods (e.g., Math.sqrt())
  • factory methods
  • global behaviors that do not depend on instance state

  • Example:

public class MathUtil {

    static int square(int x) {        // static method
        return x * x;
    }

    void instanceMethod() {
        // System.out.println(count);   // OK: accessing static variable
        // square(5);                   // OK: static method accessible
    }
}

Common errors:

// ❌ Compile error: instance method cannot be accessed directly in static context
static void go() {
    run();        // run() is instance method!
}

void run() { }

14.6.3 Static Initializer Blocks

Static initializer blocks allow executing code once, when the class is loaded.

Syntax:

static {
    // initialization logic
}

Usage:

  • initializing complex static variables
  • performing class-level setup
  • running code that must execute exactly once

  • Example:

public class Config {

    static final Map<String, String> settings = new HashMap<>();

    static {
        settings.put("mode", "production");
        settings.put("version", "1.0");
        System.out.println("Static initializer executed");
    }
}

Important

Static initializer blocks run once, in the order they appear, before main() and before any static method is called.

14.6.4 Initialization Order (Static vs. Instance)

( Please refer to Chapter: 15. Class Loading, Initialization, and Object Construction )

14.6.5 Accessing Static Members

Math.sqrt(16);
MyClass.staticMethod();

MyClass obj = new MyClass();
obj.staticMethod();   

14.6.6 Static and Inheritance

Static methods:

  • can be hidden, not overridden
  • binding is compile-time, not runtime
  • accessed based on reference type, not object type

  • Example:

class A {
    static void test() { System.out.println("A"); }
}

class B extends A {
    static void test() { System.out.println("B"); }
}

A ref = new B();
ref.test();   // prints "A" — static binding!

Note

Key rule: static methods use reference type, not object type.

14.6.7 Common Pitfalls

  • Attempting to reference an instance variable/method from a static context.
  • Assuming static methods are overridden → they are hidden.
  • Calling a static method from an instance reference (legal but confusing).
  • Confusing initialization order of static elements vs. instance elements.
  • Forgetting that static variables are shared across all objects.
  • Not knowing that static initializers run once, in declaration order.