Thoughts on static methods

Keywords: #Java

I’ve had to argue more than once why I’m making some method static, so I decided to write it down.

Methods that use no instance variables should be made static. Making the method static restricts its possibilities, making it easier to reason about. It’s more restricted in the sense that it’s impossible for it to mutate any instance variable of the class where it’s defined. If it’s static:

  1. You know it doesn’t reference any state of the class where it’s defined; it can’t.
  2. It’s highly likely it has no side-effect on the object where it’s defined. Sure, it could be mutating some static field, but that would most likely be terrible design, so one can usually discard that. It’s also possible that it mutates state of one of its arguments, so the method need not be side-effect free. But all that and more is possible with instance methods too.

All this information is also instantly accessible through the IDE, because most use italics by default for these methods (this tends to be configurable), so when seeing the italics you know it’s some utility method with those characteristics.

It’s pointless to require instantiating an object to call a method that makes no use of that object’s state (with the exception of methods meant to be overriden).

A lot of the time making a method static also reveals others can be static too (those calling it), which can end up uncovering a set of methods that makes sense to extract into a utility class.

Exposing temporal coupling

Static methods also make explicit any temporal coupling1. As a rough example, say you have the following:

private class SomeClass {
    private final Map<String, String> map = new HashMap<>();

    public void someMethod() {
	    /* ... some code ... */

	    computeMapEntries();

	    /* ... a bunch of other code ... */

	    doSomethingWithMap();
    }

    private void computeMapEntries() { /* something */ }
    private void doSomethingWithMap() { /* something */ }
}

In such a code, having the map entries computed is a prerequisite for calling doSomethingWithMap(), which will use those values. There is thus a temporal coupling between both methods, but this is nowhere specified nor enforced. It could be that as part of future changes, someone reorders the calls and introduces a bug. If, however, doSomethingWithMap() took the map as a parameter, then the dependency is made explicit. Assuming no other instance field needs to be accessed, then doSomethingWithMap() can be static, which would enforce the dependency (the implementation cannot unexpectedly reference the map nor any other such field). We might then be able also to avoid defining map as an instance variable entirely and instead declare it only inside someMethod().

private class SomeClass {

    public void someMethod() {
	    /* ... some code ... */

   	    final Map<String, String> map = computeMapEntries();

	    /* ... a bunch of other code ... */

	    doSomethingWithMap(map);
    }

    private Map<String, String> computeMapEntries() { /* something */ }
    private void doSomethingWithMap() { /* something */ }
}

With this, we further reduced the scope of the variable, which helps to avoid misuses, e.g. by having the IDE suggest it or auto-complete it in a place where it wasn’t meant to be used. In particular, the following doesn’t compile anymore:

doSomethingWithMap(map);

/* ... a bunch of other code ... */

final Map<String, String> map = computeMapEntries();

Testability

Detractors of static methods sometimes argue that static methods worsen testability as they cannot be overriden, and they are harder to mock (mocking frameworks allow mocking static methods, with the downside that those tests cannot be parallelized.) Why would one mock, for instance, Math.abs? If the method is pure, then that’s the best case for testing: you can call the method with a bunch of inputs and assert the outputs. If the static method uses some mutable dependency (for example a database, or time such as with LocalDateTime.now()), then I see why you’d want to mock or somehow change that. But that’s because you are doing a bad usage of a static method and should rethink your design (for instance, by using a java.time.Clock and calling LocalDateTime.now(clock) instead). Also, mocks should only be used for shared, unmanaged out-of-process dependencies 2, and since these dependencies tend to be few, most of the time you actually shouldn’t be mocking.

I won’t spend time here with other arguments such as “static methods are not pure OOP”, whatever that means. Also, that presuposes pure OOP-ness is something desirable.

Instance vs. static method

All this doesn’t mean we should turn all private instance methods into private static ones by adding each referenced instance field as an argument to the method. After all, we can always change:

public class AnotherClass {
    private int intField;
    private String stringField;
    private Service service;

    private void method() {
        /* uses intField, stringField and service */
    }
}

into:

public class AnotherClass {
    private int intField;
    private String stringField;
    private Service service;

    private static void method(int intField, String stringField, Service service) {
        /* uses intField, stringField and service */
    }
}

and pass the arguments when calling method().

When the referenced state is immutable (e.g. it’s a final field which points to an stateless service initialized on construction), then taking it as an argument instead of directly referencing it is pointless and more verbose.

The same is true if the referenced state is always valid, for example when the object is shared and has some feature flag:

private volatile boolean featureEnabled;

private void setFeatureEnabled(boolean featureEnabled) {
    this.featureEnabled = featureEnabled
}

Then again, it makes sense to use the field directly in an instance method, as both boolean states are valid (whether the feature is enabled or not is probably something that some other thread defines).

Another obvious case where we cannot make a method static—even if no state is used—is when the method is meant to be overriden by subclasses.

But these cases aside, I tend to make static any method that doesn’t depend on an object’s state. The change can also be suggested, for instance, by Eclipse (as a warning) or IntelliJ IDEA (as an inspection).


  1. Sometimes also found as sequential coupling, temporal coupling goes back to at least to Hunt and Thomas’ book The Pragmatic Programmer. ↩︎

  2. When to Mock ↩︎