Methods belong to Classes
So far we have used the class mechanism as a data-modeling tool, and we've presented methods as a way to encapsulate some code as a single operation. In Java, these two concepts go together: every method is part of some class. Let's go through this using Rock-Paper-Scissors as an example. The game state of Rock-Paper-Scissors is pretty simple: we just need to keep score. So we can start by modeling the game-state of the game like this:
class RockPaperScissorsGame {
int player1Score;
int player2Score;
}
enum Choice { ROCK, PAPER, SCISSORS }
In the previous section we showed two methods for this game, called beats and tie:
boolean beats ( Choice player1Choice, Choice player2Choice )
{
return player1Choice == Choice.ROCK && player2Choice == Choice.SCISSORS
|| player1Choice == Choice.PAPER && player2Choice == Choice.ROCK
|| player1Choice == Choice.SCISSORS && player2Choice == Choice.PAPER;
}
boolean tie ( Choice player1Choice, Choice player2Choice )
{
return player1Choice == player2Choice;
}
We showed them standing alone like this, but in a Java program they must be part of a class. Syntactically, this is what “part of” looks like:
class RockPaperScissorsGame {
int player1Score;
int player2Score;
boolean beats ( Choice player1Choice, Choice player2Choice )
{
return player1Choice == Choice.ROCK && player2Choice == Choice.SCISSORS
|| player1Choice == Choice.PAPER && player2Choice == Choice.ROCK
|| player1Choice == Choice.SCISSORS && player2Choice == Choice.PAPER;
}
boolean tie ( Choice player1Choice, Choice player2Choice )
{
return player1Choice == player2Choice;
}
}
enum Choice { ROCK, PAPER, SCISSORS }
Note that the declarations of beats and tie are enclosed within the body of the RockPaperScissors class (between the { and } curly braces). They are at the same level as the field declarations. Methods and fields are both called members of the RockPaperScissors class.
Instance methods
Here's a new example. Take the concept of “distance” as in the distance between two cities. The distance between Detroit and Chicago is 282 miles, or 454 kilometers (according to Rand-McNally). A distance is both a number and the units of that number. Here is the Distance class as we modeled it in the Data Modeling – Classes section:
class Distance {
double value;
Units units;
}
enum Units { Kilometers, Miles }
Suppose we create an instance of Distance, like this:
Distance detToChi = new Distance(); detToChi.value = 282.0; detToChi.units = Units.Miles;
Now suppose we want that value in kilometers. This requires simply multiplying by a conversion factor (1.609 kilometers per mile), but we have to know what that conversion factor is. So we could imagine a method that makes that conversion easy:
class Distance {
double value;
Units units;
double KILOMETERS_PER_MILE = 1.609;
double inKilometers() {
return units == Units.Kilometers ? value : value * KILOMETERS_PER_MILE;
}
}
If the value is already in kilometers (units == Units.Kilometers), it is returned as-is; if not, it is multiplied by 1.609, the number of kilometers in one mile.
Notice that the method inKilometers has no parameters. It performs its function using only the fields of the Distance class. Thus when we invoke a method, we need to include the class instance we are invoking it on. If we have an instance detToChi, we invoke a method on it like this: detToChi.inKilometers(). In this invocation, inKilometers will use the fields value and units from the detToChi instance. If we had another instance, detToCle (Detroit to Cleveland), and we invoked inKilometers on it (detToCle.inKilometers()) the inKilometers method would use the fields value and units from the detToCle instance.
We can imagine another useful method, which returns a human-readable representation of the distance:
class Distance {
double value;
Units units;
double KILOMETERS_PER_MILE = 1.609;
double inKilometers() {
return units == Units.Kilometers ? value : value * KILOMETERS_PER_MILE;
}
String toString() {
return value + (units == Units.Kilometers ? "km" : "mi");
}
}
The Distance class now has both fields and methods. A class's fields and methods are collectively referred to as its members.
inKilometers is known as an “instance method” because it is invoked on class instances. Likewise, value and units are known as “instance variables” because they are part of class instances. We use these names to distinguish instance methods and instance variables from instances and methods declared with the static keyword. The latter are known as “static instances” and “static methods”.
Methods returning void
The methods we have seen so far all have a purpose of computing some value and returning it. But there is another pattern of method where the purpose is to modify the values of an object's fields, and where returning a value is not part of this purpose. An example of such a method for the Distance class would be a method that converted a Distance object from one units to another. Suppose we create a Distance object like we did before:
Distance detToChi = new Distance(); detToChi.value = 282.0; detToChi.units = Units.Miles;
But suppose we now want to convert this object from using miles to using kilometers. We can imagine a convert() method to do that, which we would invoke like this:
detToChi.convert(Units.Kilometers);
Now if we call toString() on the detToChi object, we will get “453.738km” instead of “282.0mi.”
The convert() method does not return a value. So when we define the method, we use the return type of void. Here is how the method would be declared:
class Distance {
double value;
Units units;
double KILOMETERS_PER_MILE = 1.609;
void convert(Units newUnits) {
// first, change the value according to the new units
value =
newUnits == Units.Kilometers
// we are converting to KM, so don't do anything if units
// are already KM, else multiply by KILOMETERS_PER_MILE.
? (units == Units.Kilometers ? value : value * KILOMETERS_PER_MILE)
// we are converting to miles, so don't do anything if units
// are already in miles, else divide by KILOMETERS_PER_MILE.
: (units == Units.Miles ? value : value / KILOMETERS_PER_MILE);
// lastly, change the units
units = newUnits;
}
// not showing the other method definitions for Distance
}
The Java compiler will give you an error if you try to return a value from a method with a void return type, and will likewise, in a method with a non-void return type, give you an error if it cannot determine that you will return a value.
There is nothing to prevent you from writing a method that modifies an object's fields and returns a value as well. Sometimes that is the best way to do something. But in general it is a good idea to stick to (1) “value” methods that compute and return a value but do not modify an object's fields, or (2) “side-effect” methods that modify a value's fields but do not return a value. Staying with these two patterns as much as possible can make it easier to understand and reason about your code.