DEV Community

loading...

The Java Constants Interface Anti-Pattern

darshitpp profile image Darshit Patel Originally published at darshit.dev ・4 min read

How do you define and use constants in Java?

Most advice on Internet has the following opinions:

  1. Declare public static final for constants in a class
  2. Do not use Interfaces for constants

The most common way to define a constant is in a class and using public static final. One can then use the constant in another class using ClassName.CONSTANT_NAME. Constants are usually defined in upper cases as a rule, atleast in Java.

So if I were to define a constant for the value of Pi(π), it would be something like:

public final class Constants {
    public static final double PI = 3.14;
}
Enter fullscreen mode Exit fullscreen mode

This can then be used as Constants.PI whenever we want to reference the value of Pi.

Another way one can define constants is by the use of interfaces.

public interface Constants {
    double PI = 3.14;
} 
Enter fullscreen mode Exit fullscreen mode

However, this is not recommended by most sources on the internet. Why? Because it is an anti-pattern.

But is it really an Anti-pattern?

Let's examine the difference by using both the methods.

  1. Creating a Constants class:
package constants;

public final class MathConstantsClass {
    public static final double PI = 3.14;
}
Enter fullscreen mode Exit fullscreen mode
  1. Creating an interface:
package constants;

public interface MathConstantsInterface {
    double PI = 3.14;
}
Enter fullscreen mode Exit fullscreen mode

Let us define another interface which will help us test both the above methods.

package operations;

public interface CircleArea {
    double calculate(double radius);
}
Enter fullscreen mode Exit fullscreen mode

The above interface would help us define a contract to calculate the area of a circle. As we know, the area of a circle is dependent only on its radius, and thus is reflected in the above interface.

The following class provides the implementation of calculating the area of a circle.

import constants.MathConstantsClass;
import operations.CircleArea;

public class MathConstantsClassImplementation implements CircleArea {
    public double calculate(double radius) {
        return MathConstantsClass.PI * radius * radius;
    }
}
Enter fullscreen mode Exit fullscreen mode

To test the the above code, let us write a Test class using JUnit.

import operations.CircleArea;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class MathConstantsClassImplementationTest {

    @Test
    void calculate() {
        CircleArea area = new MathConstantsClassImplementation();

        double circleArea = area.calculate(1.0);
        assertEquals(3.14, circleArea);
    }
}
Enter fullscreen mode Exit fullscreen mode

If you run the above piece of test code, the test would pass.

For testing how we can use the constants with Interface, let's write another class called MathConstantsInterfaceImplementation.

import constants.MathConstantsInterface;
import operations.CircleArea;

public class MathConstantsInterfaceImplementation implements MathConstantsInterface, CircleArea {
    public double calculate(double radius) {
        return PI * radius * radius;
    }
}
Enter fullscreen mode Exit fullscreen mode

Similarly a test for the above class is as follows:

import operations.CircleArea;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class MathConstantsInterfaceImplementationTest {

    @Test
    void calculate() {
        CircleArea area = new MathConstantsInterfaceImplementation();

        double circleArea = area.calculate(1.0);
        assertEquals(3.14, circleArea);
    }
}
Enter fullscreen mode Exit fullscreen mode

The above Test would pass. However, the argument against the implementation is that it is not a good practice as there could be field shadowing, and that will override the original value of the constant within the class.

It can be better understood with the following example:

import constants.MathConstantsInterface;
import operations.CircleArea;

public class MathConstantsWithInterfaceImplementationAndConstantShadowing implements MathConstantsInterface, CircleArea {
    private static final double PI = 200;

    public double calculate(double radius) {
        return PI * radius * radius;
    }
}
Enter fullscreen mode Exit fullscreen mode

If, by chance, someone overrode the value of PI inside the class, it would lead to an incorrect output. It can be easily verified by the following test.

import operations.CircleArea;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class MathConstantsWithInterfaceImplementationAndConstantShadowingTest {

    @Test
    void calculate() {
        CircleArea area = new MathConstantsWithInterfaceImplementationAndConstantShadowing();

        double circleArea = area.calculate(1.0);
        assertEquals(3.14, circleArea);
    }
}
Enter fullscreen mode Exit fullscreen mode

The above test fails. The answer returned by calculate() is 200.0 instead of the expected 3.14. Another argument is, using the interface would pollute the namespace and also lead to the value propagated across the subclasses.

The above arguments are valid, and hold true.

However, what no one mentions is that you can still directly use the constants from the interface without implementing the interface. Just like the first example where we use MathConstantsClass.PI, we can also use MathConstantsInterface.PI without affecting the namespace and inheritance and shadowing issues.

This can also be easily verified:

import constants.MathConstantsInterface;
import operations.CircleArea;

public class MathConstantsInterfaceWithoutImplementation implements CircleArea {
    public double calculate(double radius) {
        return MathConstantsInterface.PI * radius * radius;
    }
}
Enter fullscreen mode Exit fullscreen mode

Test class:

import operations.CircleArea;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class MathConstantsInterfaceWithoutImplementationTest {

    @Test
    void calculate() {
        CircleArea area = new MathConstantsInterfaceWithoutImplementation();

        double circleArea = area.calculate(1.0);
        assertEquals(3.14, circleArea);
    }
}
Enter fullscreen mode Exit fullscreen mode

It would make no difference to our way of implementation. Even the number of imports remain same. Moreover, you do not need additional boilerplate of public static final as members in an interface are public static final by default.

public static final double PI = 3.14;
Enter fullscreen mode Exit fullscreen mode

vs

double PI = 3.14;
Enter fullscreen mode Exit fullscreen mode

What would you prefer? Cleaner code, anyone?

I have seen most constants almost grouped together if they are used throughout the application. You could also suggest that interface should only be used for contracts, and in most cases they are. However, keeping an interface for solely storing constants doesn't seem to be wrong to me either!

Unless, of course, some developer tries to implement a class which solely contains constants -- which would beget the question -- WHY?

You can check out the code at my Github: https://github.com/darshitpp/JavaConstants

References:

  1. Constants in Java: Patterns and Anti-Patterns

Discussion (3)

pic
Editor guide
Collapse
ervin_szilagyi profile image
Ervin Szilagyi

My suggestion is to keep the constants as close to their usage as possible (as the reference article concludes). From my experience it is pretty rare to encounter such constants which represent a "global truth", like PI in the example above. If you encounter something like this, sure extract it if you want, but usually what happens is that your constant represents some kind of magic number of magic string which does make sense only in a given context. For those, just make a static final member variable and keep them private, it that is possible.
If you decide to extract the constant in an interface, don't implement that interface, statically importing the necessary constants would be enough. Although, this wont solve the shadowing problem. To solve that, I would avoid static imports as well, I would explicitly specify where my constant is coming from in case of every usage.

Collapse
darshitpp profile image
Darshit Patel Author

My suggestion is to keep the constants as close to their usage as possible

Yes, of course, that is true, and in such cases the usage is typically local or within the class itself. In these cases, it makes perfect sense to define constants using public static final within the classes they're being used. I, however, have encountered lots of usages of constants which are being shared across classes, and that is the point I was referring to.

Of course, constant interface shouldn't be implemented, and if someone really does that, one should have a quiet word with them in private :P

Collapse
alainvanhout profile image
Alain Van Hout

Indeed. I personally like this pattern, where an interface is used to group constants without actually implementing that interface. It reduces boilerplate, adds focus on the constants, and due to the namespacing adds meaning to the code.