SwadhyAI Notes

All Java notes

Static and Instance Methods

›Short versionOpen if you only have a minute.
  • A static method has no receiver and no this. Call it on the class name.
  • An instance method runs against one object. Bare fields in the body mean this.field.
  • A static method cannot read an instance field, because no object has been selected.
  • An instance method may call a static method of its class. The reverse needs an object you already have.
  • A subclass static method hides a superclass static method. The compile-time type picks it.
  • A mutable static field is one shared variable. Prefer it for constants, and be deliberate about the rest.

main starts, and there is no account yet. The method that will eventually deposit money needs an account to deposit into. The method that squares an int does not. Those two methods should not be declared the same way.

A belongs to the class and runs without an object. An belongs to objects of the class and runs with one. The keyword is small. The consequence is whether the body is allowed to touch object state, and whether a subclass can replace the body for a particular object.

Two ways to call

MathTools.square(5) names the class. square receives 5 and returns 25. Nothing in that body depends on which MathTools object you might have constructed, so requiring one would only add a dummy new at every call.

java
class MathTools {
    static int square(int n) {
        return n * n;
    }
}

int result = MathTools.square(5);

counter.increment() names an object. That object is the . Inside increment, count++ means the count field of that receiver. A second Counter has its own count. Calling increment on the first leaves the second alone.

java
class Counter {
    int count;

    void increment() {
        count++;
    }
}

Counter counter = new Counter();
counter.increment();

The name for the receiver inside the instance method is . Writing this.count++ is the same field write. It earns its keep when a parameter is also called count: the parameter hides the field, and this.count is how the body still reaches the object.

What a static method can see

printBalance can run when zero Account objects exist, and it can run when many exist. A bare balance would require the compiler to pick an object. There is no rule that picks one, so the bare name is a compile error. The static method may still call printThisBalance on an account it was given as a parameter, because that call has a receiver.

java
class Account {
    double balance;

    static void printBalance() {
        // System.out.println(balance); // compile error: no receiver
    }

    void printThisBalance() {
        System.out.println(balance); // this.balance
    }
}

The other direction is open. An instance method may call a static method of its class without a qualifier, and it may call one on another class by name. The instance method already has a receiver. The static method simply does not use it.

main is static because the launcher has a class name and arguments, not an object. The usual shape is: main constructs the objects, then calls their instance methods. Keeping the real work in main itself piles object-shaped behavior into a method that has no object.

Hiding, overriding, and shared state

Instance methods participate in overriding. If SavingsAccount overrides deposit, then account.deposit(40) runs the savings body when account refers to a SavingsAccount, even if the variable’s compile-time type is the superclass. That is the point of an instance method that subclasses should specialize.

A static method does not take that path. A subclass static method with the same signature is . In the program below, asBase is declared as Base and holds a Sub. asBase.greet() runs Base’s static method. asBase.hello() runs Sub’s instance method. missing is null, and the static call still prints base, because the reference value is discarded after the qualifier runs. The same variable’s instance call would throw NullPointerException.

Dispatch.java
class Base {
    static String greet() { return "base"; }
    String hello() { return "base"; }
}

class Sub extends Base {
    static String greet() { return "sub"; }

    @Override
    String hello() { return "sub"; }

    public static void main(String[] args) {
        Base asBase = new Sub();
        System.out.println(asBase.greet());
        System.out.println(asBase.hello());
        Base missing = null;
        System.out.println(missing.greet());
    }
}
Terminal
$ javac Dispatch.java
$ java Sub
base
sub
base

@Override on static String greet() is rejected: a static method hides, and the annotation checks for an override. A subclass cannot mix the two either. An instance greet in Sub is rejected when Base.greet is static, and a static method is rejected when the superclass method is an instance method. final closes the matching door: a final instance method cannot be overridden, and a final static method cannot be hidden. If the behavior should follow the object, it belongs in an instance method.

Static methods are a good fit for pure calculations, for Math.max style utilities, and for factories. They are a poor fit for anything that must vary per object or per subclass. The temptation is a that holds the varying data instead. A static counter then counts for every caller in the process. Tests share it. Threads share it. A constant marked static final is the case that stays boring, which is what you want from a constant.

printBalance is static and its body says System.out.println(balance), where balance is an instance field. The class compiles otherwise. What does the compiler report, and what call would make the field legal?

The static method is rejected: balance names an instance field, and the method has no this to supply an object. Passing an Account into a static method and calling account.printThisBalance(), or reading account.balance on that parameter, is legal, because the object is explicit. Declaring printBalance as an instance method is the other fix, when the interesting balance is always the receiver’s.