close this bookHow to Think Like a Computer Scientist
source ref: ebookit.html
View the documentMetadata
View the documentChapter 1:The way of the program.
View the documentchapter 2:Variables and types
View the documentchapter 3:Methods
View the documentChapter 4:Conditionals, graphics, and recursion
View the documentChapter 5:Methods that return things
View the documentChapter 6:Iteration
View the documentChapter 7:Strings and things
View the documentChapter 8:Interesting objects
View the documentChapter 9:Create your own objects
View the documentChapter 10:Arrays
View the documentChapter 11:Object methods
View the documentChapter 12:Arrays of Objects
View the documentChapter 13:Object-oriented programming

chapter 3:Methods

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 and other non-integers. In Java, the floating-point type is called double.

You can create floating-point variables and assign values to them using the same syntax as 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;

In fact, this syntax is quite common.

Although floating-point numbers are quite useful, they are often 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?

Strictly speaking, Java distinguishes the integer value 1 from the floating-point value 1.0, even though they seem to be the same number. Nevertheless, 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 because converting an int to a double does not lose any information. This leniency is convenient, but it can cause problems; for example:


    double y = 1 / 3;

You might expect the variable y to be given the value 0.333333, which is a legal floating-point value, but in fact it will get the value 0.0. The reason is that the expression on the right appears to be 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.



3.2 Operations on doubles

All the operations we have seen so far--addition, subtraction, multiplication, and division--also work on floating-point values, although you might be interested to know that the underlying process is completely different. In fact, most processors have special hardware just for performing floating-point operations.

In addition, the Java library comes with a variety of special functions for performing mathematical operations on doubles. These functions are invoked using a syntax that is similar to the print commands we have already seen:


     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 1.5, which is the value of the variable angle. 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*PI. Conveniently, Java provides PI as a built-in value:


     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.



3.3 Adding new commands

So far we have only been using the functions that are built into Java, but it is also possible to add new functions. In other languages, these functions are sometimes called procedures, or subroutines, but in object-oriented languages like Java they are usually called methods .

Actually, we have already seen one method definition, main. The method named main is special in that it indicates where the execution of the program begins, but the syntax for main is the same as for any other method definition:


  public static void NAME ( LIST OF PARAMETERS ) {
    STATEMENTS
  }

You can make up any name you want (except of course, that you can't call it main. The list of parameters specifies what information, if any, you have to provide in order to use (or invoke) the new function.

The single parameter for main is String[] args, which indicates that whoever invokes main has to provide an array of Strings (we'll get to arrays in Chapter 10). 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 indicate that it takes no parameters. It contains only a single statement, which prints an empty String, indicated by "". Printing a String with no letters in it may not seem all that useful, except remember that println skips to the next line after it prints, so this statement has the effect of skipping to the next line.

In main we can invoke this new method using syntax that is similar to the way we invoke the built-in Java commands:


  public static void main (String[] args) {
    System.out.println ("First line.");
    newLine ();
    System.out.println ("Second line.");
  }

The output of this program is


First line.

Second line.

Notice the extra space between the two lines. What if we wanted more space between the lines? We could invoke the same procedure 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 repeatedly. In fact, it is quite common and useful to do so.
  • You can have one method invoke another method. In this case, main invokes threeLine and threeLine invokes newLine. Again, this is common and useful.
  • 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). On the other hand, it is usually a better idea to put each statement on a line by itself, to make your program easy to read. I sometimes break that rule in this book to save space.

So far, it may not be clear why it is worth the trouble to create all these new methods. Actually, there are a lot of reasons, but this example only demontrates two:

  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 command, and by using English 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, how would you print nine consecutive new lines? You could just invoke threeLine three times!



3.4 Classes and methods

Pulling together all the code fragments from the previous section, the whole class definition might look like this:


public 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. So far I haven't said much about what a class is. For now, let's just say that a class is a collection of methods. In this case, the class named NewLine contains three methods, named newLine, threeLine, and main.

Another class we've seen is the Math class. It contains methods named sqrt, sin, and many others. When we invoke a mathematical function, we have to specify the name of the class, Math and the name of the function. That's why the syntax is slightly different for built-in methods and the methods that we write:


    Math.pow (2.0, 10.0);
    newLine ();

The first statement invokes the pow method in the Math class (which raises the first value to the power of the second value). The second statement invokes the newLine method, which Java assumes (correctly) 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.5 Programs with multiple methods

When you look at a class definition that contains several methods, it is tempting to read them 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 case 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 we are executing threeLine, we get interrupted three times to go off and execute newLine.

For its part, newLine invokes the built-in method println, which causes yet another detour. Fortunately, Java is quite 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.

Actually, 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 Java 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.6 Parameters and arguments

Some of the built-in methods we have used have parameters, which are values that you provide to let the method do its job. For example, if you want to find the sine of a number, you have to indicate what the number is. Thus, sin takes a double value as a parameter. To print a string, you have to provide the string, which is why println takes a String as an parameter.

Some methods take more than one parameter, like pow, which takes two doubles, the base and the exponent.

Notice that in each of these cases we have to specify not only how many parameters there are, but also what type they are. So it shouldn't surprise you that when you write a class definition, the parameter list indicates the type of each parameter. For example:


  public static void printTwice (String phil) {
    System.out.println (phil);
    System.out.println (phil);
  }

This method takes a single parameter, named phil, that has type String. Whatever that parameter is (and at this point we have no idea what it is), it gets printed twice. I chose the name phil to suggest that the name you give a parameter is up to you, but in general you want to choose something more illustrative than phil.

In order to invoke this method, we have to provide a string. For example, we might have a main method like this:


  public static void main (String[] args) {
    printTwice ("Don't make me say this twice!");
  }

The string you provide is called an argument, and we say that the argument is passed to the method. In this case we are creating a string value that contains the text "Don't make me say this twice!" and passing that string as an argument to the printTwice where, contrary to its wishes, it will get printed twice.

Alternatively, if we had a string variable, we could use it as an argument instead:


  public static void main (String[] args) {
    String argument = "Never say never.");
    printTwice (argument);
  }

Notice something very important here: the name of the variable we pass as an argument (argument) has nothing to do with the name of the parameter (phil). Let me say that again:

The name of the variable we pass as an argument has nothing to do with the name of the parameter.

They can be the same or they can be different, but it is important to realize that they are not the same thing, except that they happen to have the same value (in this case the string "Never say never.").

The value you provide as an argument must have the same type as the parameter of the method you invoke. This rule is very important, but it often gets complicated in Java for two reasons:

  • There are some methods that can accept arguments with many different types. For example, you can send any type to print and println, and it will do the right thing no matter what. This sort of thing is an exception, though.
  • If you violate this rule, the compiler often generates a confusing error message. Instead of saying something like, "You are passing the wrong kind of argument to this method," it will probably say something to the effect that it could not find a method with that name that would accept an argument with that type. Once you have seen this error message a few times, though, you will figure out how to interpret it.

One last thing you should realize is that parameters and other variables only exist inside their own methods. Within the confines of main, there is no such thing as phil. If you try to use it, the compiler will complain. Similarly, inside printTwice there is no such thing as argument.



3.7 Composition

In the last chapter, I showed a few ways in which expressions and statements can be composed, meaning that you use one expression as part of another. Well, you can apply the same idea to methods and method invocations. 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 the cos method. (Notice that 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:


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

This statement finds the logarithm (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.8 Methods with results

You might have noticed by now that some of the methods we are using yield results, like the Math methods. But others don't, like the print methods. 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 with results," and we'll do it in a couple of chapters. I will 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 pretty good way to find out is to ask the compiler.



3.9 Glossary

floating-point:
A type of variable (or value) that can contain fractions as well as integers. In Java this type is called 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 some useful function. Methods may or may not take parameters, and may or may not produce a result.
parameter:
A piece of information you provide in order to invoke a method. Parameters are like variables in the sense that they contain values and have types.
argument:
A value that you provide when you invoke a method. This value must have the same type as the corresponding parameter.
invoke:
Cause a method to be executed.


to previous section to next section