Operators

image

An operator is a punctuation mark that says to do something to two (or three) operands. An example is the expression “a * b”. The “*” is the multiplication operator, and “a” and “b” are the operands. Most of the operators in Java will be readily familiar to any programmer.

One unusual aspect is that the order of operand evaluation in Java is well-defined. For many older languages, the order of evaluation was been deliberately left unspecified to allow the compiler-writer more freedom. In other words, in C and C++, the following operands can be evaluated and added together in any order:

i + myArray[i] + functionCall();

The function may be called before, during (on adventurous multiprocessing hardware), or after the array reference is evaluated, and the additions may be executed in any order. If the functionCall() adjusts the value of i, the overall result depends on the order of evaluation. The trade-off is that some programs give different results depending on the order of evaluation. A professional programmer would consider such programs to be badly written, but they exist nonetheless.

The order of evaluation was left unspecified in earlier languages so that compiler-writers could reorder operations to optimize register use. Java makes the trade-off in a different place. It recognizes that getting consistent results on all computer systems is a much more important goal than getting varying results a trifle faster on one system. In practice, the opportunities for speeding up expression evaluation through reordering operands seem to be quite limited in many programs. As processor speed and cost improve, it is appropriate that modern languages optimize for programmer sanity instead of performance.

Java specifies not just left-to-right operand evaluation, but the order of everything else, too, such as:

  • The left operand is evaluated before the right operand of a binary operator. This is true even for the assignment operator, which must evaluate the left operand (where the result will be stored) fully before starting any part of evaluating the right operand (what the result is).

  • An array access has this general form:

    ArrayInstance[index1][indexN]

    The ArrayInstance expression before the square brackets is fully evaluated before any part of the indexes in square brackets are evaluated. This can make a difference if any of these expressions involves calling a method that has side effects, such as changing the value of an index expression that has not yet been evaluated. The indexes are evaluated one by one from left to right.

  • A method call for an object has this general form:

    objectInstance.methodName(arguments);

    The objectInstance is fully evaluated before the methodName and arguments. This can make a difference if the objectInstance is the return value from a method that has side effects, such as changing the value of one of the arguments. Any arguments are evaluated one by one from left to right.

The Java Language Specification 2nd Edition (James Gosling, Bill Joy, and Guy L. Steele, Addison-Wesley, 2000) uses the phrase, “Java guarantees that the operands to operators appear to be evaluated from left-to-right.” This is an escape clause that allows clever compiler-writers to do brilliant optimizations, as long as the appearance of left-to-right evaluation is maintained.

For example, if some subexpressions is repeated in the same basic block, like this subexpression “x+10” on both the left and right hand side of the assignment,

a[ x*10 + y ] = x*10;

a clever compiler-writer might be able to arrange for the reuse of the first time it was calculated, instead of going through all the steps to calculate it again. In general, because of complications involving infinity and not-a-number (NaN) results, floating-point operands cannot be trivially reordered.

Note that the usual operator precedence still applies. In an expression like the following, the multiplication is always done before the addition.

b + c * d

The Java order of evaluation says that for all binary (two argument) operators, the left operand is always fully evaluated before the right operand. Therefore, the operand “b” in the previous example must be evaluated before the multiplication is done (because the multiplied result is the right operand to the addition).

Left-to-right evaluation means in practice that all operands in an expression (if they are evaluated at all) are evaluated in the left-to-right order in which they are written down on a page. Sometimes an evaluated result must be stored while a higher precedence operation is performed. Although The Java Language Specification only talks about the apparent order of evaluation of operands to individual operators, this is a necessary consequence of the rules.

Java operators

The Java operators and their precedence are shown in Table 7-3. The arithmetic operators are undoubtedly familiar to the reader. We'll outline the other operators in the next section.

Table 7-3. Java operators and their precedence

Symbol

Note

Precedence (highest number= highest precedence)

COFFEEPOT Property (see Associativity)

++ --

pre-increment, decrement

16

right

++ --

post-increment, decrement

15

left

~

flip the bits of an integer

14

right

!

logical not (reverse a boolean)

14

right

- +

arithmetic negation, plus

14

right

( typename )

type conversion (cast)

13

right

* / %

multiplicative operators

12

left

- +

additive operators

11

left

<< >> >>>

left and right bitwise shift

10

left

instanceof <

relational operators

<= > >=

9

left

== !=

equality operators

8

left

&

bitwise and

7

left

^

bitwise exclusive or

6

left

|

bitwise inclusive or

5

left

&&

conditional and

4

left

||

conditional or

3

left

? :

conditional operator

2

right

= *= /= %=

assignment operators

1

right

+= -=

<<= >>= >>>=

&= ^= |=

Char is considered to be an arithmetic type that only ever holds positive values. It is a 16-bit integer whose bit patterns run from 0 to 0xFFFF, which is 65535 in base ten.

The ++ and -- operators

The pre- and post-increment and decrement operators are shorthand for the common operation of adding or subtracting one from an arithmetic type. You write the operator next to the operand, and the variable is adjusted by one. These are all equivalent ways of increasing the value of i by one:

i = i+1; // expression assignment
i += 1;  // assignment operator
++i;     // pre-increment
i++;     // post-increment

The pre- and post-increment operators can appear in the middle of an expression, whereas the two assignments cannot. Pre- and post- differ if you bury the operator in the middle of a larger expression, like this:

int result = myArray[++i]; // pre-increment

The pre-increment here will increment i before using it as the index. The post-increment version will use the current value of i, and after it has been used, add one to it. It makes a very compact notation. Pre- and post-decrement operators (--i) work in a similar way.

The % and / operators

The division operator “/” is regular division on integer types and floating point types. Integer division just cuts off any decimal part, so -9/2 is -4.5 which is cut to -4. This is also (less meaningfully) termed “rounding towards zero.”

The remainder operator “%” means “what is left over after dividing by the right operand a whole number of times.” Thus, -7%2 is -1. This is because -7 divided by 2 is -3, with -1 left over.

Some people call “%” the modulus operator, so “-7 % 2” can be read as “-7 modulo 2”. If you have trouble remembering what modulo does, it may help to recall that all integer arithmetic in Java is modular, meaning that the answer is modulo the range. If working with 32 bits, the answer is that part of the mathematically correct answer that fits in a 32-bit range. If working with 64 bits, the answer is that part of the answer that fits in 64 bits. If doing “-8 modulo 3”, the answer is that remainder part of the division answer that fits in 3, i.e., -2.

The equality shown below is true for division and remainder on integer types:

(x / y) * y + x%y == x

If you need to work out what sign some remainder will have, just plug the values into that formula.

The << >> and >>> operators

In Java the “>>” operator does an arithmetic or signed shift right, meaning that the sign bit is propagated. In C, it has always been implementation-defined whether this was a logical shift (fill with 0 bits) or an arithmetic shift (fill with copies of the sign bit). This occasionally caused grief, as programmers discovered the implementation dependency when debugging or porting a system. Here's how you use the operator in Java:

int eighth = x >> 3; // shift right 3 times same as div by 8

One Java operator not in other languages is “>>>” which means “shift right and zero fill” or “unsigned shift” (do not propagate the sign bit). The “>>>” operator is not very useful in practice. It works as expected on numbers of canonical size, ints, and longs.

It is broken, however, for short and byte, because negative operands of these types are promoted to int with sign propagation before the shift takes place, leaving bits 7-or-15 to 31 as ones. The zero fill thus starts at bit 31, and doesn't show up in the original byte or short. So >>> is completely useless for non-canonical types, and best avoided.

If you want to do unsigned shift on a short or a byte, mask the bits you want and use >>.

byte b = -1;
b = (byte)((b & 0xff) >> 4);

That way programs won't mysteriously stop working when someone changes a type from int to short.

The instanceof Operator

The other new operator is instanceof. We've seen in several places how a class can be set up as a subclass of another class. The instanceof operator is used with superclasses to tell if you have a particular subclass object. For example, we may see the following:

class vehicle { /* some code */ }
class car extends vehicle { /* some code */ }
class convertible extends car { /* some code */ }

vehicle v; /* some code */ }
convertible c;
if (v instanceof convertible)
     c = (convertible) v;

The instanceof operator returns true or false depending on whether the object is actually of the class you mention. The operation is often followed by a statement that casts the object from the base type to the subclass. Before attempting the cast, instanceof lets us check that it is valid. There is more about this in the next chapter.

The & | and ^ operators

The “&” operator takes two boolean operands, or two integer operands. It always evaluates both operands. For booleans, it ANDs the operands, producing a boolean result. For integer types, it bitwise ANDs the operands, producing a result that is the promoted type of the operands (type long if you are working with longs, otherwise int).

To illustrate the & operator and the >> shift operator, you can get the two nibbles out of a byte with this code.

byte byteMe = someValue;
byte loNibble = (byte) (byteMe & 0x0F);

The low nibble (least significant 4 bits) are extracted by ANDing the byte with a literal that has the lowest four bits set. The high nibble (most significant 4 bits) are extracted by shifting the byte right four bits (to move the bits to the low end), and doing the AND again.

byte hiNibble = (byte) ((byteMe >> 4) & 0x0F);

If that looks like a lot of casting to (byte), the section called idening and Narrowing Conversions on page 152 explains why it is needed.

“|” is the corresponding bitwise OR operation.

“^” is the corresponding bitwise XOR operation. In case you're wondering XOR is used in some graphics operations, and it's included mainly because it's cheap to do so.

The && and || Operators

The “&&” is a conditional AND that takes only boolean operands. It avoids evaluating its second operand if possible. Consider the expression a && b. If a evaluates to false, the overall AND result must be false and the b operand is not evaluated. This is sometimes called short-circuited evaluation. “||” is the corresponding short-circuited OR operation. There is no short-circuited XOR operation.

You often use a short-circuited operation to check if a variable points to an object before invoking an instance method on it.

if ((myString != null) && (myString.equals("credit sale")) { /*code*/

In the example above, if the variable myString is null, then the second half of the expression is skipped. Here's a mnemonic you might find helpful: the longer operators “&&” or “||” try to shorten themselves by not evaluating the second operator if they can.

The ? : operator

The “? :” operator is unusual in that it is a ternary or three-operand operator. It is best understood by comparing it to an equivalent if statement:

if (someCondition)     truePart          else   falsePart
    someCondition ?    trueExpression       :    falseExpression

The conditional operator can appear in the middle of an expression, whereas an if statement cannot. The value of the expression is either the true expression or the false expression. Only one of the two expressions is evaluated. If you do use this operator, don't nest one inside another, as it quickly becomes impossible to follow. This example of ? is from the Java run-time library:

int maxValue = (a >= b) ? a : b;

The parentheses are not required, but they make the code more legible.

The assignment operators

Assignment operators are another notational shortcut. They are a combination of an assignment and an operation where the same variable is the left operand and the place to store the result. I like to think of it as “factoring out” in some sense, the common operand. For example, these two lines are equivalent:

i = i + 4;   // i gets increased by 4.

i += 4;      // factored out the common "i".

There are assignment operator versions of all the arithmetic, shifting, and bit-twiddling operators where the same variable is the left operand and the place to store the result. Here's another example:

ypoints[i] *= deltaY;

The assignment operator “i += something” is short for “i = i + something” with one subtle difference. When you use the assignment operator, the “i” part of it is only evaluated once. In the long hand form, “i” appears in the statement twice, and is evaluated twice. Say the expression wasn't “i” but was an array reference like this:

points[j++] += deltaY; // assignment operator version

Writing that longhand gives:

points[j++] = points[j++] + deltaY; // long hand version

A different element is referenced in the second points[j++] expression in the long hand version because j was bumped up by the post increment in the first points[j++] expression!

Assignment operators are carried over from C into Java, where they were originally intended to help the compiler-writer generate efficient code by leaving off a repetition of one operand. That way it was trivial to identify and reuse quantities that were already in a register. Assignment operators are most useful when the operand is a long complicated name. It saves you from having to repeat it (possibly making a typo) and it saves the maintenance programmer from having to inspect it carefully to make certain it is the same operand.

The comma operator is gone

Finally, note that Java cut back on the use of the obscure comma operator. Even if you're quite an experienced C programmer, you might never have seen the comma operator, as it was rarely used. The only place it occurs in Java is in “for” loops. The comma allows you to put several expressions (separated by commas) into each clause of a “for” loop.

for (i=0, j=0; i<10; i++, j++)

It's not actually counted as an operator in Java, so it doesn't appear in Java operators and their precedence. It's treated as an aspect of the for statement.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.212.71