This is part of a brief series on Good Habits For Java Programmers.
In the beginning...
When you write your first program in Java, you encounter the need to have a method with the signature
public static void main(String args[])
that serves as the entry point for your program. When you run your program, somehow the Java runtime finds this magic entry point, calls into, and you're off to the races. You'll have to house this method in a class, because that's just how it works.
Then, whatever you learn next, you'll probably be reading and writing short programs that exercise the new Java language bits inside that public static void main
method. Maybe you're learning how to iterate with a for
loop, and so read or write a little program to sum up all the evens between 0 and 10.
public static void main(String args[]) {
int sumOfEvens = 0;
for (int even = 0; even <= 10; even += 2) {
sumOfEvens += even;
}
System.out.println("The sum of evens from 0 to 10 is " + sumOfEvens);
}
It makes sense to keep that code in main
from a teaching and learning standpoint: it's the simplest place to put the code, and by introducing no additional complexity, the instructor and you get to focus on the other concepts being taught -- say, how a for loop works or how to use a local variable (sumOfEvens
) or how the algorithm works (adding 2 each time takes us to the next even). I have no problem with this teaching methodology. In fact, I think it's a good idea to leave you to code in main
while you focus on other things.
main is simply an entry point. It is not for modeling your data or problem space.
But let's move forward some number of weeks in your learning. You're starting to learn how to use classes to model a domain, and your assignment is to create a program that asks you to hardcode the length of the side of a square, and print out the area and the perimeter. So you create something like this:
public class Square {
public static void main(String args[]) {
float sideLength = 4.0;
System.out.println("The area of the square is " + sideLength * sideLength);
System.out.println("The perimiter of the square is " + 4 * sideLength);
}
}
This is a good start. It does what's asked. And it's somewhat nice that the class is named Square
. The name of the class tells me that we're doing something related to a Square in this class, but we're not really doing much modeling of a Square here. There's no representation of a side's length in the Square
class and there are no methods that we'd expect on a Square
class. We call the class Square
, but since all of the logic for the squarey things we do live in main
, there actually isn't much that's squarey about this class, other than that the magic Java program entry point, main
, is packed with a series steps that relate to doing some operations on a square.
Let's dwell on this. The main
method is not the best place to put your algorithms or to model the problem domain you're modeling: the need for a main
comes from a contract between the Java runtime and you about how to start your program. That's it. It's how the Java runtime finds a place to start your program, and that is its primary role. It's not for modeling your problem space or writing interesting algorithms: that modeling and those interesting algorithms belong in the distinctive methods and and fields of your classes. Treat main
solely as an entry point for the Java runtime to find, and put interesting work into your class methods.
Don't get stuck in the static
trap
So you learn about class methods and class fields, and you rewrite your Square
class:
public class Square {
private static float sideLength = 0.0f;
public static void main(String args[]) {
setSideLength(3.2f);
System.out.println("The area of the square is " + getArea());
System.out.println("The perimiter of the square is " + getPerimeter());
}
public static void setSideLength(float length) {
sideLength = length;
}
public static float getArea() {
return sideLength * sideLength;
}
public static float getPerimeter() {
return 4 * sideLength;
}
}
This is an improvement. I can see more why this class is named Square
: it stores a side length, and it has methods to calculate the area and perimeter of a square, using the correct algorithms for them based on a square's side's length.
But there's something funny here. Why is everything static
? The field sideLength
is static
, and so are getArea
and getPerimeter
? In my experience, this is not so much a conscious choice, but a carry over from the fact that main
is static
. main
has to be static
because that's the contract with the Java runtime. And students get stuck then figuring out how to not carry on that static
. They find that they can't call directly a method named setSideLength
from their static
main
unless setSideLength
is static
, too. And then a static
setSideLength
can't set a value on that field sideLength
unless it's static
.
So there are two big issues here. The first is whether we want all these to be static
. We don't, and I'll explain why. But more than that, the bigger conceptual issue is that it's not that students have made a choice I disagree with to make the field and the methods of Square
static
: it's that students feel that they are forced into it by their static
main
.
There's a big difference between static
fields and non-static
(instance or member) fields and a big difference between static
methods and non-static
(instance or member) methods. The difference I want to focus on for now is that a static
field is shared among all instances of Square
s, where each instance of Square
has its own private copy of non-static
fields. Conceptually, do we want to make it so that all Square
s have the side length 3.2 when I set that value, or that just a particular Square
has a side length of 3.2? Or put more broadly, do we want the Square
class to model squares as if they all share the same side length, or do we want to allow it to model that different squares have different side lengths? The second, for sure. In that case, we don't want sideLength
to be static
.
Okay, so we don't want that field to be static
, but as I said, most students don't choose to make it static
: they feel forced into it because of the call to setSideLength
in their public void static main
, which itself is seemingly forced into being static
The solution is to create an instance of Square
, and call a method on that:
public class Square {
private float sideLength = 0.0f;
public static void main(String args[]) {
Square square = new Square();
square.setSideLength(3.2f);
System.out.println("The area of the square is " + square.getArea());
System.out.println("The perimiter of the square is " + square.getPerimeter());
}
public void setSideLength(float length) {
sideLength = length;
}
public float getArea() {
return sideLength * sideLength;
}
public float getPerimeter() {
return 4 * sideLength;
}
}
Much better! We now have made sideLength
a non-static, instance field, and we access it through non-static, instance members. We should probably add a constructor to Square
that takes an argument for the side length, too, but let's leave it at this.
Summary
There are two lessons here I want to summarize:
- It's very easy to feel stuck having to make everything in a class
static
because you start calls to it from astatic
method,public static void main
. But you don't have to be stuck: within yourmain
, you can create an instance of your class and then make instance method calls on that instance. Those instance methods can access instance fields. If you want to make a field or method on your classstatic
because that's appropriate, too, then great! But you don't have to. -
public static void main
serves as a rendezvous point between the Java runtime and your program. It is a contract between you and Java about how to start your program. That's it. It's not the place to put parts of your modeling or algorithms specific to your classes and their desired behaviors.
Here's a related post about where main
should live.
Top comments (0)