3.5.1 Writing a Void Method in a Class
In Topics 3.3 and 3.4 you built classes with private instance variables and a constructor. A StepTracker object can be created holding 4,200 steps, but then it just sits there. Its instance variable is private, so main cannot even look at it. An object that cannot do anything is not very useful. A class needs behaviors: things its objects can do, such as report their steps or record a walk.
You already know how to read methods. In Topic 1.9 you read headers like public static void printTotal(double ticketPrice, int tickets), and in Topic 1.14 you called methods on objects with a dot, as in word.length(). Now you write methods yourself, inside your own class, so that your objects can be called the same way.
In plain words: a method written inside a class is a named set of instructions that every object of that class carries. When you call it on one object, the instructions run using that object’s instance variables.
Here is the simplest kind. A void method is a method that does not return a value. Its header has the keyword void in front of the method’s name, in the spot where a return type would go. Because nothing comes back, a call to a void method is always a statement on its own, like tracker.printSummary();.
Here is a void method inside the StepTracker class, with each piece of its header labeled:
public void printSummary()
{
System.out.println("Steps so far: " + steps);
}
| Piece | What it means |
|---|---|
public |
Code in other classes, such as main, may call this method (Topic 3.3). |
void |
The method gives nothing back to the caller. |
printSummary |
The method’s name. Callers use it: tracker.printSummary(); |
() |
The parameter list. It is empty here, so a call passes no arguments. |
{ … } |
The method body: the statements that run on each call. They can use the instance variable steps directly by name. |
The body uses steps even though steps is private. That is allowed because the method is inside the class; private only keeps out code from other classes. This is the reason classes have methods at all: the method is the class’s own, approved way to work with its private data.
Notice also that printSummary has no static. The methods you read in Topic 1.9 had it, but a method that works with one object’s instance variables leaves it out. Topic 3.7 explains what static means.
Key idea
A method written inside a class can use that class’s instance variables by name. A void method has void before its name, returns nothing, and is called as a statement on its own.
Worked example: Calling a void method you wrote
Each example program in this chapter puts two classes in one file so that it runs in one piece. Java allows only one public class per file, and its name must match the file name, so the class with main is the public one and the other class leaves public off. In a real project, each class goes in its own file and is declared public.
Read the StepTracker class first (lines 1–14), then follow main (lines 18–24).
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public void printSummary()
{
System.out.println("Steps so far: " + steps);
}
}
public class FirstVoidMethod
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
System.out.println("Checking tracker...");
tracker.printSummary();
System.out.println("Done.");
}
}| Step | Line running | What happens | Printed |
|---|---|---|---|
| 1 | 20 | The constructor runs (lines 5–8) and sets this object’s steps to 4200 |
|
| 2 | 21 | An ordinary print in main |
Checking tracker... |
| 3 | 22 | Call on tracker. Control jumps to line 10, the start of printSummary |
|
| 4 | 12 | The body uses tracker’s steps, which is 4200 |
Steps so far: 4200 |
| 5 | 23 | The method’s body is finished, so control returns to the line after the call (1.9.3) | Done. |
The output is:
Checking tracker...
Steps so far: 4200
Done.Line 22 is a statement on its own. Nothing is stored and nothing is printed by main on that line; all the printing happens inside the method.
Now vary two things: put an if statement inside the method’s body, and call the method on two different objects. The body of a method can hold any statements you have learned, including selection and loops from Unit 2.
class LibraryCard
{
private int booksOut;
public LibraryCard(int startBooks)
{
booksOut = startBooks;
}
public void printStatus()
{
if (booksOut > 3)
{
System.out.println(booksOut + " books: limit reached");
}
else
{
System.out.println(booksOut + " books: OK");
}
}
}
public class LibraryCards
{
public static void main(String[] args)
{
LibraryCard ana = new LibraryCard(2);
LibraryCard ben = new LibraryCard(5);
System.out.println("Report");
ben.printStatus();
ana.printStatus();
}
}Report
5 books: limit reached
2 books: OK
ana and ben are separate objects, each with its own booksOut (Topic 3.4). On line 30, ben.printStatus() runs the method with ben’s booksOut, which is 5, so the condition 5 > 3 is true and line 14 runs. On line 31, the very same method runs with ana’s booksOut, which is 2, so the else branch runs. The method was written once, but what it does depends on which object it was called on. The order of output follows the order of the calls in main, not the order the objects were created.
Watch out
Leaving out the return type. Students who know that a void method “returns nothing” sometimes think they can leave the return type out entirely.
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public printSummary()
{
System.out.println("Steps so far: " + steps);
}
}
public class MissingReturnType
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
tracker.printSummary();
}
}Java reports:
MissingReturnType.java:10: error: invalid method declaration; return type requiredEvery method header must say what it returns, and “nothing” is said with the word void. (Only a constructor has no return type, which is how Java tells a constructor apart from a method, and a constructor’s name must match the class.) Fix line 10 by writing public void printSummary().
Pause and try
Question 1 · Multiple choice
Using the StepTracker class from FirstVoidMethod.java, a student adds this line to main:
int shown = tracker.printSummary();What happens?
- It compiles, prints
Steps so far: 4200, and stores 4200 inshown. - It does not compile, because
printSummaryis a void method, so its call has no value to store. - It compiles and stores 0 in
shown, because the method returns nothing. - It does not compile, because
stepsis private.
Check your reasoning
B. A void method returns no value, so a call to it cannot appear where a value is needed. Java reports incompatible types: void cannot be converted to int. Printing a value is not the same as returning it.
- A is wrong: the method prints 4200 to the screen, but printing does not hand the number back to
main. - C is wrong: “returns nothing” does not mean “returns 0”. There is no value at all, so Java refuses to compile the line.
- D is wrong: the line in
mainnever mentionssteps. The method itself may usestepsbecause it is inside the class.
Reread 3.5.1 Writing a Void Method in a Class if you chose differently.
Question 2 · Tracing
What does this program print?
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public void printSummary()
{
System.out.println("Steps so far: " + steps);
}
}
public class TwoTrackers
{
public static void main(String[] args)
{
StepTracker maya = new StepTracker(4200);
StepTracker leo = new StepTracker(9800);
leo.printSummary();
maya.printSummary();
leo.printSummary();
}
}Check your reasoning
Steps so far: 9800
Steps so far: 4200
Steps so far: 9800Lines 20 and 21 create two separate objects: maya with 4200 steps and leo with 9800. Each call runs printSummary with the steps of the object before the dot. Line 22 uses leo’s 9800, line 23 uses maya’s 4200, and line 24 uses leo’s 9800 again. The output follows the order of the calls, and calling a void method twice simply runs its body twice.
Reread 3.5.1 Writing a Void Method in a Class if your output differed.
3.5.2 Mutator Methods Change an Object
A step tracker that only reports 4,200 steps forever is broken. When you walk, the count has to go up. Could main just add to the variable directly? Try it:
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
}
public class PrivateAccess
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
tracker.steps = tracker.steps + 500;
}
}Java refuses: PrivateAccess.java:16: error: steps has private access in StepTracker. As Topic 3.3 explained, private keeps code in other classes from touching the instance variable. That protection is on purpose, but it means the class itself must provide a way to change the value.
In plain words: the class offers a public “change it” button, and anyone who wants the value changed has to press that button. The class decides exactly what pressing it does.
A mutator method (also called a modifier method) is a method that changes the values of an object’s instance variables. A mutator is often a void method, because its job is to change the object, not to hand back an answer.
Most mutators need to know how much to change. Recall from Topic 1.9 that a parameter is a variable in the header that receives the argument’s value when the method is called. A method receives values through its parameters and uses those values to do its job:
public void addSteps(int newSteps)
{
steps = steps + newSteps;
}
The parameter newSteps holds whatever number the caller passes. The body adds it to the instance variable steps and stores the result back in steps. Because steps belongs to the object, the new value is still there after the method ends. Notice the parameter has a different name from the instance variable; that keeps it clear which one each line means.
Key idea
A mutator method changes an object’s instance variables, and it is often void. Its parameters bring in the values it needs; the changes it makes to instance variables last after the method ends.
Worked example: Watching a mutator change the object
Follow tracker’s steps through each call.
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public void addSteps(int newSteps)
{
steps = steps + newSteps;
}
public void printSummary()
{
System.out.println("Steps so far: " + steps);
}
}
public class AddSteps
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
tracker.addSteps(2500);
tracker.printSummary();
tracker.addSteps(800);
tracker.printSummary();
}
}| Line running | What happens | newSteps |
tracker’s steps |
Printed |
|---|---|---|---|---|
| 25 | The constructor sets steps to 4200 |
4200 | ||
| 26 → 12 | addSteps(2500): newSteps receives 2500; 4200 + 2500 is 6700 |
2500 | 6700 | |
| 27 → 17 | printSummary reads the updated value |
6700 | Steps so far: 6700 |
|
| 28 → 12 | addSteps(800): newSteps receives 800; 6700 + 800 is 7500 |
800 | 7500 | |
| 29 → 17 | printSummary again |
7500 | Steps so far: 7500 |
The newSteps column is blank between calls because a parameter exists only while its method is running. Each call gets a fresh newSteps holding that call’s argument. The instance variable, by contrast, keeps its value from one call to the next: the second call starts from 6700, not 4200.
Now vary something: a mutator does not need a parameter if it always makes the same change. Here a reset method sets the count back to 0, and the order of the calls matters.
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public void addSteps(int newSteps)
{
steps = steps + newSteps;
}
public void reset()
{
steps = 0;
}
public void printSummary()
{
System.out.println("Steps so far: " + steps);
}
}
public class ResetSteps
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
tracker.addSteps(1000);
tracker.reset();
tracker.addSteps(300);
tracker.printSummary();
}
}Steps so far: 300
Line 31 raises steps from 4200 to 5200. Line 32 calls reset, which replaces 5200 with 0 no matter what it was. Line 33 adds 300 to 0. If lines 32 and 33 were swapped, the 300 would be wiped out and the program would print Steps so far: 0. A mutator changes the object’s state at the moment it is called, so later calls start from whatever state earlier calls left behind.
Watch out
Storing the answer in the parameter instead of the instance variable. This version of addSteps computes the right sum but puts it in the wrong place:
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public void addSteps(int newSteps)
{
newSteps = steps + newSteps;
}
public void printSummary()
{
System.out.println("Steps so far: " + steps);
}
}
public class MutatorWrong
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
tracker.addSteps(2500);
tracker.printSummary();
}
}It compiles and prints Steps so far: 4200, as if the walk never happened. Line 12 stores 6700 in newSteps, the parameter. The parameter disappears when the method ends, and steps was never assigned, so the object is unchanged. Students make this mistake when they think of “the variable in the method” as the one that matters. The variable on the left of = is the one that changes, so a mutator must assign to the instance variable: steps = steps + newSteps; as in AddSteps.java.
Pause and try
Question 3 · Tracing
What does this program print? Keep a separate column for each object.
class Thermostat
{
private int temperature;
public Thermostat(int startTemperature)
{
temperature = startTemperature;
}
public void raise(int degrees)
{
temperature = temperature + degrees;
}
public void lower(int degrees)
{
temperature = temperature - degrees;
}
public void printSetting()
{
System.out.println("Set to " + temperature);
}
}
public class ThermostatTrace
{
public static void main(String[] args)
{
Thermostat hall = new Thermostat(68);
Thermostat office = new Thermostat(70);
hall.raise(4);
office.lower(3);
hall.lower(1);
hall.printSetting();
office.printSetting();
}
}Check your reasoning
Set to 71
Set to 67| Line | Call | hall’s temperature |
office’s temperature |
|---|---|---|---|
| 30 | constructor | 68 | |
| 31 | constructor | 68 | 70 |
| 32 | hall.raise(4) |
72 | 70 |
| 33 | office.lower(3) |
72 | 67 |
| 34 | hall.lower(1) |
71 | 67 |
Each mutator call changes only the object before the dot. office.lower(3) has no effect on hall, because each object has its own temperature.
Reread 3.5.2 Mutator Methods Change an Object if your output differed.
Question 4 · Debugging
A tip jar should add each tip to its running total. Starting at 10.0, after tips of 2.5 and 4.0 it should print Tips: 16.5, but it prints Tips: 4.0. Find the error and correct it.
class TipJar
{
private double total;
public TipJar(double startTotal)
{
total = startTotal;
}
public void addTip(double tip)
{
total = tip;
}
public void printTotal()
{
System.out.println("Tips: " + total);
}
}
public class TipJarBug
{
public static void main(String[] args)
{
TipJar jar = new TipJar(10.0);
jar.addTip(2.5);
jar.addTip(4.0);
jar.printTotal();
}
}Check your reasoning
The error is on line 12. total = tip; replaces the total with the newest tip instead of adding to it. After the first call total is 2.5, and after the second it is 4.0; the starting 10.0 and the first tip are lost. The corrected line is:
total = total + tip;Now the trace is 10.0 → 12.5 → 16.5, and the program prints Tips: 16.5. The original compiled because assigning a double to a double is legal; the mistake is in the logic, so Java cannot catch it. A mutator that accumulates must use the instance variable’s old value on the right side of =.
Reread 3.5.2 Mutator Methods Change an Object if you found a different error.
3.5.3 Non-Void Methods Return a Value
Suppose a fitness app needs to know how many steps are left until the daily goal, so it can show a progress bar or work out how many laps remain. A void method could print the number, but printed text goes to the screen, not back into the program. main could not do math with it. The class needs a method that hands back a number.
In Topic 1.9 you used methods like Math.sqrt that return a value, and you saw that Java uses the returned value in place of the call. Now you write the method that does the returning.
A non-void method is a method that returns exactly one value to its caller. Its header puts a return type, such as int, double, or boolean, in the spot where a void method has void. Its body must contain a return statement: the keyword return followed by an expression.
public int stepsToGoal()
{
return goal - steps;
}
When return goal - steps; runs, Java first evaluates the expression to a single value, then sends that value back to the caller. What travels back is the value itself, a number like 5800, not the variables goal or steps. This is called return by value.
The value must be compatible with the return type, meaning it must be a value Java would let you assign to a variable of that type. The rules are the ones from Topics 1.2 and 1.5: an int value is fine in a method that returns double (Java widens it automatically), but a double value in a method that returns int would lose its decimal part, so Java refuses to compile it.
Key idea
A non-void method’s header names a return type instead of void. Its return statement evaluates an expression and sends that one value back to the caller, and the value must be compatible with the return type.
Worked example: Writing and using an int method
stepsToGoal returns the steps still needed. Watch the two different ways main uses the returned value.
class StepTracker
{
private int steps;
private int goal;
public StepTracker(int startSteps, int dailyGoal)
{
steps = startSteps;
goal = dailyGoal;
}
public int stepsToGoal()
{
return goal - steps;
}
}
public class StepsToGoal
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200, 10000);
int left = tracker.stepsToGoal();
System.out.println("Steps left: " + left);
System.out.println("Laps left: " + tracker.stepsToGoal() / 500);
}
}Steps left: 5800
Laps left: 11- Line 22 creates a tracker with
steps4200 andgoal10000. - Line 23 calls
stepsToGoal. Control jumps to line 14, which evaluates10000 - 4200to 5800 and returns it. Back on line 23, the call is replaced by 5800, so the statement becomesint left = 5800;. The value is stored. - Line 24 prints
Steps left: 5800. - Line 25 calls the method again. It returns 5800 again (nothing has changed the object), and the call is replaced by that value:
"Laps left: " + 5800 / 500. Integer division gives 11, so it printsLaps left: 11. This time the value is used in an expression and never stored.
The return type int matches: goal - steps is an int minus an int, which is an int.
Now vary the return type. The same class can have methods that return a double and a boolean.
class StepTracker
{
private int steps;
private int goal;
public StepTracker(int startSteps, int dailyGoal)
{
steps = startSteps;
goal = dailyGoal;
}
public double milesWalked()
{
return steps / 2000.0;
}
public boolean reachedGoal()
{
return steps >= goal;
}
}
public class GoalChecks
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200, 10000);
double miles = tracker.milesWalked();
System.out.println(miles);
if (tracker.reachedGoal())
{
System.out.println("Goal met!");
}
else
{
System.out.println("Keep walking.");
}
}
}2.1
Keep walking.
milesWalked(lines 12–15) returnssteps / 2000.0. Dividing by2000.0makes it floating-point division (Topic 1.3), so 4200 / 2000.0 is 2.1, adouble, which matches the return typedouble.reachedGoal(lines 17–20) returnssteps >= goal. A relational expression produces aboolean(Topic 2.2), so the method returnsfalsehere, since 4200 is not at least 10000. Noifis needed inside the method: the comparison’s result is the answer.- Line 30 uses the returned
booleandirectly as anifcondition. Since it isfalse, theelsebranch printsKeep walking.
Finally, the widening rule in action. Here length * width is an int, but the method returns double:
class Garden
{
private int length;
private int width;
public Garden(int gardenLength, int gardenWidth)
{
length = gardenLength;
width = gardenWidth;
}
public double area()
{
return length * width;
}
}
public class GardenArea
{
public static void main(String[] args)
{
Garden plot = new Garden(3, 4);
System.out.println(plot.area());
}
}It prints 12.0. Java computes 3 × 4 = 12 as an int, then widens it to 12.0 to match the return type, just as double area = 12; would.
Watch out
Returning a value that does not fit the return type. Here the header promises an int, but the expression is a double:
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public int milesWalked()
{
return steps / 2000.0;
}
}
public class LossyReturn
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
System.out.println(tracker.milesWalked());
}
}Java reports:
LossyReturn.java:12: error: incompatible types: possible lossy conversion from double to intsteps / 2000.0 is 2.1, and an int cannot hold the .1. Java will not silently throw away part of a value. Students make this mistake by picking the return type before thinking about what the expression produces. Fix it by making the return type match the value: public double milesWalked(). (A cast, (int) (steps / 2000.0), would also compile, but only if dropping the decimal part is truly what you want.)
Pause and try
Question 5 · Multiple choice
A StepTracker has int instance variables steps and goal. Which return statement compiles inside public boolean overHalfway()?
return goal - steps;return steps > goal / 2;return steps / 2.0;return "yes";
Check your reasoning
B. A boolean method must return a boolean value. steps > goal / 2 is a relational expression, so it evaluates to true or false.
- A is wrong:
goal - stepsis anint. Java reportsincompatible types: int cannot be converted to boolean; a number is not true or false. - C is wrong:
steps / 2.0is adouble, which is not compatible withboolean. - D is wrong:
"yes"is aString, not aboolean. The words “yes” andtruemean the same to a person but not to Java.
Reread 3.5.3 Non-Void Methods Return a Value if you chose differently.
Question 6 · Tracing
What does this program print?
class PizzaOrder
{
private int pizzas;
public PizzaOrder(int pizzaCount)
{
pizzas = pizzaCount;
}
public int slices()
{
return pizzas * 8;
}
public double totalCost()
{
return pizzas * 12.5;
}
}
public class PizzaTrace
{
public static void main(String[] args)
{
PizzaOrder order = new PizzaOrder(3);
int perPerson = order.slices() / 5;
System.out.println(perPerson);
System.out.println(order.totalCost() + 4);
}
}Check your reasoning
4
41.5- Line 26:
order.slices()returns 3 × 8 = 24, so the statement becomesint perPerson = 24 / 5;. Both areints, so integer division gives 4 (not 4.8). - Line 27 prints
4. - Line 28:
order.totalCost()returns 3 × 12.5 = 37.5, adouble. The call is replaced by 37.5, and 37.5 + 4 = 41.5 prints.
If you wrote 4.8, check the types: the returned value 24 is an int, and int / int drops the remainder. If you wrote 37.5, remember the returned value is used in the expression + 4 before printing.
Reread 3.5.3 Non-Void Methods Return a Value if your output differed.
3.5.4 Accessor Methods Share a Copy
Private instance variables protect an object’s data from careless changes, but sometimes other code legitimately needs to know a value. A leaderboard needs each tracker’s step count; a report needs the goal. main cannot read tracker.steps directly (that is the same private access error from 3.5.2). How can other classes look at a value without being able to change it?
In plain words: the class answers questions about itself. You ask, “What are your steps?” and it tells you the number. Telling you the number does not give you the power to change it, the same way a friend telling you their locker combination does not move the dial.
An accessor method is a method that lets code in other classes obtain a copy of an instance variable’s value. An accessor is always a non-void method, because its whole job is to return something. By convention its name is get followed by the variable’s name, so accessors are often called “getters”:
public int getSteps()
{
return steps;
}
Because of return by value (3.5.3), return steps; sends back the number stored in steps, not the variable itself. The caller gets its own copy of that number. (Accessors can also report class variables, which belong to a whole class instead of one object; those arrive in Topic 3.7.)
Accessors and mutators are easy to mix up, so compare them side by side:
| Question | Accessor | Mutator |
|---|---|---|
| Job | Report an instance variable’s value | Change instance variables |
| Return type | Always non-void (int getSteps()) |
Often void (void addSteps(int newSteps)) |
| Changes the object? | No | Yes |
| Typical call | int s = tracker.getSteps(); |
tracker.addSteps(500); |
Key idea
An accessor method is a non-void method that returns a copy of an instance variable’s value, so other classes can read it without being able to change it.
Worked example: Reading private data through accessors
This StepTracker has two accessors and one mutator. Watch how the accessor’s answer changes after the mutator runs.
class StepTracker
{
private int steps;
private int goal;
public StepTracker(int startSteps, int dailyGoal)
{
steps = startSteps;
goal = dailyGoal;
}
public int getSteps()
{
return steps;
}
public int getGoal()
{
return goal;
}
public void addSteps(int newSteps)
{
steps = steps + newSteps;
}
}
public class Accessors
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200, 10000);
System.out.println(tracker.getSteps());
tracker.addSteps(3000);
System.out.println(tracker.getSteps());
System.out.println(tracker.getGoal() - tracker.getSteps());
}
}4200
7200
2800| Line | What happens | Printed |
|---|---|---|
| 33 | getSteps returns a copy of steps, which is 4200 |
4200 |
| 34 | The mutator adds 3000; steps is now 7200 |
|
| 35 | getSteps returns a copy of the current value, 7200 |
7200 |
| 36 | Both accessors are called: getGoal() returns 10000, getSteps() returns 7200, then 10000 − 7200 |
2800 |
An accessor always reports the value at the moment it is called. It does not remember an old answer. Line 36 also shows that main can do its own math with the returned copies.
Now vary it: what happens if the caller changes the copy it received?
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public int getSteps()
{
return steps;
}
}
public class AccessorCopy
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
int mySteps = tracker.getSteps();
mySteps = 0;
System.out.println(mySteps);
System.out.println(tracker.getSteps());
}
}0
4200
Line 21 stores the returned copy, 4200, in a new variable mySteps that belongs to main. Line 22 sets mySteps to 0. That changes only main’s variable. The object’s steps is a different variable in a different place, and it still holds 4200, which line 24 confirms. This is exactly why accessors are safe: they share a value, not access.
| Variable | After line 21 | After line 22 |
|---|---|---|
mySteps (in main) |
4200 | 0 |
tracker’s steps |
4200 | 4200 |
Watch out
Trying to change a value through an accessor call. A student wants to add 500 steps and writes:
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public int getSteps()
{
return steps;
}
}
public class AccessorAssign
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
tracker.getSteps() += 500;
}
}Java reports:
AccessorAssign.java:21: error: unexpected type
required: variable
found: valueThe message says it exactly: += needs a variable on its left, but tracker.getSteps() is replaced by a value, the number 4200. Writing 4200 += 500; makes no sense, and neither does this. An accessor only reads. To change the object, call a mutator: tracker.addSteps(500);.
Pause and try
Question 7 · Multiple choice
A PiggyBank class has the instance variable private double balance;. Which method is a correct accessor for balance?
public double getBalance() { return balance; }public void getBalance() { System.out.println(balance); }public double getBalance(double amount) { balance = amount; return balance; }public int getBalance() { return balance; }
Check your reasoning
A. An accessor is a non-void method that returns a copy of the instance variable’s value, with a return type that matches it. balance is a double, so the method returns double.
- B is wrong: it is a void method. It prints the balance on the screen, but the caller receives nothing it can store or use. An accessor must return the value.
- C is wrong: it assigns to
balance, so it changes the object. That makes it a mutator, whatever its name says. - D is wrong: it does not compile. Returning a
doublefrom anintmethod givespossible lossy conversion from double to int.
Reread 3.5.4 Accessor Methods Share a Copy if you chose differently.
Question 8 · Short answer
Predict what this program prints, and explain why the second line is not 7.0.
class PiggyBank
{
private double balance;
public PiggyBank(double startBalance)
{
balance = startBalance;
}
public double getBalance()
{
return balance;
}
}
public class PiggyCopy
{
public static void main(String[] args)
{
PiggyBank bank = new PiggyBank(12.0);
double money = bank.getBalance();
money = money - 5.0;
System.out.println(money);
System.out.println(bank.getBalance());
}
}Check your reasoning
It prints:
7.0
12.0Model answer: getBalance() returns a copy of the value in balance, 12.0, and line 21 stores that copy in money, a variable that belongs to main. Line 22 subtracts 5.0 from money, so money becomes 7.0. The object’s balance is a separate variable that nothing changed, so the second call to getBalance() still returns 12.0.
A complete answer must:
- say that the accessor returns a copy of the value (return by value), not the instance variable itself, and
- say that changing
moneyaffects onlymain’s variable, sobalancestays 12.0.
Reread 3.5.4 Accessor Methods Share a Copy if your answer differed.
3.5.5 A Return Statement Ends the Method
A tracker awards a badge: level 3 for at least 10,000 steps, level 2 for at least 5,000, and level 1 otherwise. As soon as the method knows the answer, it should stop and report it. Checking the other cases would be wasted work, and might even pick the wrong level.
In Topic 1.9 you saw that when a method finishes, control returns to the point of the call. The return keyword is how a method finishes on purpose, at any point you choose.
The return keyword sends the flow of control back to the place where the method was called, immediately. Two consequences follow:
- Nothing after it runs. Any statement that comes after a
returnstatement in the same run of the method is never executed. - It exits an
ifor a loop too. If areturnstatement runs inside anifstatement or a loop, it does not just leave that statement; it ends the whole method on the spot. A loop does not finish its remaining iterations.
A void method can use return as well, written with no value: return;. It simply means “stop here.” (The same is true inside a constructor, though it is rarely needed there.)
Key idea
When a return statement runs, the method ends immediately and control goes back to the caller. This holds even inside an if statement or a loop: the rest of the method, including any remaining loop iterations, is skipped.
Worked example: Returning from inside if statements
badgeLevel has three return statements. For each call, find the first one that runs.
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public int badgeLevel()
{
if (steps >= 10000)
{
return 3;
}
if (steps >= 5000)
{
return 2;
}
return 1;
}
}
public class BadgeLevel
{
public static void main(String[] args)
{
StepTracker sam = new StepTracker(7000);
StepTracker kim = new StepTracker(12000);
StepTracker raj = new StepTracker(2500);
System.out.println(sam.badgeLevel());
System.out.println(kim.badgeLevel());
System.out.println(raj.badgeLevel());
}
}2
3
1| Call (line) | steps |
steps >= 10000? |
steps >= 5000? |
Return that runs |
|---|---|---|---|---|
sam (31) |
7000 | false: skip line 14 | true | line 18: returns 2 |
kim (32) |
12000 | true | never checked | line 14: returns 3 |
raj (33) |
2500 | false | false | line 20: returns 1 |
Look at kim. 12000 is also at least 5000, so if the method kept going it would reach return 2;. It does not: return 3; ends the method at line 14, so line 16 is never even checked. Look at sam: line 20, return 1;, sits after the if statements with no condition, yet it does not run, because return 2; already ended the method. Line 20 runs only when neither if returned first. That is why this method needs no else.
Now vary it: put the return inside a loop. extraDaysNeeded checks day by day whether walking stepsPerDay more each day for that many days reaches the goal, for up to a week.
class StepTracker
{
private int steps;
private int goal;
public StepTracker(int startSteps, int dailyGoal)
{
steps = startSteps;
goal = dailyGoal;
}
public int extraDaysNeeded(int stepsPerDay)
{
for (int day = 1; day <= 7; day++)
{
if (steps + day * stepsPerDay >= goal)
{
return day;
}
}
return -1;
}
}
public class DaysToGoal
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200, 10000);
System.out.println(tracker.extraDaysNeeded(2000));
System.out.println(tracker.extraDaysNeeded(500));
}
}3
-1
Call on line 30, stepsPerDay is 2000, steps is 4200, goal is 10000:
day |
4200 + day * 2000 |
At least 10000? | What happens |
|---|---|---|---|
| 1 | 6200 | false | loop continues |
| 2 | 8200 | false | loop continues |
| 3 | 10200 | true | line 18 returns 3 |
The loop was set up to run up to 7 times, but the return on line 18 stops it after 3 and exits the method. Days 4 to 7 are never checked, and return -1; on line 21 never runs.
Call on line 31, stepsPerDay is 500: even on day 7, 4200 + 3500 = 7700 is short of the goal, so the if is false every time. The loop ends normally after day reaches 8, and control falls through to line 21, which returns −1. Using a value that could never be a real answer (no one needs −1 days) is a common way to report “not found”.
A void method can stop early with return;. Here addSteps refuses a negative number of steps:
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public void addSteps(int newSteps)
{
if (newSteps < 0)
{
System.out.println("Ignored: " + newSteps);
return;
}
steps = steps + newSteps;
System.out.println("Added: " + newSteps);
}
public int getSteps()
{
return steps;
}
}
public class EarlyVoidReturn
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200);
tracker.addSteps(-300);
tracker.addSteps(600);
System.out.println(tracker.getSteps());
}
}Ignored: -300
Added: 600
4800
On line 32, newSteps is −300, so the if is true: line 14 prints, and return; on line 15 ends the method. Lines 17 and 18 are skipped, so steps stays 4200. On line 33, newSteps is 600, the if is false, and lines 17 and 18 run, making steps 4800.
Watch out
Writing code after a return statement. Code placed directly after a return in the same block can never run, and Java treats that as an error:
class StepTracker
{
private int steps;
private int goal;
public StepTracker(int startSteps, int dailyGoal)
{
steps = startSteps;
goal = dailyGoal;
}
public int stepsToGoal()
{
return goal - steps;
System.out.println("Calculated steps left");
}
}
public class Unreachable
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200, 10000);
System.out.println(tracker.stepsToGoal());
}
}Unreachable.java:15: error: unreachable statementThe message would never print, so Java stops you. Students write this when they think of return as “remember the answer” instead of “leave now.” Fix it by moving the print above the return.
Leaving a path with no return. A non-void method must return a value on every path through it. Take away the last line of badgeLevel:
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public int badgeLevel()
{
if (steps >= 10000)
{
return 3;
}
if (steps >= 5000)
{
return 2;
}
}
}
public class MissingReturn
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(7000);
System.out.println(tracker.badgeLevel());
}
}MissingReturn.java:20: error: missing return statementJava points at the closing brace on line 20. If steps were 2500, both conditions would be false and control would reach the end of the method with no value to send back. Java checks this before the program runs, even though this particular object has 7000 steps. Fix it by adding return 1; after the second if, so every path returns.
Pause and try
Question 9 · Tracing
What does this program print? Watch where each call’s loop stops.
class SavingsJar
{
private int dollars;
public SavingsJar(int startDollars)
{
dollars = startDollars;
}
public int weeksUntil(int target, int weekly)
{
int total = dollars;
int week = 0;
while (week < 10)
{
if (total >= target)
{
return week;
}
week++;
total = total + weekly;
}
return 10;
}
}
public class SavingsTrace
{
public static void main(String[] args)
{
SavingsJar jar = new SavingsJar(15);
System.out.println(jar.weeksUntil(40, 10));
System.out.println(jar.weeksUntil(10, 5));
}
}Check your reasoning
3
0First call, weeksUntil(40, 10), with dollars 15:
week at check |
total |
total >= 40? |
Action |
|---|---|---|---|
| 0 | 15 | false | week 1, total 25 |
| 1 | 25 | false | week 2, total 35 |
| 2 | 35 | false | week 3, total 45 |
| 3 | 45 | true | line 18 returns 3 |
Second call, weeksUntil(10, 5): total starts at 15, and on the very first iteration 15 >= 10 is true, so line 18 returns 0 before week is ever increased.
In both calls the return inside the if ends the loop and the method together, so return 10; on line 23 never runs. Also note that line 12 creates total fresh on each call, starting from a copy of dollars; the method never changes dollars.
Reread 3.5.5 A Return Statement Ends the Method if your output differed.
Question 10 · Multiple choice
What does this program print?
class Quiz
{
private int score;
public Quiz(int startScore)
{
score = startScore;
}
public void addBonus(int bonus)
{
if (score + bonus > 100)
{
score = 100;
return;
}
score = score + bonus;
System.out.println("Bonus added");
}
public int getScore()
{
return score;
}
}
public class ScoreCheck
{
public static void main(String[] args)
{
Quiz quiz = new Quiz(95);
quiz.addBonus(10);
System.out.println(quiz.getScore());
}
}Bonus addedthen100100Bonus addedthen105105
Check your reasoning
B. 95 + 10 is 105, which is greater than 100, so the if is true. Line 14 sets score to 100, and return; on line 15 ends the method right away. Lines 17 and 18 never run, so nothing else prints, and getScore() returns 100.
- A is wrong: it treats
return;as if it only left theif. A return statement leaves the whole method, so line 18 is skipped. - C is wrong: it skips the
ifentirely and runs lines 17 and 18 instead. The condition is true, so theifruns. - D is wrong:
scoreis capped at 100 by line 14, and line 17 never runs to make it 105.
Reread 3.5.5 A Return Statement Ends the Method if you chose differently.
3.5.6 Parameters Receive Copies
When you hand a method a variable, can the method change that variable? In 3.5.2 a mutator changed the object’s instance variable, and the change lasted. A parameter is also a variable the method can assign to. So if a method changes its parameter, does the caller’s variable change too?
Topic 1.9 answered this for the methods you read there: Java uses call by value. The same rule holds for every method you write in a class.
In plain words: when you call a method, Java photocopies the argument’s value and gives the copy to the parameter. The method can scribble on its copy all it likes; your original is untouched.
When an argument is a primitive value, such as an int, double, or boolean, the parameter is initialized with a copy of that value. The parameter and the caller’s variable are two separate variables that happen to start with the same value. Changing the parameter has no effect on the argument.
This is different from changing an instance variable. An instance variable belongs to the object and outlives the call. A parameter belongs to one run of the method and disappears when the method ends.
Key idea
A primitive argument’s value is copied into the parameter. The method may change its parameter, but the caller’s variable keeps its value; only changes to instance variables last after the method ends.
Worked example: A deposit that tries to empty the caller's wallet
deposit adds amount to the balance and then sets amount to 0. Does main’s cash become 0?
class PiggyBank
{
private double balance;
public PiggyBank(double startBalance)
{
balance = startBalance;
}
public void deposit(double amount)
{
balance = balance + amount;
amount = 0;
}
public double getBalance()
{
return balance;
}
}
public class DepositCopy
{
public static void main(String[] args)
{
PiggyBank bank = new PiggyBank(5.0);
double cash = 20.0;
bank.deposit(cash);
System.out.println(cash);
System.out.println(bank.getBalance());
}
}20.0
25.0Picture the variables at each moment:
| Moment | cash (main) |
amount (parameter) |
bank’s balance |
|---|---|---|---|
| After line 27 | 20.0 | (does not exist) | 5.0 |
| Call on line 28 begins | 20.0 | 20.0 (a copy) | 5.0 |
| After line 12 | 20.0 | 20.0 | 25.0 |
| After line 13 | 20.0 | 0.0 | 25.0 |
| Back on line 29 | 20.0 | (gone) | 25.0 |
Line 12 changes an instance variable, so that change lasts. Line 13 changes only the parameter, the copy, and the copy disappears when the method ends. cash was never touched, so line 29 prints 20.0.
Now vary it: let a loop count the parameter down to 0. walkLaps adds 400 steps per lap.
class StepTracker
{
private int steps;
public StepTracker(int startSteps)
{
steps = startSteps;
}
public void walkLaps(int laps)
{
while (laps > 0)
{
steps = steps + 400;
laps--;
}
}
public int getSteps()
{
return steps;
}
}
public class WalkLaps
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(0);
int plannedLaps = 3;
tracker.walkLaps(plannedLaps);
System.out.println(plannedLaps);
System.out.println(tracker.getSteps());
}
}3
1200
Inside the method, laps starts as a copy of 3. The loop runs while laps > 0, adding 400 to steps and decreasing laps each time: 3 → 2 → 1 → 0. After three iterations laps is 0, the loop ends, and steps is 1200. Back in main, plannedLaps still holds 3, because the loop counted down the copy, not the original. Using a parameter as a counter is perfectly safe for exactly this reason.
Watch out
Trying to send an answer back through a parameter. A student wants the steps left and writes a void method that stores the answer in its parameter:
class StepTracker
{
private int steps;
private int goal;
public StepTracker(int startSteps, int dailyGoal)
{
steps = startSteps;
goal = dailyGoal;
}
public void findStepsLeft(int result)
{
result = goal - steps;
}
}
public class ParameterAsResult
{
public static void main(String[] args)
{
StepTracker tracker = new StepTracker(4200, 10000);
int left = 0;
tracker.findStepsLeft(left);
System.out.println(left);
}
}It compiles and prints 0. Line 14 stores 5800 in result, but result is only a copy of left’s value. When the method ends, the copy and its 5800 vanish, and left still holds 0. Students expect this to work because the method “fills in” the variable it was given. The fix is to return the answer: make the method non-void, public int stepsToGoal() with return goal - steps;, and have the caller store it: int left = tracker.stepsToGoal(); (as in StepsToGoal.java from 3.5.3). A return value is the way a method sends a primitive answer back.
Object references passed as arguments follow the same copying rule, but with an extra twist that Topic 3.6 covers.
Pause and try
Question 11 · Tracing
What does this program print?
class Fundraiser
{
private int raised;
public Fundraiser(int startRaised)
{
raised = startRaised;
}
public void collect(int donation)
{
donation = donation * 2;
raised = raised + donation;
}
public int getRaised()
{
return raised;
}
}
public class CopyTrace
{
public static void main(String[] args)
{
Fundraiser drive = new Fundraiser(100);
int gift = 25;
drive.collect(gift);
drive.collect(gift);
System.out.println(gift);
System.out.println(drive.getRaised());
}
}Check your reasoning
25
200Each call to collect gets a fresh copy of gift, which is 25.
| Call | donation starts |
After line 12 | raised after line 13 |
gift in main |
|---|---|---|---|---|
| 1 | 25 | 50 | 150 | 25 |
| 2 | 25 | 50 | 200 | 25 |
Doubling donation on line 12 doubles only the copy, so gift stays 25 and the second call starts from 25 again, not 50. The changes to raised, an instance variable, last between calls.
If you answered 50 for the first line, you assumed doubling donation changed gift. If you answered 250 for the second line, you assumed the second call received 50.
Reread 3.5.6 Parameters Receive Copies if your output differed.
Question 12 · Multiple choice
In DepositCopy.java, a student wants cash in main to be 0.0 after the deposit. Which statement is true?
- Line 13 already does this;
cashis 0.0 after the call. - Renaming the parameter from
amounttocashwould make the method changemain’scash. - No statement inside
depositcan changemain’scash;mainitself must assign it, for example withcash = 0.0;after the call. - Calling
bank.deposit(0)instead would setcashto 0.0.
Check your reasoning
C. A primitive argument is copied into the parameter, so the method only ever has a copy. The caller’s variable can change only through an assignment in the caller.
- A is wrong: line 13 sets the parameter
amount, which is a copy. The program prints20.0forcash. - B is wrong: a parameter is always a new variable that belongs to the method, whatever its name. A parameter named
cashwould still be a separate copy, andmainwould still print20.0. - D is wrong: that passes the value 0 to the method, which deposits nothing. It does not touch
cash, which stays 20.0.
Reread 3.5.6 Parameters Receive Copies if you chose differently.
Putting it together
A phone battery class uses every kind of method from this chapter: an accessor, a void mutator, and a mutator that also returns an answer. The use method spends charge only if there is enough, and it returns true or false to say whether it worked. A mutator is often void, but not always; use changes the object and reports back.
Worked example: A battery with three kinds of methods
class Battery
{
private int percent;
public Battery(int startPercent)
{
percent = startPercent;
}
public int getPercent()
{
return percent;
}
public void charge(int amount)
{
percent = percent + amount;
if (percent > 100)
{
percent = 100;
}
}
public boolean use(int amount)
{
if (amount > percent)
{
return false;
}
percent = percent - amount;
return true;
}
}
public class BatteryDemo
{
public static void main(String[] args)
{
Battery phone = new Battery(30);
int videoCost = 45;
System.out.println(phone.use(videoCost));
phone.charge(80);
System.out.println(phone.getPercent());
System.out.println(phone.use(videoCost));
System.out.println(phone.getPercent() + " " + videoCost);
}
}First, classify the methods:
| Method | Return type | Changes percent? |
Kind |
|---|---|---|---|
getPercent |
int |
No | Accessor |
charge |
void |
Yes | Mutator (void) |
use |
boolean |
Sometimes | Mutator that also returns a value |
Now trace main:
| Line | Call | Inside the method | percent |
Printed |
|---|---|---|---|---|
| 39 | constructor | percent set to 30 |
30 | |
| 41 | use(45) |
45 > 30 is true, so line 28 returns false. Line 30 never runs. |
30 | false |
| 42 | charge(80) |
30 + 80 = 110; 110 > 100, so it is capped at 100 |
100 | |
| 43 | getPercent() |
returns a copy, 100 | 100 | 100 |
| 44 | use(45) |
45 > 100 is false; line 30 makes it 55; line 31 returns true |
55 | true |
| 45 | getPercent() |
returns 55; joined with " " and videoCost |
55 | 55 45 |
The output is:
false
100
true
55 45Every idea from the chapter is visible here. charge is a void mutator whose call stands alone on line 42 (3.5.1, 3.5.2). use returns a boolean that main prints directly (3.5.3). getPercent reports the current value without changing it (3.5.4). On line 41, return false; ends use before the subtraction, so a failed attempt costs nothing (3.5.5). And videoCost is still 45 at the end, because use received only a copy of it (3.5.6).
Common mistakes
The Watch Out boxes explained each mistake. This table is a lookup for the compiler messages you met, so you can match an error to its cause quickly. Exact wording can vary slightly between Java versions.
| Compiler message | Usual cause | Fix |
|---|---|---|
invalid method declaration; return type required |
A method header with no return type | Add void or the type the method returns |
has private access in |
Code in another class uses a private instance variable | Call an accessor or mutator instead |
possible lossy conversion from double to int |
A double returned from an int method (or passed to an int parameter) |
Match the return type to the value |
void cannot be converted to or 'void' type not allowed here |
A void method’s call used as a value | Call it as a statement, or write a non-void method |
unexpected type (required: variable, found: value) |
Assigning to a method call, such as getSteps() += 500 |
Call a mutator |
unreachable statement |
A statement directly after return in the same block |
Move it above the return |
missing return statement |
Some path through a non-void method ends without a return |
Add a return that covers the remaining cases |
Vocabulary
| Term | Definition | Example |
|---|---|---|
| behavior | Something an object of a class can do, written as a method in the class | Recording steps |
| void method | A method that does not return a value; its header has void before the name |
public void printSummary() |
| mutator method | A method that changes an object’s instance variables; often void. Also called a modifier method | public void addSteps(int newSteps) |
| parameter | A variable in a method header that receives a copy of the argument’s value when the method is called | newSteps |
| non-void method | A method that returns exactly one value; its header has a return type in place of void |
public int stepsToGoal() |
| return type | The type of value a method returns, written before its name | int, double, boolean |
| return statement | The keyword return, usually followed by an expression; it sends a value back and ends the method |
return goal - steps; |
| return by value | Returning the value an expression evaluates to, not the variables in it | return steps; sends 4200 |
| accessor method | A non-void method that returns a copy of an instance variable’s value | public int getSteps() |
Chapter summary
- Methods written inside a class give its objects behaviors. They can use the class’s private instance variables by name, and a call runs the method with the instance variables of the object before the dot.
- A void method has
voidin its header and returns nothing, so its call is a statement on its own. A non-void method has a return type instead, and its return statement evaluates an expression and sends back one value, which must be compatible with the return type (return by value). - A mutator method changes an object’s instance variables, using values received through its parameters; mutators are often void. An accessor method is non-void and returns a copy of an instance variable’s value, so callers can read it but not change it.
- A return statement ends the method immediately and sends control back to the caller, even from inside an
ifor a loop. Code after it does not run, and a non-void method must return a value on every path. - A primitive argument’s value is copied into its parameter. Changing the parameter never changes the caller’s variable; only changes to instance variables last after the method ends.
Review questions
Question 13 · Multiple choice
A GymMembership class has an int instance variable visits and an int instance variable monthsLeft. Its methods have these headers and behaviors:
public int getVisits() // returns visits
public void checkIn() // adds 1 to visits
public boolean renew(int months) // adds months to monthsLeft and returns true
public double costPerVisit() // returns 40.0 / visits; changes nothingWhich statement is true?
renewcannot be a mutator, because it is not a void method.costPerVisitis a mutator, because it uses an instance variable.checkInis a mutator andgetVisitsis an accessor.- A caller can change
visitsby changing the value thatgetVisits()returns.
Check your reasoning
C. checkIn changes the instance variable visits, which makes it a mutator. getVisits is non-void and returns a copy of visits, which makes it an accessor.
- A is wrong: a mutator is often void, not always.
renewchangesmonthsLeft, so it is a mutator, and it also returns aboolean. (The battery’susemethod in Putting it together was the same kind.) - B is wrong: using an instance variable is not the same as changing it.
costPerVisitonly readsvisitsto compute an answer. - D is wrong:
getVisits()returns a copy of the value. Changing the copy changes only the caller’s variable.
Reread 3.5.4 Accessor Methods Share a Copy if you chose differently.
Question 14 · Tracing
A bakery sells muffins but cannot sell more than it has. What does this program print?
class Bakery
{
private int muffins;
public Bakery(int startMuffins)
{
muffins = startMuffins;
}
public int sell(int wanted)
{
if (wanted > muffins)
{
wanted = muffins;
}
muffins = muffins - wanted;
return wanted;
}
public int getMuffins()
{
return muffins;
}
}
public class BakeryReview
{
public static void main(String[] args)
{
Bakery shop = new Bakery(10);
int order = 4;
int sold = shop.sell(order);
System.out.println(sold + " " + order);
order = 9;
sold = shop.sell(order);
System.out.println(sold + " " + order);
System.out.println(shop.getMuffins());
}
}Check your reasoning
4 4
6 9
0| Line | Call | wanted (copy) |
Inside sell |
muffins |
Returned | order in main |
|---|---|---|---|---|---|---|
| 32 | sell(4) |
4 | 4 > 10 false; 10 − 4 |
6 | 4 | 4 |
| 35 | sell(9) |
9 → 6 | 9 > 6 true, so wanted becomes 6; 6 − 6 |
0 | 6 | 9 |
On the second call, line 14 changes wanted from 9 to 6, but wanted is a copy, so order in main is still 9. The method returns the value of wanted, 6, which is how many muffins were actually sold. Line 37 then prints the remaining muffins, 0.
If you printed 6 6, you let the change to the parameter flow back into order. If you printed 9 9, you missed that the if lowered wanted before it was subtracted and returned.
Reread 3.5.6 Parameters Receive Copies if your output differed.
Question 15 · Debugging
A movie review should earn 5 stars for a score of 90 or more, 4 stars for 70 to 89, 3 stars for 50 to 69, and 1 star for anything lower. This class does not compile. Explain the error and correct it.
class MovieReview
{
private int score;
public MovieReview(int reviewScore)
{
score = reviewScore;
}
public int stars()
{
if (score >= 90)
{
return 5;
}
else if (score >= 70)
{
return 4;
}
else if (score >= 50)
{
return 3;
}
}
}
public class StarsMissingReturn
{
public static void main(String[] args)
{
MovieReview review = new MovieReview(75);
System.out.println(review.stars());
}
}Check your reasoning
Java reports StarsMissingReturn.java:24: error: missing return statement, pointing at the closing brace of stars. If the score is below 50, all three conditions are false and control reaches the end of the method without returning anything, but the header promises an int on every call. It does not matter that this program only tests 75; Java checks every possible path before the program runs.
The correction is to add a return for the remaining case after the last else if:
else if (score >= 50)
{
return 3;
}
return 1;
}With this fix, a review with score 75 returns 4 and one with score 32 returns 1. (Changing the last else if (score >= 50) into a plain else would compile too, but then every score below 70 would earn 3 stars, which breaks the rules above.)
Reread 3.5.5 A Return Statement Ends the Method if you found a different error.
Question 16 · Tracing
What does this program print? The method changes the object’s seconds, and it is called twice.
class CountdownTimer
{
private int seconds;
public CountdownTimer(int startSeconds)
{
seconds = startSeconds;
}
public int tickUntil(int alarmAt)
{
int ticks = 0;
while (seconds > 0)
{
seconds = seconds - 5;
ticks++;
if (seconds <= alarmAt)
{
return ticks;
}
System.out.println("tick " + seconds);
}
System.out.println("timer ended");
return ticks;
}
public int getSeconds()
{
return seconds;
}
}
public class TimerReview
{
public static void main(String[] args)
{
CountdownTimer timer = new CountdownTimer(30);
System.out.println(timer.tickUntil(18));
System.out.println(timer.getSeconds());
System.out.println(timer.tickUntil(-1));
}
}Check your reasoning
tick 25
tick 20
3
15
tick 10
tick 5
tick 0
timer ended
3First call, tickUntil(18), with seconds 30:
seconds after line 15 |
ticks |
seconds <= 18? |
What happens |
|---|---|---|---|
| 25 | 1 | false | prints tick 25 |
| 20 | 2 | false | prints tick 20 |
| 15 | 3 | true | line 19 returns 3 |
The return ends the loop and the method, so timer ended does not print. main prints 3. seconds is an instance variable, so it keeps the value 15, and getSeconds() prints 15.
Second call, tickUntil(-1): ticks starts at 0 again (line 12 runs on every call), but seconds starts at 15, where the first call left it.
seconds after line 15 |
ticks |
seconds <= -1? |
What happens |
|---|---|---|---|
| 10 | 1 | false | prints tick 10 |
| 5 | 2 | false | prints tick 5 |
| 0 | 3 | false | prints tick 0 |
Now seconds > 0 is false, so the loop ends normally. Lines 23 and 24 run: timer ended prints and the method returns 3, which main prints.
Reread 3.5.5 A Return Statement Ends the Method if your output differed.
Question 17 · Multiple choice
Use the Battery class from Putting it together, with Battery phone = new Battery(50);. Which statement compiles?
int level = phone.charge(10);phone.getPercent() = 50;if (phone.use(20)) { phone.charge(5); }boolean ok = phone.use(12.5);
Check your reasoning
C. use returns a boolean, so its call can serve as an if condition, and charge is void, so it stands alone as a statement. (With 50 percent, use(20) succeeds and leaves 30, and charge(5) makes it 35.)
- A is wrong:
chargeis a void method, so there is no value to store. Java reportsvoid cannot be converted to int. - B is wrong: a method call is replaced by a value, and you cannot assign to a value. Java reports
unexpected type. Use a mutator to change the battery. - D is wrong:
usehas anintparameter, and 12.5 is adouble. Passing it would lose the .5, so Java reportspossible lossy conversion from double to int.
Reread 3.5.3 Non-Void Methods Return a Value if you chose differently.
Question 18 · Short answer
An Order class could provide either of these methods. Both compute the same total from its price and quantity instance variables.
public void printTotal()
{
System.out.println(price * quantity);
}
public double getTotal()
{
return price * quantity;
}A program needs to add a 3.00 delivery fee to the total and store the result in a variable. Which method can it use, and why can it not use the other one?
Check your reasoning
Model answer: It must use getTotal, for example double withFee = order.getTotal() + 3.00;. getTotal is a non-void method, so its return statement sends the total back, and Java uses that value in place of the call, where it can be added to and stored. printTotal is a void method: it shows the total on the screen, but it sends nothing back to the program, so there is no value to add 3.00 to. Writing order.printTotal() + 3.00 would not compile; Java reports 'void' type not allowed here.
A complete answer must:
- choose
getTotaland say that it returns a value the caller can use in an expression or store, and - say that
printTotalis void, so printing the total does not give the program a value to work with.
Reread 3.5.3 Non-Void Methods Return a Value if your answer differed.
Question 19 · Debugging
A gradebook should report the average of its quiz scores. After quizzes of 9 and 10 it prints 9.0 instead of 9.5. The method is only called after at least one quiz has been added. Find the error and correct it.
class Gradebook
{
private int totalPoints;
private int quizCount;
public Gradebook()
{
totalPoints = 0;
quizCount = 0;
}
public void addQuiz(int points)
{
totalPoints = totalPoints + points;
quizCount++;
}
public double average()
{
return totalPoints / quizCount;
}
}
public class AverageBug
{
public static void main(String[] args)
{
Gradebook book = new Gradebook();
book.addQuiz(9);
book.addQuiz(10);
System.out.println(book.average());
}
}Check your reasoning
The error is on line 20. totalPoints and quizCount are both ints, so totalPoints / quizCount is integer division: 19 / 2 is 9, with the remainder thrown away. Only then does Java widen the returned int 9 to the double 9.0 to match the return type. The return type being double does not change how the division inside the expression is done. The corrected line is:
return (double) totalPoints / quizCount;The cast turns 19 into 19.0 before dividing, so the division is floating-point and the method returns 9.5. (Writing (double) (totalPoints / quizCount) would not help: the parentheses make the integer division happen first, giving 9.0 again.) The precondition that at least one quiz exists matters too: with quizCount 0, the division would be undefined.
Reread 3.5.3 Non-Void Methods Return a Value if you found a different error.
Reflection
Check each statement. If the answer is “not yet,” reread the linked subsection.
- I can read a void method inside a class and explain why its call is a statement on its own. Reread
- I can explain what a mutator method does and trace how it changes an object over several calls. Reread
- I can tell whether a return statement matches its method's return type and trace what the call is replaced by. Reread
- I can explain what an accessor method is and why changing its returned value does not change the object. Reread
- I can find exactly where a method stops when a return statement runs inside an if statement or a loop. Reread
- I can explain why changing a primitive parameter never changes the caller's variable. Reread