16

I'm working on a larger solo project and right now, and I have several classes in which I do not see any reason to create an instance of.

My dice class right now, for example, stores all of its data statically and all of its methods are static too. I don't need to initialize it because when I want to roll the dice and get a new value, I just use Dice.roll().

I have several similar classes that only have one main function like this and I'm about to start working on a sort of "controller" class that will be in charge of all of the events (like when a player moves, and what current turn it is) and I've found that I could follow the same idea for this class. I don't ever plan on creating multiple objects for these specific classes, so would it be a bad idea to make them fully static?

I was wondering if this is considered "bad practice" when it comes to Java. From what I've seen, the community seems to be kind of split on this topic? Anyways, I would love some discussion on this and links to resources would be great too!

  • 1
    If your program is fully procedural. Why did you choose Java? – Laiv Apr 11 '18 at 6:22
  • 12
    Try writing unit tests for these classes, and you'll figure out why people don't like static methods that access static state. – Joeri Sebrechts Apr 11 '18 at 6:35
  • @Laiv I'm still kind of new to programming, about a year of C++, I'm taking a Java class this semester and I'm starting to like Java a lot more, the graphics libraries especially. – HexTeke Apr 11 '18 at 6:36
  • 5
    Regarding static classes. If static methods are pure (hold no state, have input arguments and a return type). There's nothing to be worried about – Laiv Apr 11 '18 at 6:49
20

There is nothing wrong with static classes that are truly static. That is to say, there is no internal state to speak of that would cause the output of the methods to change.

If Dice.roll() is simply returning a new random number from 1 to 6, it's not changing state. Granted, you may be sharing a Random instance, but I wouldn't consider that a change of state as by definition, the output is always going to be well, random. It is also thread-safe so there are no problems here.

You'll often see final "Helper" or other utility classes which have a private constructor and static members. The private constructor contains no logic and serves only to prevent someone from instantiating the class. The final modifier only brings this idea home that this isn't a class you'd ever want to derive from. It is merely a utility class. If done properly, there should be no singleton or other class members which are not themselves static and final.

So long as you follow these guidelines and you're not making singletons, there is absolutely nothing wrong with this. You mention a controller class, and this will almost certainly require state changes, so I would advise against using only static methods. You can rely heavily on a static utility class, but you cannot make it a static utility class.


What is considered a change in state for a class? Well, lets exclude random numbers for a second, as they're nondeterministic by definition and therefore the return value changes often.

A pure function is one which is deterministic, which is to say, for a given input, you will get one and exactly one output. You want static methods to be pure functions. In Java there are ways of tweaking behavior of static methods to hold state, but they're almost never good ideas. When you declare a method as static, the typical programmer will assume right off the bat that it is a pure function. Deviating from expected behavior is how you tend to create bugs in your program, generally speaking and should be avoided.

A singleton is a class containing static methods about as opposite of "pure function" as you can be. A single static private member is kept internally to the class which is used to ensure there is exactly one instance. This is not best practice and can get you into trouble later for a number of reasons. To know what we're talking about, here is a simple example of a singleton:

// DON'T DO THIS!
class Singleton {
  private String name; 
  private static Singleton instance = null;

  private Singleton(String name) {
    this.name = name;
  }

  public static Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton("George");
    }
    return instance;
  }

  public getName() {
    return name;
  }
}

assert Singleton.getInstance().getName() == "George"
  • 7
    If I want to test what happens when a double six is rolled, I’m stuck here as you have s static class with a static random number generator. So no, Dice.roll() is not a valid exception to the “no global state” rule. – David Arno Apr 11 '18 at 6:48
  • 1
    @HexTeke Updated answer. – Neil Apr 11 '18 at 6:55
  • 1
    @DavidArno True, but I suppose at that point we're really in trouble if we're testing a single call to random.nextInt(6) + 1. ;) – Neil Apr 11 '18 at 6:57
  • 3
    @Neil, apologies, I didn't explain myself very well. We aren't testing the random number sequence, we are affecting that sequence to assist other tests. If we are testing that eg RollAndMove() rolls again when we supply a double six, then the easiest, most robust way to do that is to mock out either Dice or the random number generator. Ergo, Dice does not want to be a static class using a static random generator. – David Arno Apr 11 '18 at 7:13
  • 12
    Your update hits the issue on the head though: static methods should be deterministic; they should have no side effects. – David Arno Apr 11 '18 at 7:15
9

To give an example of the limitations of a static class, what if some of your gamers want to get a slight bonus on their die rolls? And they are willing to pay big bucks! :-)

Yes, you could add another parameter, so Dice.roll(bonus),

Later you need D20s.

Dice.roll(bonus, sides)

Yeah, but some players have the "supremely capable" feat so they can never "fumble" (roll a 1).

Dice.roll(bonus, sides, isFumbleAllowed).

This is getting messy, is it not?

  • this seems to be orthogonal to the question, its getting messy whether these are static methods or normal methods – jk. Apr 11 '18 at 15:46
  • 4
    @jk, I think I understood his point. Does not make sense to have a Dice static class when you think in more types of dices. In that case, we can have different dice objects, modeling them with good OOP practices. – Dherik Apr 11 '18 at 15:59
  • 1
    @TimothyTruckle I'm not disputing that, all I'm saying is that this answer does not actually answer the question, because this kind of scope creep has nothing to do with the method being static or not. – nvoigt Apr 11 '18 at 20:01
  • 2
    @nvoigt "I'm not disputing that" - Well, I do. The most powerfull feature of an OO language is polymorphism. And static access effectively prevents us from using that at all. And you're right: new Dice().roll(...) has to be concidered static access as well as Dice.roll(...). We only benefit if we inject that dependency. – Timothy Truckle Apr 11 '18 at 20:15
  • 2
    @jk the difference being that static messiness happens in every call, and the knowledge needed is required in every call, so it gets globally splashed all over you application. In an OOP structure, only the constructor / factory need be messy, and the details of that die are encapsulated. After that, use polymorphism and just call roll(). I might edit this answer to clarify. – user949300 Apr 11 '18 at 22:35
3

In the particular case of a Dice class I think using instance methods rather than statics will make testing significantly easier.

If you want to unit test something that uses an instance of Dice (e.g. a Game class) then from your tests you can inject some form of test double of Dice that always returns a fixed sequence of values. Your test can check that the game has the right outcome for those dice rolls.

I'm not a Java developer but I think it would be significantly harder to do that with a fully static Dice class. See https://stackoverflow.com/questions/4482315/why-does-mockito-not-mock-static-methods

  • Just for the records: In Java we have PowerMock to replace static dependencies by manipulating the byte code. But using it is just a surrender to bad design... – Timothy Truckle Apr 11 '18 at 20:18
0

This is actually known as the Monostate pattern, where every instance (and even a "no-instance") is sharing their status. Every member is a class member (i.e., no instance members). They're commonly used to implement "toolkit" classes that bundle a set of methods and constants related to a single responsibility or requirement but don't need a state in order to work (they're purely functional). In fact, Java comes with some of them bundled (for instance, Math).

A bit off-topic: I rarely agree with the naming of keywords in VisualBasic, but in this case, I think shared is certainly clearer and semantically better (it is shared among the class itself and all of its instances) than static (remains after the lifecycle of the scope it's declared in).

protected by gnat Nov 12 '18 at 14:02

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.