Class Methods

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 44 of 133

Methods defined inside a class give its objects behavior. There are two kinds: instance methods, which operate on a particular object and its fields, and static methods, which belong to the class as a whole. Choosing between them, and designing methods that protect an object's state, is central to writing object-oriented Java.

Instance Methods

An instance method is declared without static. It is called on an object, and inside it the fields refer to that object's values:

public class Thermostat {
    private double current;
    private double target;

    public Thermostat(double current, double target) {
        this.current = current;
        this.target = target;
    }

    public void raiseTarget(double degrees) {
        target += degrees;
    }

    public boolean isHeating() {
        return current < target;
    }

    public String status() {
        return String.format("current %.1f°, target %.1f°, %s",
                current, target, isHeating() ? "heating" : "idle");
    }
}
Thermostat living = new Thermostat(19.5, 21);
Thermostat bedroom = new Thermostat(20, 18);

living.raiseTarget(1);

System.out.println(living.status());    // current 19.5°, target 22.0°, heating
System.out.println(bedroom.status());   // current 20.0°, target 18.0°, idle

When living.raiseTarget(1) runs, target means living's target. The same method called on bedroom would change bedroom's target. Inside status, isHeating() with no object in front calls the method on the same object status was called on.

The implicit this

Every instance method receives a hidden reference to the object it was called on, available as this. Writing target inside the method is shorthand for this.target:

public void raiseTarget(double degrees) {
    this.target += degrees;          // identical to: target += degrees;
}

this becomes necessary when a parameter has the same name as a field, and when an object needs to pass itself to another method. The this keyword has its own lesson.

Static Methods

A static method is declared with static. It belongs to the class, is called on the class name, and has no this:

public class Temperature {
    public static double celsiusToFahrenheit(double c) {
        return c * 9 / 5 + 32;
    }

    public static double fahrenheitToCelsius(double f) {
        return (f - 32) * 5 / 9;
    }
}
double f = Temperature.celsiusToFahrenheit(37);   // 98.6

The conversion depends only on its argument, not on any object's state, so there is no reason to create a Temperature object to perform it. Math.max, Integer.parseInt, List.of, and Arrays.sort are static methods for the same reason.

What static methods cannot do

Because a static method has no object, it cannot use instance fields or call instance methods directly:

public class Thermostat {
    private double target;

    public static void reset() {
        target = 20;          // compile error: non-static variable target cannot be
                              // referenced from a static context
    }
}

There is no single target to reset: every Thermostat object has its own. A static method can work with instances only if it is given one, for example as a parameter.

Instance Versus Static: How to Choose

QuestionIf yes
Does the method read or change an object's fields?Instance method
Does the result differ between objects of the class?Instance method
Does it depend only on its parameters?Static method
Is it a factory that creates instances?Static method
Is it a general utility (math, parsing, validation)?Static method, often in a utility class
public class Rectangle {
    private final double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double area() {                       // instance: uses this object's fields
        return width * height;
    }

    public boolean fitsInside(Rectangle other) { // instance: compares this with another
        return width <= other.width && height <= other.height;
    }

    public static Rectangle square(double side) { // static factory
        return new Rectangle(side, side);
    }

    public static Rectangle parse(String text) {  // static factory from text like "4x3"
        String[] parts = text.toLowerCase().split("x");
        return new Rectangle(Double.parseDouble(parts[0].strip()),
                             Double.parseDouble(parts[1].strip()));
    }
}
Rectangle box = Rectangle.parse("40 x 30");
Rectangle tile = Rectangle.square(25);

System.out.println(box.area());              // 1200.0
System.out.println(tile.fitsInside(box));    // true

Static factory methods

Methods like square and parse are static factory methods: alternatives to constructors. They have advantages:

  • They have descriptive names (square(25) says more than new Rectangle(25, 25)).
  • They can return a cached instance instead of always creating a new one (Integer.valueOf, List.of()).
  • They can return a subtype, hiding which implementation is used.

The JDK uses them widely: LocalDate.of(...), Path.of(...), Optional.empty(), String.valueOf(...).

Kinds of Instance Methods

Accessors (getters)

Return information about the object without changing it:

public double getTarget() { return target; }
public boolean isHeating() { return current < target; }

By convention, accessors start with get, or is for booleans. Some accessors return stored values; others, like isHeating, compute an answer from the state.

Mutators

Change the object's state. Good mutators validate their input so that the object can never enter an invalid state:

public void setTarget(double target) {
    if (target < 5 || target > 35) {
        throw new IllegalArgumentException("target must be between 5 and 35");
    }
    this.target = target;
}

Named, intention-revealing mutators are usually better than generic setters:

public void raiseTarget(double degrees) { setTarget(target + degrees); }
public void lowerTarget(double degrees) { setTarget(target - degrees); }

Here, the range check lives in exactly one place, and every route that changes target passes through it.

Query methods and command methods

A useful design rule is to separate queries (return information, change nothing) from commands (change state, return nothing or a status). A method named getBalance that also deducts a fee surprises every caller. Keeping the two separate makes code easier to reason about and safer to call repeatedly.

Returning this for Method Chaining

A method that modifies the object can return this, allowing several calls in a single expression:

public class EmailBuilder {
    private String to, subject, body = "";

    public EmailBuilder to(String to)           { this.to = to; return this; }
    public EmailBuilder subject(String subject) { this.subject = subject; return this; }
    public EmailBuilder line(String text)       { this.body += text + "\n"; return this; }

    public String build() {
        return "To: " + to + "\nSubject: " + subject + "\n\n" + body;
    }
}
String email = new EmailBuilder()
        .to("ada@example.com")
        .subject("Course enrollment")
        .line("Hi Ada,")
        .line("You are enrolled in Java Basics.")
        .build();

Each call returns the same builder, so the next call can follow directly. StringBuilder.append, HttpRequest.newBuilder(), and many configuration APIs use this pattern.

Methods Calling Other Methods

Methods within a class call each other by name. Breaking a complex operation into smaller private methods keeps each piece understandable:

public class Payroll {
    private static final double TAX_RATE = 0.10;
    private static final double OVERTIME_MULTIPLIER = 1.5;

    public double netPay(double hourlyRate, double hours) {
        double gross = grossPay(hourlyRate, hours);
        return gross - tax(gross);
    }

    private double grossPay(double rate, double hours) {
        double regular = Math.min(hours, 40);
        double overtime = Math.max(hours - 40, 0);
        return regular * rate + overtime * rate * OVERTIME_MULTIPLIER;
    }

    private double tax(double amount) {
        return amount * TAX_RATE;
    }
}

netPay is the public operation; grossPay and tax are private helpers, implementation details hidden from other classes. They can be rewritten freely without affecting any code outside Payroll.

Overloaded Methods

A class can have several methods with the same name and different parameter lists:

public void raiseTarget()               { raiseTarget(0.5); }      // default step
public void raiseTarget(double degrees) { setTarget(target + degrees); }

The compiler chooses the version matching the call's arguments. Overloading has its own lesson.

Practical Example: Bank Account

import java.util.ArrayList;
import java.util.List;

public class BankAccount {
    private static int nextNumber = 1001;               // shared counter

    private final int number;
    private final String owner;
    private double balance;
    private final List<String> history = new ArrayList<>();

    private BankAccount(String owner) {
        this.number = nextNumber++;
        this.owner = owner;
    }

    public static BankAccount open(String owner, double initialDeposit) {
        BankAccount acc = new BankAccount(owner);
        acc.deposit(initialDeposit);
        return acc;
    }

    public void deposit(double amount) {
        requirePositive(amount);
        balance += amount;
        record("DEP", amount);
    }

    public void withdraw(double amount) {
        requirePositive(amount);
        if (amount > balance) {
            throw new IllegalStateException("Insufficient funds in " + number);
        }
        balance -= amount;
        record("WDR", amount);
    }

    public void transferTo(BankAccount target, double amount) {
        this.withdraw(amount);
        target.deposit(amount);
    }

    public double getBalance() { return balance; }
    public List<String> getHistory() { return List.copyOf(history); }

    private void record(String type, double amount) {
        history.add(type + " " + amount + " -> " + balance);
    }

    private static void requirePositive(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
    }

    @Override
    public String toString() {
        return "#" + number + " " + owner + ": " + balance;
    }

    public static void main(String[] args) {
        BankAccount ada = BankAccount.open("Ada", 1000);
        BankAccount linus = BankAccount.open("Linus", 200);

        ada.transferTo(linus, 300);
        linus.withdraw(100);

        System.out.println(ada);
        System.out.println(linus);
        System.out.println(linus.getHistory());
    }
}

Output:

#1001 Ada: 700.0
#1002 Linus: 400.0
[DEP 200.0 -> 200.0, DEP 300.0 -> 500.0, WDR 100.0 -> 400.0]

The constructor is private, so accounts can only be created through the static factory open, which guarantees every account starts with a validated deposit. nextNumber is static because it is shared across all accounts; number, owner, balance, and history are instance fields because each account has its own. transferTo is an instance method that operates on two objects: this (the source) and target. record is a private instance helper that needs the object's balance; requirePositive is a private static helper because it checks only its argument. getHistory returns an unmodifiable copy so callers cannot alter the account's records.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Class Methods and get answers drawn from it.

Signed-in readers only.