Think Java: How to Think Like a Computer Scientist by Allen B. Downey - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

Chapter 3

Methods

3.1

Floating-point

In the last chapter we had some problems dealing with numbers that were not

integers. We worked around the problem by measuring percentages instead

of fractions, but a more general solution is to use floating-point numbers,

which can represent fractions as well as integers. In Java, the floating-point

type is called double, which is short for “double-precision.”

You can create floating-point variables and assign values to them using the

same syntax we used for the other types. For example:

double pi;

pi = 3.14159;

It is also legal to declare a variable and assign a value to it at the same time:

int x = 1;

String empty = "";

double pi = 3.14159;

This syntax is common; a combined declaration and assignment is sometimes

called an initialization.

Although floating-point numbers are useful, they are a source of confusion

because there seems to be an overlap between integers and floating-point

numbers. For example, if you have the value 1, is that an integer, a floating-

point number, or both?

26

Chapter 3. Methods

Java distinguishes the integer value 1 from the floating-point value 1.0, even

though they seem to be the same number. They belong to different types, and

strictly speaking, you are not allowed to make assignments between types.

For example, the following is illegal:

int x = 1.1;

because the variable on the left is an int and the value on the right is a

double. But it is easy to forget this rule, especially because there are places

where Java will automatically convert from one type to another. For example:

double y = 1;

should technically not be legal, but Java allows it by converting the int to a

double automatically. This leniency is convenient, but it can cause problems;

for example:

double y = 1 / 3;

You might expect the variable y to get the value 0.333333, which is a legal

floating-point value, but in fact it gets 0.0. The reason is that the expression

on the right is the ratio of two integers, so Java does integer division, which

yields the integer value 0. Converted to floating-point, the result is 0.0.

One way to solve this problem (once you figure out what it is) is to make the

right-hand side a floating-point expression:

double y = 1.0 / 3.0;

This sets y to 0.333333, as expected.

The operations we have seen so far—addition, subtraction, multiplication,

and division—also work on floating-point values, although you might be in-

terested to know that the underlying mechanism is completely different. In

fact, most processors have special hardware just for performing floating-point

operations.

3.2

Converting from double to int

As I mentioned, Java converts ints to doubles automatically if necessary,

because no information is lost in the translation. On the other hand, going

from a double to an int requires rounding off. Java doesn’t perform this

3.3. Math methods

27

operation automatically, in order to make sure that you, as the programmer,

are aware of the loss of the fractional part of the number.

The simplest way to convert a floating-point value to an integer is to use a

typecast. Typecasting is so called because it allows you to take a value that

belongs to one type and “cast” it into another type (in the sense of molding

or reforming).

The syntax for typecasting is to put the name of the type in parentheses and

use it as an operator. For example,

double pi = 3.14159;

int x = (int) pi;

The (int) operator has the effect of converting what follows into an integer,

so x gets the value 3.

Typecasting takes precedence over arithmetic operations, so in the following

example, the value of pi gets converted to an integer first, and the result is

60.0, not 62.

double pi = 3.14159;

double x = (int) pi * 20.0;

Converting to an integer always rounds down, even if the fraction part is

0.99999999. These behaviors (precedence and rounding) can make typecast-

ing error-prone.

3.3

Math methods

In mathematics, you have probably seen functions like sin and log, and you

have learned to evaluate expressions like sin(π/2) and log(1/x). First, you

evaluate the expression in parentheses, which is called the argument of the

function. Then you can evaluate the function itself, either by looking it up

in a table or by performing various computations.

This process can be applied repeatedly to evaluate more complicated expres-

sions like log(1/ sin(π/2)). First we evaluate the argument of the innermost

function, then evaluate the function, and so on.

Java provides functions that perform the most common mathematical opera-

tions. These functions are called methods. The math methods are invoked

using a syntax that is similar to the print statements we have already seen:

28

Chapter 3. Methods

double root = Math.sqrt(17.0);

double angle = 1.5;

double height = Math.sin(angle);

The first example sets root to the square root of 17. The second example

finds the sine of the value of angle, which is 1.5. Java assumes that the

values you use with sin and the other trigonometric functions (cos, tan) are

in radians. To convert from degrees to radians, you can divide by 360 and

multiply by 2π. Conveniently, Java provides Math.PI:

double degrees = 90;

double angle = degrees * 2 * Math.PI / 360.0;

Notice that PI is in all capital letters. Java does not recognize Pi, pi, or

pie.

Another useful method in the Math class is round, which rounds a floating-

point value off to the nearest integer and returns an int.

int x = Math.round(Math.PI * 20.0);

In this case the multiplication happens first, before the method is invoked.

The result is 63 (rounded up from 62.8319).

3.4

Composition

Just as with mathematical functions, Java methods can be composed, mean-

ing that you use one expression as part of another. For example, you can use

any expression as an argument to a method:

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

This statement takes the value Math.PI, divides it by two and adds the result

to the value of the variable angle. The sum is then passed as an argument to

cos. (PI is the name of a variable, not a method, so there are no arguments,

not even the empty argument ()).

You can also take the result of one method and pass it as an argument to

another:

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

In Java, the log method always uses base e, so this statement finds the log

base e of 10 and then raises e to that power. The result gets assigned to x;

I hope you know what it is.

3.5. Adding new methods

29

3.5

Adding new methods

So far we have used methods from Java libraries, but it is also possible to

add new methods. We have already seen one method definition: main. The

method named main is special, but the syntax is the same for other methods:

public static void NAME( LIST OF PARAMETERS ) {

STATEMENTS

}

You can make up any name you want for your method, except that you

can’t call it main or any Java keyword. By convention, Java methods start

with a lower case letter and use “camel caps,” which is a cute name for

jammingWordsTogetherLikeThis.

The list of parameters specifies what information, if any, you have to provide

to use (or invoke) the new method.

The parameter for main is String[] args, which means that whoever in-

vokes main has to provide an array of Strings (we’ll get to arrays in Chap-

ter 12). The first couple of methods we are going to write have no parameters,

so the syntax looks like this:

public static void newLine() {

System.out.println("");

}

This method is named newLine, and the empty parentheses mean that it

takes no parameters.

It contains one statement, which prints an empty

String, indicated by "". Printing a String with no letters in it may not

seem all that useful, but println skips to the next line after it prints, so this

statement skips to the next line.

In main we invoke this new method the same way we invoke Java methods:

public static void main(String[] args) {

System.out.println("First line.");

newLine();

System.out.println("Second line.");

}

The output of this program is

30

Chapter 3. Methods

First line.

Second line.

Notice the extra space between the lines. What if we wanted more space

between the lines? We could invoke the same method repeatedly:

public static void main(String[] args) {

System.out.println("First line.");

newLine();

newLine();

newLine();

System.out.println("Second line.");

}

Or we could write a new method, named threeLine, that prints three new

lines:

public static void threeLine() {

newLine();

newLine();

newLine();

}

public static void main(String[] args) {

System.out.println("First line.");

threeLine();

System.out.println("Second line.");

}

You should notice a few things about this program:

❼ You can invoke the same procedure more than once.

❼ You can have one method invoke another method. In this case, main

invokes threeLine and threeLine invokes newLine.

❼ In threeLine I wrote three statements all on the same line, which is

syntactically legal (remember that spaces and new lines usually don’t

change the meaning of a program). It is usually a good idea to put

each statement on its own line, but I sometimes break that rule.

You might wonder why it is worth the trouble to create all these new methods.

There are several reasons; this example demonstrates two:

3.6. Classes and methods

31

1. Creating a new method gives you an opportunity to give a name to

a group of statements. Methods can simplify a program by hiding

a complex computation behind a single statement, and by using En-

glish words in place of arcane code.

Which is clearer, newLine or

System.out.println("")?

2. Creating a new method can make a program smaller by eliminating

repetitive code. For example, to print nine consecutive new lines, you

could invoke threeLine three times.

In Section 7.7 we will come back to this question and list some additional

benefits of dividing programs into methods.

3.6

Classes and methods

Pulling together the code fragments from the previous section, the class def-

inition looks like this:

class NewLine {

public static void newLine() {

System.out.println("");

}

public static void threeLine() {

newLine();

newLine();

newLine();

}

public static void main(String[] args) {

System.out.println("First line.");

threeLine();

System.out.println("Second line.");

}

}

The first line indicates that this is the class definition for a new class called

NewLine. A class is a collection of related methods. In this case, the class

named NewLine contains three methods, named newLine, threeLine, and

main.

32

Chapter 3. Methods

The other class we’ve seen is the Math class. It contains methods named

sqrt, sin, and others. When we invoke a mathematical method, we have to

specify the name of the class (Math) and the name of the method. That’s

why the syntax is slightly different for Java methods and the methods we

write:

Math.pow(2.0, 10.0);

newLine();

The first statement invokes the pow method in the Math class (which raises

the first argument to the power of the second argument). The second state-

ment invokes the newLine method, which Java assumes is in the NewLine

class, which is what we are writing.

If you try to invoke a method from the wrong class, the compiler will generate

an error. For example, if you type:

pow(2.0, 10.0);

The compiler will say something like, “Can’t find a method named pow in

class NewLine.” If you have seen this message, you might have wondered

why it was looking for pow in your class definition. Now you know.

3.7

Programs with multiple methods

When you look at a class definition that contains several methods, it is

tempting to read it from top to bottom, but that is likely to be confusing,

because that is not the order of execution of the program.

Execution always begins at the first statement of main, regardless of where

it is in the program (in this example I deliberately put it at the bottom).

Statements are executed one at a time, in order, until you reach a method

invocation. Method invocations are like a detour in the flow of execution.

Instead of going to the next statement, you go to the first line of the invoked

method, execute all the statements there, and then come back and pick up

again where you left off.

That sounds simple enough, except that you have to remember that one

method can invoke another. Thus, while we are in the middle of main, we

might have to go off and execute the statements in threeLine. But while

3.8. Parameters and arguments

33

we are executing threeLine, we get interrupted three times to go off and

execute newLine.

For its part, newLine invokes println, which causes yet another detour.

Fortunately, Java is adept at keeping track of where it is, so when println

completes, it picks up where it left off in newLine, and then gets back to

threeLine, and then finally gets back to main so the program can terminate.

Technically, the program does not terminate at the end of main. Instead,

execution picks up where it left off in the program that invoked main, which

is the Java interpreter. The interpreter takes care of things like deleting

windows and general cleanup, and then the program terminates.

What’s the moral of this sordid tale? When you read a program, don’t read

from top to bottom. Instead, follow the flow of execution.

3.8

Parameters and arguments

Some of the methods we have used require arguments, which are values

that you provide when you invoke the method. For example, to find the sine

of a number, you have to provide the number. So sin takes a double as

an argument. To print a string, you have to provide the string, so println

takes a String as an argument.

Some methods take more than one argument; for example, pow takes two

doubles, the base and the exponent.

When you use a method, you provide arguments. When you write a method,

you specify a list of parameters. A parameter is a variable that stores an

argument. The parameter list indicates what arguments are required.

For example, printTwice specifies a single parameter, s, that has type

String. I called it s to suggest that it is a String, but I could have given it

any legal variable name.

public static void printTwice(String s) {

System.out.println(s);

System.out.println(s);

}

34

Chapter 3. Methods

When we invoke printTwice, we have to provide a single argument with

type String.

printTwice("Don✬t make me say this twice!");

When you invoke a method, the argument you provide are assigned to

the parameters. In this example, the argument "Don’t make me say this

twice!" is assigned to the parameter s. This processing is called parame-

ter passing because the value gets passed from outside the method to the

inside.

An argument can be any kind of expression, so if you have a String variable,

you can use it as an argument:

String argument = "Never say never.";

printTwice(argument);

The value you provide as an argument must have the same type as the

parameter. For example, if you try this:

printTwice(17);

You get an error message like “cannot find symbol,” which isn’t very helpful.

The reason is that Java is looking for a method named printTwice that can

take an integer argument. Since there isn’t one, it can’t find such a “symbol.”

System.out.println can accept any type as an argument. But that is an

exception; most methods are not so accommodating.

3.9

Stack diagrams

Parameters and other variables only exist inside their own methods. Within

the confines of main, there is no such thing as s. If you try to use it, the

compiler will complain. Similarly, inside printTwice there is no such thing

as argument.

One way to keep track of where each variable is defined is with a stack

diagram. The stack diagram for the previous example looks like this:

index-55_1.png

3.10. Methods with multiple parameters

35

For each method there is a gray box called a frame that contains the

method’s parameters and variables. The name of the method appears out-

side the frame. As usual, the value of each variable is drawn inside a box

with the name of the variable beside it.

3.10

Methods with multiple parameters

The syntax for declaring and invoking methods with multiple parameters is

a common source of errors. First, remember that you have to declare the

type of every parameter. For example

public static void printTime(int hour, int minute) {

System.out.print(hour);

System.out.print(":");

System.out.println(minute);

}

It might be tempting to write int hour, minute, but that format is only

legal for variable declarations, not parameter lists.

Another common source of confusion is that you do not have to declare the

types of arguments. The following is wrong!

int hour = 11;

int minute = 59;

printTime(int hour, int minute);

// WRONG!

In this case, Java can tell the type of hour and minute by looking at their

declarations. It is not necessary to include the type when you pass them as

arguments. The correct syntax is printTime(hour, minute).

Exercise 3.1. Draw a stack frame that shows the state of the program when

main invokes printTime with the arguments 11 and 59.

36

Chapter 3. Methods

3.11

Methods with results

Some of the methods we are using, like the Math methods, yield results. Other

methods, like println and newLine, perform an action but they don’t return

a value. That raises some questions:

❼ What happens if you invoke a method and you don’t do anything with

the result (i.e. you don’t assign it to a variable or use it as part of a

larger expression)?

❼ What happens if you use a print method as part of an expression, like

System.out.println("boo!") + 7?

❼ Can we write methods that yield results, or are we stuck with things

like newLine and printTwice?

The answer to the third question is “yes, you can write methods that return

values,” and we’ll do it in a couple of chapters. I leave it up to you to answer

the other two questions by trying them out. In fact, any time you have a

question about what is legal or illegal in Java, a good way to find out is to

ask the compiler.

3.12

Glossary

initialization: A statement that declares a new variable and assigns a value

to it at the same time.

floating-point: A type of variable (or value) that can contain fractions as

well as integers. The floating-point type we will use is double.

class: A named collection of methods. So far, we have used the Math class

and the System class, and we have written classes named Hello and

NewLine.

method: A named sequence of statements that performs a useful function.

Methods may or may not take parameters, and may or may not produce

a result.

parameter: A piece of information a method requires before it can run.

Parameters are variables: they contain values and have types.

3.13. Exercises

37

argument: A value that you provide when you invoke a method. This value

must have the same type as the corresponding parameter.

frame: A structure (represented by a gray box in stack diagrams) that con-

tains a method’s parameters and variables.

invoke: Cause a method to execute.

3.13

Exercises

Exercise 3.2. The point of this exercise is to practice reading code and to

make sure that you understand the flow of execution through a program with

multiple methods.

1. What is the output of the following program? Be precise about where

there are spaces and where there are newlines.

HINT: Start by describing in words what ping and baffle do when

they are invoked.

2. Draw a stack diagram that shows the state of the program the first time

ping is invoked.

public static void zoop() {

baffle();

System.out.print("You wugga ");

baffle();

}

public static void main(String[] args) {

System.out.print("No, I ");

zoop();

System.out.print("I ");

baffle();

}

public static void baffle() {

System.out.print("wug");

38

Chapter 3. Methods

ping();

}

public static void ping() {

System.out.println(".");

}

Exercise 3.3. The point of this exercise is to make sure you understand how

to write and invoke methods that take parameters.

1. Write the first line of a method named zool that takes three parameters:

an int and two Strings.

2. Write a line of code that invokes zool, passing as arguments the value

11, the name of your first pet, and the name of the street you grew up

on.

Exercise 3.4. The purpose of this exercise is to take code from a previous

exercise and encapsulate it in a method that takes parameters. You should

start with a working solution to Exercise 2.2.

1. Write a method called printAmerican that takes the day, date, month

and year as parameters and that prints them in American format.

2. Test your method by invoking it from main and passing appropriate

arguments. The output should look something like this (except that the

date might be different):

Saturday, July 16, 2011

3. Once you have debugged printAmerican, write another method called

printEuropean that prints the date in European format.