Session 33: Bits and pieces

Parameters (Section 7.4)
    Parameters and arguments
    Parameter passing
    Passing objects (and arrays)
    Namespaces
Conditional operator (Section 7.3)
The switch statement (Section 8.1)
    Cases
    The necessity of break
    The default case
    When switch doesn't apply

Parameters

Textbook: Section 7.4

We've pretty much finished up everything, but there are some issues that we haven't really covered completely. Today I want to pick up three different issues, which I feel we should cover to finish with Java completely, but which I have put off because there was never time to do it. The first of these has to do with parameters.

Parameters and arguments

A parameter is a named value defined at the time a method is called. An argument is the particular value given into the method. This distinction is somewhat weird, but you'll find that people use it quite often.

For example, consider the following very simple program.

import csbsju.cs160.*;

public class Example {
    public static double square(double x) {
        return x * x;
    }
    public static void main(String[] args) {
        IO.println(square(10));
    }
}
In this program, the parameter of square() is x, and the argument passed into square within main() is 10 (which x represents for the duration of the function.

Parameter passing

Java uses a technique for passing parameters called call by value. That is, an argument's value is copied into the parameter variable, so that changes to the parameter variable do not affect anything except for the method itself. Consider the following program.

import csbsju.cs160.*;

public class Example {
    public static double square(double x) {
        x = x * x;
        return x;
    }
    public static void main(String[] args) {
        int y = 10;
        IO.println(square(y));
        IO.println(square(y));
    }
}
In this program, the variable y within main() holds 10. We pass that in as the value for the parameter x in square(), and that method immediately changes x to be its square, 100. But that change of x does not affect the value of y within main()! This is because the value 10 is copied into the local parameter variable x, and subsequent changes to x only affects that single local variable.

(Some languages pass parameters differently, and some give you a choice. Java isn't among them.)

Passing objects (and arrays)

Objects and arrays work similarly - but it's important to remember that the value of an object variable is actually a pointer to the actual object. So this memory address is what actually gets copied, not the entire object.

Consider the following program using the Account class we defined earlier.

import csbsju.cs160.*;

public class Example {
    public static void doubleAccount(Account acct) {
        acct.deposit(acct.getBalance());
    }
    public static void main(String[] args) {
        Account mine = new Account();
        mine.deposit(100.0);
        doubleAccount(mine);
        IO.println(mine.getBalance());
    }
}
Here, the mine variable's value is the argument to doubleAccount(), and so acct holds the same memory address as mine does. So, actually, acct and mine point to the same object. Thus, the deposit() method is actually also depositing into the object pointed to by mine.

Naturally, this doesn't extend to changes to the actual parameter value. Say we instead wrote doubleAccount() as follows.

    public static void doubleAccount(Account acct) {
        Account new_acct = new Account();
        new_acct.deposit(acct.getBalance());
        new_acct.deposit(acct.getBalance());
        acct = new_acct;
    }
This wouldn't work the same, because the final line here is changing acct to point to a different object. It doesn't actually affect the object pointed to by mine. The net effect of this method is simply to waste time - it creates a new account, but other methods have no way of accessing it as written.

Arrays work the same - which isn't too surprising, since arrays in Java are objects too. For example, you might write the following method to zero out an array.

    public static void zeroArray(int[] array) {
        for(int i = 0; i < array.length; i++) array[i] = 0;
    }

Namespaces

You've probably already figured this out, but it's important to know that each method's variables lives in its own space of names. You can't refer to a method's variables within another method.

In my earlier examples, I was careful to give different variables different names, even if they're in different methods. But that was just for clarity's sake. In the last example program, we could have used acct in place of mine, and it would work the same, since the acct within main() has no relationship to the acct within doubleAccount() - they're completely different variables.

Conditional operator

Textbook: Section 7.3

I wanted to quickly tell you about this new operator, since the textbook has covered it by this point. It's called the conditional operator. (Sometimes it's called the ternary operator, because it is peculiar in that it actually works with three arguments. The other operators are all either binary (like *) or unary (like ++).)

The conditional operator gives you a way of putting an if-then-else into an expression.

IO.println(m > n ? m : n);
Both the question mark and the colon are parts of the conditional operator. The part before the question mark is the condition. The computer will evaluate this to see whether the condition is true or false. If it is true, the computer evaluates the expression between the question mark and the colon, and the result is the value of the overall conditional expression. If the condition is false, then the computer evaluates the expression after the colon, and the result is the value of the overall conditional expression.

So in the above line, the conditional expression is m if m exceeds n and n otherwise. In other words, this line will display the larger of m and n.

In the order of operations, the conditional operator ranks just above the assignment operator. Usually this is what you want, but it's good practice to use parentheses here even when you don't need them, to make things more readable.

int max = m > n ? m : n;   // UGLY: don't do this.
int max = (m > n ? m : n); // much better

The conditional operator is never necessary when you program, but it's occassionally convenient. But you should be aware that it's extremely controversial. Many people say it should be banned. But many others use it. So it's worth knowing about.

The switch statement

Textbook: Section 8.1

We've seen else if popping up in programs with some frequency.

if(letter == 'A') {
    mingrade = 90;
} else if(letter == 'B') {
    mingrade = 80;
} else if(letter == 'C') {
    mingrade = 70;
} else if(letter == 'D') {
    mingrade = 60;
} else if(letter == 'F') {
    mingrade = 0;
}
The switch statement is an alternative to this.

Cases

A switch statement looks like the following.

switch(letter) {
case 'A':
    mingrade = 90;
    break;
case 'B':
    mingrade = 80;
    break;
case 'C':
    mingrade = 70;
    break;
case 'D':
    mingrade = 60;
    break;
case 'F':
    mingrade = 0;
    break;
}
You put an expression in the parentheses after switch (in this case, we want to switch based on the value of letter). Then, between the braces, we list what we want to do for different possible values of that expression. For each possible value, we write case, followed by the value, followed by a colon. Then we list what to do, followed by a break statement, telling the compiler that we've reached the end of our case. The break statement sends the computer to the next statement following the closing brace.

The necessity of break

It's important to remember break statement! If you leave it out, the computer will proceed into the next case. (This is called `falling through' - which is occasionally useful. Most often, however, it's an error.) Suppose, for example, we wrote the earlier switch statement as follows.

switch(letter) {
case 'A':
    mingrade = 90;
case 'B':
    mingrade = 80;
case 'C':
    mingrade = 70;
case 'D':
    mingrade = 60;
case 'F':
    mingrade = 0;
}
Then what would happen is that mingrade would become 0, regardless of what letter holds. If, for example, letter held 'A', the computer would set mingrade to be 90, then 80, then 70, then 60, and then finally 0.

This aspect of switch statements is usually irritating. But there is one particularly important case where this is useful: When you want multiple cases to do the same thing. For example, the following code converts a character into the value it represents as a hexadecimal value.

switch(letter) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
    value = letter - '0';
    break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
    value = letter - 'A';
    break;
}

The default case

You can also use the default label as a sort of ``else'' condition.

switch(letter) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
    value = letter - '0';
    break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
    value = letter - 'A';
    break;
default:
    throw new Exception("not a hexadecimal digit");
}
Here, if letter is anything other than the accepted characters, an exception is thrown.

When switch doesn't apply

Though switch is good to know about, its usefulness is severely hampered by the following two facts.