|
|
|
This chapter presented 16 Java operations. Their evaluation precedence is shown in Table 3.6. Higher-precedence operators, the ones that are evaluated first, appear at the top.
|
Category |
Operators |
|---|---|
|
Unary |
+ - ! ~ ++ -- |
|
Higher-precedence arithmetic |
* / % |
|
Lower-precedence arithmetic |
+ - |
|
Shift |
>> << >>> |
|
Bitwise |
& ^ |
|
Short circuit |
&& |
|
|
|
|
|
|
| Note |
The solutions to these exercises are in Appendix B. |
What happens when a comment appears inside a literal string? (Recall from Chapter 2 that a literal string is a run of text
System.out.println("A /* Did this print? */ Z");
Write a program that includes this line. Does the program print the entire literal string, or does it just print "A Z"?
What is the value of ~100? What is the value of ~-100? First try to figure it out, and then write a program to print out the values. (Hint: You can figure it out without using pen and paper if you remember something that was discussed in Chapter 2.)
Write a program that prints out the following values:
32 << 3
32 >> 3
32 >>> 3
-32 << 3
-32 >> 3
-32 >>> 3
What are the values of the following expressions? First do the
false & ((true^(true&(false!(truefalse))))^true) true (true^false^false^true&(false!(true&true)))
The following expression looks innocent:
boolean b = (x == 0) (10/x > 3);
You can assume x is an int. Write a program that prints out the value of this expression for the following values of x: 5, 2, 0. What goes wrong? (You will see a failure message that you might not be familiar with, because we have not introduced it yet. Don't worry – just try to understand the general concept.) How can you make the code more robust by adding a single character to the expression?
The 32-bit float type is wider than the 64-bit long type. How can a 32-bit type be wider than a 64-bit type?
Write a program that contains the following two lines:
byte b = 6; byte b1 = -b;
What happens when you try to compile the program?
|
|
|
|
|
|
S o far, all of the Java applications we have seen have been linear: The processing has proceeded line by line, from the start through the end of the block that begins public static void main(String[] args) .
Such applications don't really take full advantage of your computer's capability. In fact, with linear code your computer is not much more than an expensive calculator. The topics in the following two chapters will begin to branch out—and so will the paths of execution through the programs we will study. Instead of proceeding line by line, we will see how to make the execution
This chapter will look at methods, which are
|
|
|
|
|
|
The
1. public class NoMethods 2. { 3. public static void main(String[] args) 4. { 5. int n = 5; 6. int n5th = n*n*n*n*n; 7. System.out.println(n + " >=> " + n5th); 8. n = 6; 9. n5th = n*n*n*n*n; 10. System.out.println(n + " >=> " + n5th); 11. n = 7; 12. n5th = n*n*n*n*n; 13. System.out.println(n + " >=> " + n5th); 14. n = 8; 15. n5th = n*n*n*n*n; 16. System.out.println(n + " >=> " + n5th); 17. n = 9; 18. n5th = n*n*n*n*n; 19. System.out.println(n + " >=> " + n5th); 20. } 21. }
The application's output is
5 >=> 3125 6 >=> 7776 7 >=> 16807 8 >=> 32768 9 >=> 59049
When you look at the source code, you might get the feeling that life ought to be better than this. The application has a lot of repetition… and aren't computers supposed to be good at eliminating repetitive
It would be great to have a piece of subordinate code that could compute the 5 th power of anything . Of course, there would have to be a way to tell the subordinate code what number to work with. The following application does just that, using methods:
1. public class UsesMethods 2. { 3. public static void main(String[] args) 4. { 5. int n = 5; 6. int n5th = toThe5th(n); 7. System.out.println(n + " >=> " + n5th); 8. n = 6; 9. n5th = toThe5th(n); 10. System.out.println(n + " >=> " + n5th); 11. n = 7; 12. n5th = toThe5th(n); 13. System.out.println(n + " >=> " + n5th); 14. n = 8; 15. n5th = toThe5th(n); 16. System.out.println(n + " >=> " + n5th); 17. n = 9; 18. n5th = toThe5th(n); 19. System.out.println(n + " >=> " + n5th); 20. } 21. 22. static int toThe5th(int x) 23. { 24. int result = x * x * x * x * x; 25. return result; 26. } 27. }
The application's output is the same as the output from the previous version.
The code from lines 22-26
The general format of a method declaration is
Optional_modifiers Return_type Name(Optional_ arguments)
The only mandatory
You have already been patiently tolerating the unexplained presence of the
static
modifier in every application we have
We'll look at the return type in a moment. Let's move now to the method name. The rules for the name are the same as those for variable
As with variable names, you have a broad choice. You should pick the name that does the best possible job of describing what the method does. When the name appears outside the method, as in lines 6, 9, 12, 15, and 18 of this example, the
Note that in the lines where the method is called, the method call (the name followed by something parenthetical) is used in a context where you would expect to see a value. Until now, the right-hand side of an assignment has been either a literal, a variable, or an arithmetic or boolean expression
The arguments are the method's inputs. In this example, the argument list is
int x
. This means that the method has one input, whose type is int. Within the body of the method, that input will be called
x
. When the method runs, the actual value of
x
will be whatever the caller wants. The caller specifies an input value by
The return type in the method declaration tells the type of the return value. The returning of a value happens in line 25, where we see the return keyword. This causes the path of execution to return to the caller line. The value following return is the return value. In this example, the return value is result , which is the 5 th power of the argument.
The toThe5th method in the previous example took a single argument, so within the parentheses in the method declaration, we saw a single type ( int ) followed by a single name ( x ). You can create methods with an arbitrarily number of arguments of arbitrary types. To do this, just write a declaration with the following format:
Mods Ret_type Name(type0 arg0, type1 arg1, type2 arg2 ...)
| Note |
Note that the numbering of the arguments starts at 0, rather than 1. Whenever you see someone do this, you can be certain that they work in the computer field, where counting from 0 is conventional. We already saw this in Chapter 1, where SimCom's memory addresses started at 0. We will see it again in Chapter 6, "Arrays." It is important to get into the habit of counting from 0 as soon as possible, even though there is a
|
The following method takes 2 arguments:
static double hypotSquared(double leg0, double leg1) { return leg0*leg0 + leg1*leg1; }
Recall that in any arithmetic expression, multiplication takes precedence over addition, so the method really does return the square of the
The MethodLab animated illustration demonstrates the passing of arguments and the returning of return values. To run the program, type
java methods.MethodLab
. The display
Figure 4.1:
MethodLab
Click on the Run button to see the animation. The black lines
Figure 4.2:
MethodLab after animating
You can customize MethodLab by typing any integer value you like into the text fields for a and b . You can also enter any numeric formula for the value of z , which becomes the return value. Try the formula in the preceding example. Since this method's arguments are called x and y , the formula should be
x*x + y*y
With this formula, try running MethodLab with a = 3 and b = 4. Try again with a = 12 and b = 5. These combinations are the only small integers that represent
A method's argument list can be arbitrarily long. At times you might even want a method with no arguments at all. In that case, the method's declaration has an empty pair of parentheses after the name, and the caller
float f = sayHello(); … static float sayHello() { System.out.println("Hello"); return 3.14159f; }
The important thing is that when you call a method, the call should have the same number of arguments as the method declaration, and the types of the arguments passed by the caller should be compatible with the types in the method declaration. This is subtly different from saying that the caller's argument types should exactly match the types in the method declaration. Recall from Chapter 3 that a value can be assigned to a variable of a different type, provided the new type is wider than the old type. Figure 4.3 (first shown in Chapter 3) shows the width relationships among the numeric primitive types.
Figure 4.3:
Numeric type widths
The rule for passing method arguments is similar to the rule for assignment: You can pass an argument whose type is different from the type declared by the method, provided the type declared by the method is wider than the type that you pass . Recall that a type is wider than another type if you can get from the first type to the second type by following the arrows in Figure 4.3.
For example, suppose a method has the following declaration:
static char abcde(long wayToGo)
This method can be called with a long argument, or with any argument whose type is narrower than long: byte, short, char, or int.
At times, you might want to create a method that doesn't return anything. The method might print out a message, display a dialog box, or store a value in a file. In cases such as these, it is difficult to think of any value that the method could meaningfully return, and concocting a return value just for the sake of having one would not contribute to the quality of the program. (A basic principle of writing
Suppose you want a method that prints a number along with a message. Since no return value is needed or relevant, replace the return type in the declaration with the word void :
static void printPretty(int x) { System.out.println("x = " + x); }
Note the absence of a return statement. A void method runs until it executes its last line. Then it returns automatically. Optionally, you can add a return statement with no value at the end of the method:
static void printPretty(int x) { System.out.println("x = " + x); return; }
Here the return statement doesn't contribute to program execution or readability (we already know that the method returns when it hits bottom), but later we will see cases where explicitly saying return can be useful.
A call to a method with a non-void return type can be used anywhere that a variable or literal of the same type can be used: as the right-hand side of an assignment, as an operand in an operation, or even as an argument of another method call. By contrast, a call to a void method has no type. So if method iAmVoid is void, you could not say int z = iAmVoid(); because there would be no value to assign to z . When you call a void method, you just want it to do its thing, so you make the call all by itself, followed by a semicolon, like this:
iAmVoid();
If a method changes the state of the program or the computer in any way (other than returning the return value), the change is called a side effect . Clearly, when you call a void method, you do so because you are interested in a side effect. (In this example, the side effect was the printing of the message.) Sometimes you might want to call a non-void method, not because you are interested in the return value, but because you want the side effect. In that case, you can just call the method as if it were void.
For example, the following method both prints out and returns hypotenuse squared:
static int vocalHypotSquared(int a, int b) { int hSquared = a*a + b*b; System.out.println("h-squared = " + hSquared); return hSquared; }
If you just wanted to print out the message, you could call the method like this, ignoring the return value:
vocalHypotSquared(5, 12);
Polymorphism comes from the Greek for "many forms." It is one of several five-syllable words pertaining to object-oriented programming. In our context, it means that a method can have one name but many forms. In other words, you can define multiple methods with the same name.
At first, this might seem
For example, the following two methods could appear in the same program:
static int getMass(int n) { … } static int getMass(double a, char c) { … }
Here, getMass() is legitimately overloaded. However, the following two methods could not appear in the same program:
static int getMass(int n) { … } static int getMass(int x) { … }
The argument names are different, but that doesn't help. Both method versions have the same name, and each version takes a single argument of type int, so the compilation will fail. If the argument types were different (for example, if the argument of getMass() had type long or byte), the code would compile without error.
Methods can be called from anywhere – even from other methods. In fact, any complicated program is likely to consist of methods that call other methods that call other methods, and so on, to many levels of depth. For example, you might have a method that prints out two values:
static void print2Vals(int val0, int val1) { System.out.println(val0 + " and " + val1); }
Now what if you want a method that prints out the cubes of its two arguments? You might do it as follows:
static void print2Cubes(int val0, int val1) { int val0Cubed = val0*val0*val0; int val1Cubed = val1*val1*val1; System.out.println(val0Cubed + " and " + val1Cubed); }
Since you already have the print2Vals method, you can rewrite print2Cubes as follows:
static void print2Cubes(int val0, int val1) { int val0Cubed = val0*val0*val0; int val1Cubed = val1*val1*val1; print2Vals(val0Cubed, val1Cubed); }
Now you have a method, ( print2Cubes ) that calls another method ( print2Vals ). You can rewrite print2Cubes to be even more terse, as follows:
static void print2Cubes(int val0, int val1) { print2Vals(val0*val0*val0, val1*val1*val1); }
Since the multiplication is repetitious, you can also introduce a new method:
static int nCubed(int n) { return n*n*n; }
Now print2Cubes becomes
static void print2Cubes(int val0, int val1) { print2Vals(nCubed(val0), nCubed(val1)); }
Now you have a method, (
print2Cubes
) that consists of a single call to another method (
print2Vals
), and that call's arguments are both
In formal computer terminology, we say that Java passes by value . This is just another way to say that methods get copies of their arguments and have no access to the original values. The alternative is called passing by reference , where methods work with the caller's data and not with copies. The latter is a perfectly valid way to design a language; it's just not the way Java does it.
When a caller calls a method and the flow detours into the method's body, the JVM copies the argument values provided by the caller and gives the copies to the method. This means that if the method alters its arguments, the alteration has no effect on the caller.
Consider the following method:
static void print3x(int x) { x = 3*x; System.out.println("3 times x = " + x); }
The method triples its argument. You might
int z = 10; print3x(z); System.out.println("Now z is " + z);
Does this code print "Now z is 10" or "Now z is 30"? If the method has access to the actual data passed in by the caller, the code should print out "Now z is 30". However, the code actually prints "Now z is 10" because the method triples its own private copy, not touching the
caller's version. After the method returns, the memory used for storing the method's copy is recycled. The method's copy does not survive after the method returns.
A program is, in large part, a collection of methods that call other methods. These other methods call still other methods, and so on.
Within an application, methods can appear in any order. Once again, you are in a situation where your choices can make a program either easier or harder for others to read. It makes sense to put
|
|
|