Return Values, Composition, and Incremental Development

The Math class and its constants, composing method calls the way you compose mathematical functions, writing methods that return a value, and the incremental method that keeps you from ever debugging more than a line or two at a time. Follows Think Java 2e, Chapter 4 (Methods and Testing), Sections 4.6-4.9, pp. 58-64, cross-referenced against The Java Tutorials — Returning a Value from a Method.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Return Values, Composition, and Incremental Development

Title

Think Java 2e · Chapter 4 · Methods and Testing

Sections 4.6-4.9 · pp. 58-64

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 4 (Methods and Testing), Sections 4.6-4.9, pp. 58-64. Everything on these slides can be checked against those pages.

1. Use Math methods and constants, and say why PI has no parentheses.

2. Compose method calls, passing one call's result as another's argument.

3. Recognise the 'cannot find symbol' error caused by omitting a class name.

4. Write a value-returning method: declare a return type and use a return statement.

5. Develop a method incrementally from a stub, checking intermediate values as you go.

6. Explain what scaffolding is and why you remove it.

3. Retrieve before you read

Warm-up

One idea from the previous lesson, because this one removes its main limitation.

Discussion prompt

Why can a method not change its caller's variables? And given that, how could a method ever be useful for computing something the caller needs?

Hint: Parameter passing is an assignment. What direction does information travel?

Answer:

Because parameter passing copies the value into a new variable in a new frame. Changing the copy leaves the original alone, and when the method returns, its frame and everything in it disappears.

So information travels in through arguments. To get information back out, the method has to return a value and the caller has to catch it — which is what this lesson is about.

4. Methods that hand something back

Concept

Every method you have written so far was void. This lesson replaces that word with a real type, so a method can compute a value and give it to whoever called it — which is what makes methods composable.

Figure (svg): A diagram showing a value passed into a method as a parameter and a result returned back to the caller

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 4 (Methods and Testing), Sections 4.6-4.9, pp. 58-64 — Sections 4.6-4.9, printed pages 58-64.

5. Math methods and constants

Section

Section 4.6

6. You do not always have to write the method

Concept

The Java library contains thousands of classes you can use. The Math class provides common mathematical operations — and because it is in java.lang, you do not have to import it.

double root = Math.sqrt(17.0);

double angle = 1.5;
double height = Math.sin(angle);

double degrees = 90;
double radians = degrees / 180.0 * Math.PI;
expressionwhat it givesnote
Math.sqrt(17.0)the square root of 17a method — it has parentheses
Math.sin(angle)the sine of 1.5the argument must be in RADIANS
Math.PIan approximation of pia constant — no parentheses
degrees / 180.0 * Math.PI90 degrees as radiansdivide by 180 and multiply by pi

Values for the trigonometric functions — sin, cos and tan — must be in radians. That is the single most common source of wrong answers when people first use the Math class.

7. A constant is not a method

Notation

Math.PI and Math.sqrt look similar and behave completely differently. The parentheses are the tell, as they were in Lesson 3a.

Annotate

  • PI is in capital letters, following the constant convention from Lesson 3a. Java does not recognise Pi, pi or pie — the capitalisation is part of the name.
  • PI is the name of a constant, not a method, so it does not have parentheses. Writing Math.PI() is an error. The same is true of Math.E, which approximates Euler's number.
  • sqrt and round are methods, so they take parentheses and arguments. Reading the name is not enough — the parentheses are what distinguish the two kinds of thing.
  • Converting to and from radians is common enough that Math provides toRadians and toDegrees so you do not have to write the division and multiplication by hand.
  • Math.round returns a long, not an int. A long is like an int but bigger: an int uses 32 bits and holds up to about 2 billion; a long uses 64 and holds about 9 quintillion.

The habit worth forming: take a minute to read the documentation for a class before using it. The easiest way to find it is a web search for Java and the class name.

8. Converting degrees to radians, two ways

Worked example

Do the conversion by hand first, then with the library method, and check they agree. Checking a library call against something you computed yourself is a habit worth keeping.

double degrees = 90;
double byHand = degrees / 180.0 * Math.PI;
double byLibrary = Math.toRadians(degrees);
System.out.println(byHand);
System.out.println(byLibrary);
stepexpressionvalue
divide by 18090 / 180.00.5
multiply by pi0.5 * 3.14159...1.5707963267948966
the library versionMath.toRadians(90)1.5707963267948966
compare—identical

Write 180.0 rather than 180.

Why: Lesson 2b's rule: with two ints, 90 / 180 would be 0, and the whole answer would be zero.

Multiply by Math.PI.

Why: No parentheses — it is a constant, not a method.

Compare against Math.toRadians.

Why: The library method does exactly this calculation, and agreeing with it confirms you did it right.

Prefer the library method from now on.

Why: It says what it means, and it cannot be mistyped as a wrong formula.

Verify: Both lines should print 1.5707963267948966 — half of pi, as expected for 90 degrees.

Why: If your hand version printed 0.0, you wrote 180 instead of 180.0 and integer division gave 0 before the multiplication ever happened.

9. Which of these will not compile?

Elimination

One of these treats a constant as a method.

Eliminate the wrong options

Rule out the three that are fine.

  • A. double x = Math.sqrt(16.0);
  • B. double y = Math.PI();
  • C. double z = Math.PI * 2;
  • D. long n = Math.round(2.7);

Survives elimination: B

Why: PI is the name of a constant rather than a method, so it does not take parentheses. Writing Math.PI() asks Java to call a method that does not exist. The parentheses are the difference between naming a stored value and invoking a piece of behaviour — the same distinction as System.out versus System.out.println().

10. int, long, and how big a number can get

Concept

Math.round returns a long, which is a type you have not met. It is worth knowing what it is and why it exists.

long x = Math.round(Math.PI * 20.0);   // 63, rounded up from 62.8319
stepvalue
Math.PI * 20.062.83185307179586
Math.round(...)63 — rounded to the NEAREST integer
the typelong
typebitslargest valueroughly
int322 to the 31st, minus 1about 2 billion
long642 to the 63rd, minus 1about 9 quintillion

Note the contrast with Lesson 3b: a (int) cast truncates toward zero, so (int) 62.83 would be 62. Math.round rounds to the nearest, giving 63. Two different operations, and choosing between them is a decision you should make deliberately.

11. Passing degrees to a trigonometric method

Trap

The trap

The assumption. Math.sin(90) should give 1, because the sine of 90 degrees is 1.

System.out.println(Math.sin(90));
what you meantwhat Java computedresult
sin of 90 degreessin of 90 RADIANS0.8939966636005579
expected 1.0—a plausible-looking wrong answer

The result is a number between -1 and 1, so nothing looks obviously broken. This is a logic error of the worst kind: no message, and output that passes a glance.

The fix

Convert to radians first.

System.out.println(Math.sin(Math.toRadians(90)));   // 1.0

// or by hand:
System.out.println(Math.sin(90 / 180.0 * Math.PI));
expressionvalue
Math.toRadians(90)1.5707963267948966
Math.sin(1.5707963...)1.0

A good habit when a Math method returns a surprising number: check the units before checking anything else. Trigonometric functions take radians in essentially every programming language, not just Java.

12. Which is bigger, round or a cast?

Prediction

The same value, two ways of making it a whole number.

double d = 2.7;
System.out.println((int) d);
System.out.println(Math.round(d));
operationbehaviourresult
(int) dtruncates toward zero2
Math.round(d)rounds to the nearest3

Predict first

What two values are printed?

  • 2 then 3
  • 3 then 3
  • 2 then 2
  • 3 then 2

Correct: 2 then 3

Why: A cast to int always rounds toward zero, discarding the fractional part, so 2.7 becomes 2. Math.round rounds to the nearest integer, so 2.7 becomes 3. Choosing between them is a decision about what you want, and the two agree only when the value has no fractional part.

13. Match the Math member to what it gives

Matching

Two constants and two methods.

Match the pairs

  • a. Math.PI
  • b. Math.sqrt(9.0)
  • c. Math.toRadians(180.0)
  • d. Math.round(4.6)
  • r1. an approximation of pi
  • r2. 3.0
  • r3. about 3.14159 — 180 degrees in radians
  • r4. 5, as a long

Why: The first is a constant and the others are methods. Note that toRadians(180) gives pi itself, which is a useful check on your understanding of the conversion: half a turn in degrees is pi in radians.

14. Why does Math.round return a long?

Socratic

It could have returned an int. Ask why it does not.

Discussion prompt

Math.round(double) returns a long rather than an int. Given that a long holds far larger values than an int, why would the designers choose that — and what would go wrong with an int?

Hint: How large can a double be?

Answer:

A double can hold values far larger than any int — well beyond 2 billion. If round returned an int, rounding a large double would have nowhere to put the answer, and would have to fail or silently give a wrong one.

Returning a long moves that problem much further away without eliminating it entirely. It is a good small example of a design principle worth noticing: choose the return type that can hold every answer the method might produce, not the one that is most convenient at the call site.

The practical consequence for you: int n = Math.round(2.7); will not compile, because assigning a long to an int is a narrowing conversion. You need long n, or an explicit cast.

15. Composition

Section

Section 4.7

16. A method call is an expression

Concept

You have learned to evaluate expressions like the sine of pi over two: first evaluate the argument, then the function itself. Java methods compose the same way — you can use any expression as an argument, as long as its value has the right type.

double x = Math.cos(angle + Math.PI / 2.0);

double y = Math.exp(Math.log(10.0));

double z = Math.pow(2.0, 10.0);   // 1024.0
expressioninnermost firstthen
Math.cos(angle + Math.PI / 2.0)divide PI by 2, add angletake the cosine of the sum
Math.exp(Math.log(10.0))log base e of 10raise e to that power — giving 10 back
Math.pow(2.0, 10.0)two arguments, no nesting2 raised to the 10th, which is 1024.0

In Java the log method always uses base e. Some Math methods take more than one argument: Math.pow raises its first argument to the power of its second.

17. Evaluating a nested call from the inside out

Picture it

Exactly the process you use for the logarithm of one over the sine of pi over two: evaluate the innermost argument, then the function around it, and repeat.

Figure (svg): A trace strip reducing a nested Math expression from the inside out in four steps

That last value is worth a comment: the cosine of pi over two is exactly zero mathematically, and Java gives a number around 10 to the minus 17. That is Lesson 2b's rounding error, appearing exactly where you were told to expect it.

18. Passing one method's result to another

Worked example

You can take the result of one method and pass it straight to another. Trace what happens, and in what order.

double x = Math.exp(Math.log(10.0));
stepexpressionvalue
1Math.log(10.0)2.302585092994046
2Math.exp(2.302585092994046)10.000000000000002
3assigned to x10.000000000000002

Evaluate the innermost call first.

Why: Math.log(10.0) gives the log base e of 10.

Use its result as the argument to the outer call.

Why: That value is passed to Math.exp, which raises e to that power.

Note the type must match at each step.

Why: log returns a double and exp requires a double, so the composition is legal.

Observe the answer.

Why: Mathematically exp(log(10)) is exactly 10; Java gives 10.000000000000002.

Verify: Print x and expect a value extremely close to 10 but not exactly 10.

Why: Two lessons meet here: composition works exactly as in mathematics, and floating-point arithmetic is approximate. If you had tested x == 10.0 it would have been false — which is Lesson 2b's warning about comparing doubles.

19. Order the evaluation

Ranking

For Math.sqrt(Math.pow(3.0, 2.0) + Math.pow(4.0, 2.0)).

Put in order

  1. Math.pow(3.0, 2.0) gives 9.0
  2. Math.pow(4.0, 2.0) gives 16.0
  3. 9.0 + 16.0 gives 25.0
  4. Math.sqrt(25.0) gives 5.0

Why: Arguments are evaluated before the method that uses them, innermost first. Both pow calls must finish before their results can be added, and the addition must finish before sqrt can be given its argument. The answer, 5.0, is the hypotenuse of a 3-4-5 triangle — which is the same example the next idea uses.

20. Forgetting the class name

Concept

When using Math methods, beginners often leave off the word Math. The resulting error message is confusing until you know what it is telling you.

double x = pow(2.0, 10.0);   // no Math.
line of the messagewhat it means
Error: cannot find symbola name could not be resolved
symbol: method pow(double,double)specifically, a method with this signature
location: class Testit looked in YOUR class and did not find it

The last two lines are the useful hint. If you do not specify a class name when referring to a method, the compiler looks in the current class by default — so it searched Test.java for a pow method and found none. Writing Math.pow tells it where to look.

21. cannot find symbol, for two different reasons

Trap

The trap

Two mistakes with identical-looking messages. Knowing which one you have is the whole diagnosis.

double x = pow(2.0, 10.0);    // forgot the class name
Scanner in = new Scanner(System.in);   // forgot the import
mistakewhat the message saysthe giveaway
forgot Math.symbol: method pow — location: class Testit names a METHOD and YOUR class
forgot the importsymbol: class Scannerit names a CLASS

Both say cannot find symbol. The lines beneath say which kind of name went missing, and that decides whether you need a class prefix or an import statement.

The fix

Read the symbol: and location: lines, and fix accordingly.

double x = Math.pow(2.0, 10.0);   // class name supplied

import java.util.Scanner;         // at the top of the file
Scanner in = new Scanner(System.in);
symbol: saysyou need
method ... location: class YourClassa class name in front of the method call
class Somethingan import for that class
variable somethinga declaration, or a fix to a typo in the name

Three causes, one message, and the symbol: line distinguishes them every time. This is Lesson 3a's point about vocabulary paying off: the message names the kind of thing it could not find.

22. What is the error?

Prediction

One word is missing.

double x = sqrt(16.0);
line of the messagecontent
Errorcannot find symbol
symbolmethod sqrt(double)
locationclass Test

Predict first

What is wrong, and what does the message's location: line tell you?

  • The class name Math is missing; the compiler searched the current class by default
  • sqrt needs an import statement
  • The argument should be an int
  • sqrt returns a long, not a double

Correct: The class name Math is missing; the compiler searched the current class by default

Why: If you do not specify a class name when referring to a method, the compiler looks in the current class — which is exactly what location: class Test is reporting. Writing Math.sqrt(16.0) tells it where the method actually lives. Math needs no import because it is in java.lang.

23. Compose two calls

Fill the middle

Compute the square root of 2 raised to the 10th power.

Fill in the blanks

double x = Math.sqrt(Math.pow(2.0, 10.0));

Why: Math.pow(2.0, 10.0) gives 1024.0, and Math.sqrt of that gives 32.0. The inner call is evaluated first and its result becomes the outer call's argument — which works because pow returns a double and sqrt requires one.

24. Why can a method call be an argument?

Explain it to yourself

State the rule that makes composition possible.

Discussion prompt

You can write Math.exp(Math.log(10.0)). In one sentence, say what property of a method call makes it legal to put one inside another — and say which methods this would NOT work for.

Hint: What does an argument have to be?

Answer:

A value-returning method call is an expression, and any expression whose value has the right type can be used as an argument. So a call that produces a double can go wherever a double is required.

It would not work for a void method. Math.exp(printTwice("hi")) is meaningless, because printTwice produces no value — there is nothing for exp to receive. That is the practical difference between the two kinds of method, and it is why the next section matters.

25. Return values

Section

Section 4.8

26. Declare a return type and return a value

Concept

When you invoke a void method, the invocation is usually on a line by itself. When you invoke a value-returning method, you have to do something with the result — assign it to a variable, or use it as part of an expression.

public static double calculateArea(double radius) {
    double result = Math.PI * radius * radius;
    return result;
}
difference from a void methodin this example
declares the type of the return valuedouble where you are used to seeing void
uses at least one return statementreturn result;
the caller must use the resultdouble area = calculateArea(5.0);

The last line is a new form of the return statement meaning return immediately from this method, and use the following expression as the return value.

27. A value in, a value out

Picture it

When main invokes calculateArea, the value 5.0 is assigned to the parameter radius; calculateArea then returns 78.54, which is assigned to the variable area.

Figure (svg): A stack diagram showing main with area, and calculateArea below with radius 5.0 and result 78.54

Two crossings of the boundary, in opposite directions. The argument goes down into the new frame as a parameter; the return value comes back up. Nothing else passes between them.

28. Writing calculateArea two ways

Worked example

The expression you return can be arbitrarily complex, so the method can be written more concisely. Both versions are correct, and the choice is about debugging.

// with a temporary variable
public static double calculateArea(double radius) {
    double result = Math.PI * radius * radius;
    return result;
}

// more concisely
public static double calculateArea(double radius) {
    return Math.PI * radius * radius;
}
versionadvantage
with resultyou can print it or inspect it in a debugger before returning
concisefewer lines, nothing to name
bothidentical behaviour and identical return value

Replace void with the type you will return.

Why: double, because the area of a circle is a floating-point value.

Compute the value.

Why: Math.PI * radius * radius — pi r squared, using the constant rather than a magic 3.14159.

Return it.

Why: The type of the expression must match the return type you declared.

Decide whether to keep the temporary variable.

Why: Think Java's advice: temporary variables like result often make debugging easier, especially when stepping through with a debugger.

Verify: Call it with 5.0 and expect approximately 78.53981633974483.

Why: Check the value independently: pi times 25 is about 78.54. Testing a method means knowing the right answer before you run it — a point the next idea makes central.

29. void or value-returning?

Discrimination

Decide from what the method is for, before looking at any code.

Sort into buckets

Sort each method by the kind you would write.

void
print a heading with dashes around it; display a formatted time
value-returning
work out the area of a circle; convert centimetres to inches; work out the larger of two numbers
v
Its whole job is an effect — something appears on the screen. There is no answer for the caller to use afterwards.
r
It computes an answer the caller needs. Making it return the value rather than print it means the caller can print it, store it, or feed it into another calculation.

30. The return type is a promise

Concept

When you declare that the return type is double, you are making a promise that this method will eventually produce a double value. The compiler holds you to it.

what you writewhat the compiler does
return type double, return a doubleaccepts it
return type double, return with no expressionerror — a value was promised
return type double, return a Stringerror — wrong type
return type void, return a valueerror — nothing was promised
return type double, no return statement at allerror — missing return statement

The type of the expression in the return statement must match the return type of the method itself. This is the same bargain as declaring a variable's type: you say what will be there, and the compiler checks every place it could go wrong.

31. Calling a value-returning method and ignoring the result

Trap

The trap

The mistake. Calling it like a void method and expecting something to happen.

double radius = 5.0;
calculateArea(radius);            // the value is computed and thrown away
System.out.println(radius);       // still 5.0 — nothing changed
stepwhat happens
calculateArea(radius)computes 78.54
the return valuereturned to the caller
the callerdoes nothing with it — the value is discarded
radiusunchanged, because a method cannot change its caller's variables

This compiles and runs. It is a logic error: the work is done and the answer is thrown away, and nothing anywhere reports a problem.

The fix

Catch the return value in a variable, or use it in an expression.

double radius = 5.0;
double area = calculateArea(radius);        // caught in a variable
System.out.println(area);

System.out.println(calculateArea(3.0) * 2); // used in an expression
how the result is usedlegal?
assigned to a variableyes — the usual way
used as part of a larger expressionyes
passed as an argument to another methodyes — this is composition
ignored entirelylegal, but almost always a mistake

This is the counterpart of Lesson 4a's rule. A method cannot change its caller's variables, so the only way information comes back is the return value — and only if the caller does something with it.

32. What does this print?

Prediction

Watch what the caller does with the result.

public static int doubled(int n) {
    return n * 2;
}

public static void main(String[] args) {
    int x = 5;
    doubled(x);
    System.out.println(x);
}
stepxthe returned value
int x = 5;5—
doubled(x)510 — returned
the caller ignores it5discarded
println(x)5—

Predict first

What is displayed?

  • 5
  • 10
  • nothing
  • a compile error

Correct: 5

Why: The method computes 10 and returns it, but the caller does nothing with the value, so it is discarded. x is unchanged because a method cannot modify its caller's variables. To see 10 you would write x = doubled(x); — the assignment in main is what actually changes x.

33. Write a value-returning method

Fill the middle

A method that returns the larger of two integers is not possible yet — but the square is.

Fill in the blanks

public static int square(int n) return} n * n;
}

Why: The method produces an int, so int replaces void in the header as a promise about what will come back. The return statement then delivers on that promise, and the type of the expression n * n — an int times an int — matches what was declared.

34. What is missing from this method?

Missing information

It declares a return type but the compiler rejects it.

Discussion prompt

public static double half(double x) { double h = x / 2.0; } does not compile. What is missing, what exactly does the compiler say, and why is this an error rather than a warning?

Hint: The header made a promise.

Answer:

The return h; statement is missing, and the compiler says missing return statement. The header promised a double and no path through the method produces one.

It is an error rather than a warning because the caller is entitled to rely on the promise. double y = half(4.0); has to receive a value — if the method could finish without producing one, there would be nothing to assign, and Java has no way to represent 'no answer' for a double.

This is a good example of a type system doing real work: the declaration is checked at both ends. The method must produce what it promised, and the caller can rely on getting it.

35. Incremental development

Section

Section 4.9

36. Start with a working program and change it a little

Concept

People often make the mistake of writing a lot of code before they try to compile and run it — and then spend far too long debugging. A better approach is incremental development.

Figure (svg): Three boxes stating the key aspects of incremental development: small changes, intermediate variables, and consolidating only at the end

This is the same principle Section 1.9 gave for debugging, applied to writing rather than to fixing. It is also how Linux began, as a program that switched between printing AAAA and BBBB.

37. A stub is a method that compiles and does nothing

Notation

The first step is to decide what the method's inputs and output are, and write the header with a placeholder body.

Annotate

  • The parameters are the inputs. Two points, and it is natural to represent them as four double values.
  • The return type is the output. The distance is a length, so it should be a double.
  • The return statement is a placeholder, necessary only so the program compiles. At this stage the method does nothing useful.
  • Compile it anyway. It is good to compile at this point so you find any syntax errors before adding more code — a misspelled parameter is much easier to see now than later.
  • Then write the test before the body. It is usually a good idea to think about testing before you develop a new method; doing so can help you figure out how to implement it.

The header is the hard part and the body is the easy part. Getting the inputs and output right first is what makes the rest mechanical.

38. Choosing a test case with a known answer

Worked example

To test the method we invoke it from main with sample values. The values are not arbitrary — they are chosen so that you already know the right answer.

double dist = distance(1.0, 2.0, 4.0, 6.0);
// horizontal distance 3.0, vertical distance 4.0
// so the answer should be 5.0
quantityvaluewhy you know it
x2 - x13.04 minus 1
y2 - y14.06 minus 2
dsquared25.09 plus 16
distance5.0the hypotenuse of a 3-4-5 triangle

Pick inputs whose answer you can work out independently.

Why: A 3-4-5 triangle is the classic choice because the answer is a whole number you already know.

Write down the expected answer before running anything.

Why: When you are testing a method, it is necessary to know the right answer.

Note the intermediate values too.

Why: 3.0, 4.0 and 25.0 — each one is a checkpoint you can verify on the way.

Only then start filling in the body.

Why: Every step now has something to check against.

Verify: Once the method is finished, distance(1.0, 2.0, 4.0, 6.0) must give exactly 5.0.

Why: If it gives 25.0 you forgot the square root; if it gives 7.0 you added the differences instead of their squares. Knowing the intermediate values is what lets you tell those two apart immediately.

39. Order the incremental steps

Ranking

Building the distance method, from nothing to finished.

Put in order

  1. decide the parameters and return type
  2. write the stub with return 0.0 and compile it
  3. choose a test case whose answer you know
  4. add dx and dy, print them, check they are 3.0 and 4.0
  5. add dsquared, print it, check it is 25.0
  6. add Math.sqrt and return the real result
  7. remove the scaffolding

Why: The header comes first because it decides everything else; the stub is compiled immediately to catch syntax errors while there are only three lines to search. The test case is chosen before the body is written, so every subsequent step has something to check against. Scaffolding goes last, once nothing still depends on it.

40. Scaffolding: code that helps you build and then goes

Concept

The print statements you add to check intermediate values are useful while you are building and are not part of the finished method. Think Java calls them scaffolding.

public static double distance
        (double x1, double y1, double x2, double y2) {
    double dx = x2 - x1;
    double dy = y2 - y1;
    System.out.println("dx is " + dx);      // scaffolding
    System.out.println("dy is " + dy);      // scaffolding
    return 0.0;    // stub
}
linepermanent or temporary?
double dx = x2 - x1;permanent — a real step
println("dx is " + dx);scaffolding — remove when finished
return 0.0; // stubtemporary — replaced by the real answer

The name is exact: scaffolding is helpful for building the program but is not part of the final product. Leaving it in is not harmless — it clutters the output and confuses anyone who reads the method later.

41. Writing the whole method before compiling

Trap

The trap

The all-at-once approach. Forty lines, then compile, then face whatever comes.

// write all of it
// compile
// 6 errors, in 4 different lines
// fix one, recompile, 5 errors
// ...
what went wrongwhere do you look?
a syntax erroranywhere in 40 lines
a wrong intermediate valueanywhere in 40 lines
two mistakes interactinganywhere, twice

The cost is not the number of errors — it is that each one could be anywhere. Debugging becomes a search rather than a check.

The fix

One change, then compile and run. The error is in what you just added.

// stub          -> compile, run           (finds header errors)
// add dx, dy    -> compile, run, print   (should be 3.0 and 4.0)
// add dsquared  -> compile, run, print   (should be 25.0)
// add sqrt      -> compile, run          (should be 5.0)
stagewhat you checkexpected
stubit compilesno output
differencesdx and dy3.0 and 4.0
squares summeddsquared25.0
square rootthe return value5.0

After each incremental change you recompile and run. If there is an error, you have a very good idea where to look: the lines you just added. As you gain experience you may write more than one line at a time — but if you find yourself spending a lot of time debugging, take smaller steps.

42. Why compile a stub that does nothing?

Prediction

It returns 0.0 and computes nothing. It seems like a wasted step.

Predict first

What is the point of compiling the stub before writing any real code?

  • To catch syntax errors in the header while there are only three lines to search
  • To check that the answer is 0.0
  • Because Java requires a method to be compiled before it is edited
  • To make the method available to other classes

Correct: To catch syntax errors in the header while there are only three lines to search

Why: The header is where a misspelled type, a missing comma between parameters or an unbalanced parenthesis will be, and compiling now means any such error is in the three lines you just typed. It is the first instance of the whole principle: never let the amount of unverified code get large.

43. Which lines are scaffolding?

Error analysis

A method part-way through development. Decide what stays and what goes.

Annotate

  • double dx = x2 - x1; — permanent. It is a real step in the calculation, and it is also a temporary variable that makes the value inspectable, which Think Java recommends keeping.
  • println("dx is " + dx); — scaffolding. It exists to check an intermediate value and should be removed when the method works.
  • double dsquared = ...; — permanent, for the same reason as dx.
  • println("dsquared is " + dsquared); — scaffolding.
  • return 0.0; // stub — neither. It is a placeholder to be REPLACED, not removed: the finished method still needs a return statement, just one that returns the real answer.
  • The distinction worth holding: temporary variables can stay (they aid debugging later too), print statements go, and the stub is replaced.

Removing scaffolding is a real step, not tidying. A method that prints while computing cannot be used inside a larger program without spraying output everywhere.

44. Develop a method incrementally, on paper

Constraint

Practise the method itself rather than the example.

Discussion prompt

You need a method that returns the average of three doubles. Write down, in order, the four or five incremental stages you would go through — including what you would check at each one and what test values you would use.

Hint: Start with the header, and pick numbers whose average you know instantly.

Answer:

1. Decide the header: three double parameters, returning a double. 2. Write the stub return 0.0; and compile it. 3. Choose a test case — 2.0, 4.0 and 6.0, whose average is plainly 4.0. 4. Add double sum = a + b + c; and print it; expect 12.0. 5. Replace the stub with return sum / 3.0; and check the result is 4.0. 6. Remove the print.

The two decisions that matter are choosing test values with an obvious answer, and checking sum separately. If the final answer were wrong, knowing whether sum was right immediately halves the search — the bug is either in the addition or in the division, and never in both at once.

Note the 3.0 rather than 3. Sum is a double so the division is floating-point either way, but writing 3.0 says so, and Lesson 2b is a good reason to be in that habit.

45. Building distance, step by step

Section

Section 4.9, continued

46. The finished method, and how it was reached

Concept

Here is the completed method. Nothing in it is complicated — the point of the section is that it was never written in this form. It was grown, one checked line at a time.

public static double distance
        (double x1, double y1, double x2, double y2) {
    double dx = x2 - x1;
    double dy = y2 - y1;
    double dsquared = dx * dx + dy * dy;
    double result = Math.sqrt(dsquared);
    return result;
}
lineadded at stagechecked against
the header1it compiles
dx and dy23.0 and 4.0
dsquared325.0
result and return45.0

Each line was compiled and run before the next was added. At no point was there more than one line of unverified code in the method.

47. Four versions of one method

Picture it

The same method at each stage. What grows is the body; what stays constant is that it compiles and runs at every step.

Figure (svg): Two panels comparing the stub version of distance with the finished version

The left version is worth more than it looks. It proves the header is right, which is where most of the syntax errors in a new method live.

48. Tracing distance with the test case

Worked example

Run the finished method by hand on the values chosen earlier, checking each intermediate against what was predicted.

double dist = distance(1.0, 2.0, 4.0, 6.0);
statementexpressionvaluepredicted?
double dx = x2 - x1;4.0 - 1.03.0yes
double dy = y2 - y1;6.0 - 2.04.0yes
double dsquared = dx * dx + dy * dy;9.0 + 16.025.0yes
double result = Math.sqrt(dsquared);sqrt(25.0)5.0yes
return result;—5.0yes

Compute the differences.

Why: Note that they are computed as x2 minus x1, not the other way round — though squaring makes the sign irrelevant here.

Square each and add.

Why: Think Java notes you could use Math.pow, but multiplying each term by itself is simpler and more efficient.

Take the square root.

Why: Math.sqrt on the sum of the squares — the definition of distance.

Return the result.

Why: The temporary variable makes the value inspectable in a debugger, which is why it is kept.

Verify: The answer must be exactly 5.0 — the hypotenuse of a 3-4-5 triangle.

Why: Every intermediate matched its prediction, which means that had the answer been wrong, the error would have to be in the one step you had not yet checked. That is the entire payoff of developing incrementally.

49. Which intermediate would reveal a missing square root?

Prediction

Suppose the method returned dsquared instead of its square root.

double dx = x2 - x1;        // 3.0
double dy = y2 - y1;        // 4.0
double dsquared = dx * dx + dy * dy;
return dsquared;            // the bug
valuecorrectwith the bug
dx3.03.0
dy4.04.0
dsquared25.025.0
returned5.025.0

Predict first

Which checked value first differs from what was predicted?

  • The returned value — every intermediate is correct
  • dx
  • dy
  • dsquared

Correct: The returned value — every intermediate is correct

Why: All three intermediates are right, so the bug must be in the only step after them — the square root. This is precisely how incremental development pays off: because each earlier value was checked, the error is localised to the last thing added, without any searching.

50. When to take bigger steps

Concept

Incremental development is a dial rather than a rule. The right step size depends on how confident you are, and there is a simple feedback signal for adjusting it.

situationstep size
a language feature you have just metone line at a time
a pattern you have written many timesseveral lines
you are spending a lot of time debuggingtake smaller steps
everything works first time, repeatedlyyou can afford larger ones

Think Java's own phrasing: as you gain more experience programming, you might write and debug more than one line at a time. But if you find yourself spending a lot of time debugging, consider taking smaller steps. The amount of time you spend debugging is the signal telling you to adjust.

51. Testing with values whose answer you do not know

Trap

The trap

The mistake. Calling the new method with arbitrary numbers to 'see if it works'.

double d = distance(1.7, 2.3, 5.1, 8.8);
System.out.println(d);   // 7.457881...  is that right?
what you learn if it prints 7.457881what you learn if it prints 55.61
nothing — it might be rightnothing — it might be right
you cannot tellyou cannot tell

A test you cannot grade is not a test. It tells you the method did not crash, which you already suspected, and nothing about whether the answer is correct.

The fix

Choose values whose answer you already know.

double d = distance(1.0, 2.0, 4.0, 6.0);
System.out.println(d);   // must be exactly 5.0
test casewhy it is a good one
(1,2) to (4,6)a 3-4-5 triangle — the answer is exactly 5.0
(0,0) to (0,0)the same point — the answer must be 0.0
(0,0) to (3,0)horizontal only — the answer is 3.0

Think Java states this as a requirement rather than advice: when you are testing a method, it is necessary to know the right answer. The other two cases here are worth adding as well — a zero distance and a purely horizontal one are the edges where a sign or a swapped variable would show up.

52. Test case: useful or not?

Definition probe

A test is useful only if you know what it should produce.

Sort into buckets

Sort each proposed test of distance.

a useful test
distance(1.0, 2.0, 4.0, 6.0) — expect 5.0; distance(0.0, 0.0, 0.0, 0.0) — expect 0.0; distance(0.0, 0.0, 3.0, 0.0) — expect 3.0
not a test
distance(1.7, 2.3, 5.1, 8.8) — expect... something
good
You know the right answer before running it, so the output can be graded. The zero case and the horizontal case are especially good — they are edges where a swapped or negated variable would show up.
bad
You cannot say what the output should be, so whatever appears teaches you nothing. It confirms only that the method did not crash.

53. Watch the method grow

Scale up

Four stages, each one compiled and run before the next.

Step through it

At which stage would a misspelled parameter name have been caught?

  1. The stub compiles. This proves the header — four parameters, a double return type — is written correctly.
  2. dx is 3.0 and dy is 4.0, matching the prediction. If either were wrong, the error would be in one of two new lines.
  3. dsquared is 25.0. Nine plus sixteen — again exactly as predicted before the line was written.
  4. Math.sqrt(25.0) gives 5.0. Every intermediate was verified, so this last step is the only place a new error could be.

The first. That is the whole argument for compiling a stub that does nothing: it verifies the part of the method that everything else depends on, while there is almost nothing to search.

54. Where else does 'always keep it working' appear?

Real world

Incremental development is not just advice for beginners. It has a name in professional practice.

Discussion prompt

The rule is: never let the program be broken for long, and always know that the last change is the cause. Where have you seen that idea outside programming — and what would it look like in a team of twenty people?

Hint: Think about anything built in stages where you can go back a step.

Answer:

In a team it becomes continuous integration: everyone's changes are compiled and tested automatically, many times a day, so a break is always attributable to a change made minutes ago rather than to any of a thousand made this month.

It is the same logic scaled up. The reason to keep the program working is not tidiness — it is that a working baseline turns debugging from a search into a check. Think Java makes the point with Linux, which grew from a program that alternated printing AAAA and BBBB.

The habit transfers to anything built in steps: keep a version that works, change one thing, verify, repeat. It feels slower and is reliably faster.

55. void methods and value-returning methods

Comparison

The two kinds, side by side. Fill the blanks.

Comparison matrix

void methodvalue-returning method
header saysvoidthe type of the value it returns
body containsstatements onlyat least one return statement with an expression
how you call iton a line of its ownassign the result, or use it in an expression
can be nested in another call?no — there is no value to passyes — this is composition
exampleprintTime(11, 59);double a = calculateArea(5.0);

The fourth row is the practical difference. A void method is a dead end; a value-returning one can be built into something larger, which is what makes a program more than a list of actions.

56. The pattern to carry away

Pattern

Developing any new method follows the same four stages, and the discipline is worth more than any individual piece of Java in this chapter.

// 1. header: what goes in, what comes out
public static double average(double a, double b, double c) {
    return 0.0;                      // stub - compile it now
}

// 2. test case with a KNOWN answer
double x = average(2.0, 4.0, 6.0);   // must be 4.0

// 3. one step at a time, checking intermediates
double sum = a + b + c;              // print it: expect 12.0

// 4. finish, then remove the scaffolding
return sum / 3.0;
stagewhat it proves
stub compilesthe header is right
test case chosenyou will be able to grade the output
intermediate checkedthe error, if any, is in the last line added
scaffolding removedthe method is usable inside a larger program

57. Check: return values

Check

Work it out before you click.

public static int addTen(int n) {
    return n + 10;
}

public static void main(String[] args) {
    int a = 5;
    int b = addTen(a);
    System.out.println(a + " " + b);
}
stepab
int a = 5;5—
addTen(a) returns 155—
b = that value515

Check your understanding

What does this display?

  • A. 5 15 (correct)
  • B. 15 15
  • C. 5 5
  • D. 15 5

Answer: A

Why: The method receives a copy of a's value and returns 15, which main assigns to b. a itself is never modified, because a method cannot change its caller's variables — only the assignment in main changes anything, and it changes b.

Why B tempts people
This assumes calling addTen changed a as well, but the parameter received a copy and the caller's variable was untouched.
Why C tempts people
This ignores the return value entirely; b was assigned the result, so it cannot still be 5.
Why D tempts people
This has the two variables the wrong way round — a is printed first and was never changed.

58. Check: Math methods

Check

Work it out before you click.

Check your understanding

Which line correctly computes the sine of 30 degrees?

  • A. double s = Math.sin(Math.toRadians(30)); (correct)
  • B. double s = Math.sin(30);
  • C. double s = Math.sin(Math.PI(30));
  • D. double s = sin(Math.toRadians(30));

Answer: A

Why: Trigonometric methods take radians, so the degrees must be converted first — and Math.toRadians does exactly that conversion. The result is 0.49999999999999994, which is 0.5 to within floating-point rounding error.

Why B tempts people
This passes 30 radians rather than 30 degrees, giving a plausible-looking but completely wrong answer with no error message.
Why C tempts people
PI is a constant, not a method, so it cannot be called with an argument — this does not compile.
Why D tempts people
Without the class name the compiler looks for sin in the current class and reports 'cannot find symbol'.

59. Check: incremental development

Check

Work it out before you click.

Check your understanding

Why does Think Java recommend compiling the stub before writing any of the method's real code?

  • A. So that any syntax error in the header is found while there are only a few lines to search (correct)
  • B. Because Java requires a method to compile before it can be edited
  • C. To check that the stub returns the right answer
  • D. So the method appears in the documentation

Answer: A

Why: The header — the parameter list, the return type, the braces — is where most of a new method's syntax errors live, and compiling immediately means any such error is in the three lines you just typed rather than hidden among forty.

Why B tempts people
There is no such requirement; you can edit a file as much as you like before compiling it.
Why C tempts people
The stub deliberately returns a placeholder value, so its answer is known to be wrong and is not what is being checked.
Why D tempts people
Documentation is generated from doc comments and has nothing to do with when you compile.

60. Why library methods are worth finding

Real world

You could have written toRadians yourself in one line. There are reasons to look for the library version first.

Discussion prompt

Math.toRadians(d) does what d / 180.0 * Math.PI does. Give two reasons to prefer the library call, and one situation where writing it yourself would be better.

Hint: Think about what a reader sees, and what a typo would do.

Answer:

It says what it means. A reader sees the intent rather than having to recognise a formula. And it cannot be mistyped into a plausible wrong formula — writing 180 instead of 180.0 gives zero, silently.

The wider point is that the standard edition of Java comes with several thousand classes, and Think Java's advice is to take a minute to read the documentation for a class before using it. Time spent finding an existing method is usually less than time spent debugging your own.

When to write it yourself: when you are learning, and the point of the exercise is to understand the operation rather than to get the answer. That is exactly why this chapter has you compute radians both ways before telling you the library method exists.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

What is wrong with int n = Math.round(2.7);?

  • Math.round returns a long, and assigning a long to an int is a narrowing conversion Java will not do silently
  • Nothing — it compiles and n becomes 3
  • Math.round takes an int, not a double
  • round should be written Math.Round

Correct: Math.round returns a long, and assigning a long to an int is a narrowing conversion Java will not do silently

Why: A long holds far larger values than an int, so assigning one to the other could lose information and Java refuses to do it automatically — the same rule that forbids int x = 1.1; in Lesson 2b. The fix is either long n = Math.round(2.7); or an explicit cast, int n = (int) Math.round(2.7);. Note the cast here is harmless: round has already produced a whole number, so nothing is truncated.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate has written a method that computes an answer and prints it, and cannot see why anyone would return a value instead. Give them a concrete example where returning is necessary and printing will not do.

Hint: What if the caller needs to use the answer for something else?

Answer:

Suppose you have a method that computes the distance between two points. If it prints the answer, that is all it can ever do. If it returns the answer, the caller can print it — or compare it with another distance, or add it to a running total, or pass it to Math.round.

And you cannot compose printing. Math.sqrt(printDistance(...)) is meaningless, because printing produces no value. Returning is what lets a method be used as a building block rather than as a finished action.

The general principle worth stating: a method that computes should return, and a method that displays should print — and the two jobs belong in different methods. Mixing them is why the classmate's method cannot be reused.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

What are the two things a value-returning method has that a void method does not?

  • A declared return type in place of void, and at least one return statement with an expression
  • Parameters, and a call from main
  • A public modifier, and a semicolon
  • A stub, and scaffolding

Correct: A declared return type in place of void, and at least one return statement with an expression

Why: Those two go together: the header declares what type the method promises to produce, and the return statement delivers a value of that type. The compiler checks both ends — a method that declares a type and never returns one gives 'missing return statement', and a return whose expression has the wrong type is rejected too. Parameters and modifiers appear on both kinds of method, and stubs and scaffolding are development techniques rather than parts of a method.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw the stack diagram for double area = calculateArea(5.0); at the moment just before the return, labelling which value crosses the boundary in each direction. Then write out the four stages of building the distance method, with the value you would check at each stage. Finally, write one sentence saying why a method that prints its answer cannot be composed.

65. Recap

Recap

Four sections that complete the chapter: methods you did not write, methods that hand a value back, and the discipline for building them without ever debugging much at once.

if you remember one thingit is this
about methodscompute and return; display somewhere else
about testingyou must know the right answer first
about buildingnever let unverified code get large

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 4 (Methods and Testing), Sections 4.6-4.9, pp. 58-64
  2. The Java Tutorials — Returning a Value from a Method
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108