Chapter 3

Using Java Operators and Conditional Logic

The fundamental unit of work in a Java program is the statement. To write useful statements, you must learn how to operate on your data. To write accurate statements, you must also learn the rules of the operation themselves. There is no substitute for understanding Java’s operators very well. Your primary reward is a big one: You’ll spend less time getting your code to do the right thing. Once your statements evaluate correctly, you’ll need to test them and make sure all the decisions based on those statements evaluate as they should.

In this chapter, we’ll cover the following topics:

  • Using Java operators
  • Using parentheses to control operators
  • Understanding object equality
  • Using if and if/else statements
  • Using a switch statement

Using Java Operators

It’s important to use clear, unambiguous terms as much as possible when describing operators because your code will rely heavily on using them correctly. We will first sort them into categories that make it easier to remember them. We’ll discuss the basics of each group, paying particular attention to operators that are unfamiliar to a programmer just getting started.

In this book, we won’t concern ourselves with the many technical subtleties that influence the way some operators work. Defining the rules of machine computation is a highly technical, detailed, and exacting discipline, in part because every valid case for using an operator must have a known, unambiguous result.

As a beginning programmer, you don’t have to apply the same rigor to using these operators right away. A foundation in basic math and logic is sufficient to understand how most of them work. It is important to observe, however, that behind virtually every operator is some subtlety of implementation, and somewhere down the road you will confront them.


You can download or browse the Java Language Specification at http://docs.oracle.com/javase/specs/.

Every programming language must document how its operators work so that programmers know what to expect from testing and regular use. For Java, the authoritative document for these definitions is called the Java Language Specification, or JLS. It defines the meaning for each and every element allowed in the language, including the following elements:

  • Reserved words
  • Allowed types and values
  • Legal names for classes, fields, and methods
  • Variable declaration and initialization
  • Exceptions

And it covers many other topics you’re not yet acquainted with. For programs to run reliably on a machine, every code element has to be specified so there’s no confusion over its role. Sometimes that leads to usage that seems ambiguous until you cover the rules carefully.

For example, the JLS distinguishes between what it calls separators—characters that signify the end of one program element and the beginning of another—and what it calls operators—characters that symbolize a complex action. You’ve seen the period (or dot) used to separate an object reference from a field or method. You’ll also see it used to separate the whole and fractional parts of a floating-point value. You might not once think about, even years after using Java, but somewhere it has to be defined how the compiler is going to tell the difference.

Or consider the asterisk. In a declaration like import java.util.*, it globs, or expands to a list of, all the classes in the package. In an expression, it is of course a multiplication operator. Again, it’s context that determines the meaning and the JLS that sets the rules for interpretation.

The characters that Java defines as separators are as follows:

( ) { } [ ] ; , .

We discuss arrays in Chapter 4, “Using Java Arrays.”

Parentheses, braces, and brackets are used as containers, as you’d expect. Braces contain a block of code. Brackets are used for declaring and accessing arrays. The semicolon is a statement terminator. Commas are used in a few different contexts to list things, such as multiple parameters in a method call. The dot also separates package names from class names. Used between an object reference and one of its members, it is actually considered an access operator. Once again, there’s no way to know that without consulting the JLS.

Parentheses have several uses too. You’ve already used them with method calls to hold a list of parameters (zero or more), but they have other operational meanings as well. I’ll describe those and other separator meanings as they occur in this chapter.

The JLS, section 3.12, lists 37 tokens as operators. The JLS does not, surprisingly, provide a table for these operators. To understand the reasons why, you need to know more about operators and how they are defined. Table 3-1 lists the tokens.

Table 3-1: Java operator tokens

OperatorsPurposeAssociativity
[].()Array accessObject accessMethod invocationLeft to right
++ --++ --Post-increment/decrement
Pre-increment/decrement
Left to right
Right to left
+ -Unary plus/minusRight to left
! ~Logical/bitwise NOTRight to left
()newCastingObject creationRight to left
* / % + -+Arithmetic
String concatenation
Left to right
<< >> >>>Bit shiftingLeft to right
< <= > >=instanceofRelational test
Type test
Left to right
== !=Equality testLeft to right
& ^ |Bitwise operationLeft to right
&& ||?:Conditional operationLeft to right
Right to left
= += -+ *= /+ %=&= ^= |=<<= >>= >>>=AssignmentRight to left

The tokens listed include some familiar figures, like the arithmetic symbols you’d see on a calculator. Some you might see on a programmable calculator. Others you will see only in a general-purpose programming language, and still one or two more are specific to Java. The second column describes the nature of each group; I’ll explain each of these as we work our way down the list. The third column describes how these operators associate when they are used in the same expression.

This list also includes two Java operators that are also language keywords, new and instanceof. The new keyword is used to prompt object creation. The instanceof keyword checks the type of an object reference. You’ll see an example of its use later in the chapter.

Understanding Operator Types and Properties

There are several different properties that help classify and describe the roles of our operators. Detailing each operator by its properties goes beyond the scope of this guide, but learning the primary concepts will make it easier for you to break down and absorb the set shown in Table 3-1.

One way we describe an operator is by how many operands it takes. It’s important for the compiler to know, when it sees an operator in context, which of the surrounding elements are intended as operands to that operator. Each Java operator takes one, two, or three operands and is described as unary, binary, or ternary, respectively. Careful study of the table reveals that some symbols have more than one meaning. The designers did this to reduce the total number of characters needed to represent operator actions. Also, many of these tokens have a meaning that carries to other languages. Sometimes it’s the number of operands that tells us which operator is intended.

There are two unary operators (++ and --) that have both a prefix and a postfix behavior. That is, they will modify an operand either before or after it has been evaluated in an expression. You might wonder how important it is to have both options. I can’t recall the last time specifying a prefix or postfix increment spelled the difference between a groundbreaking new program and one that just didn’t work. However, if you do need to express exactly when you are reading an integral value and when you are modifying it, these operators give you way to do it.

Some operators work in a bitwise fashion. That is, they exploit the way Java stores an operand in memory to manipulate it. Some veteran programmers, in particular ones who understand machine code well, prefer bitwise expressions because they are terse and efficient. For newcomers to programming, it is not always obvious on first sight when or why you’d use them, much less what another programmer intended by using them. When code efficiency is important, bitwise operations can be useful but also cryptic.

Some operators that are technically bitwise are instead called shifting operators. These operators move a value’s bits to the left or right of their current position in storage. Consider the value two, which is represented in four bits as 0010. If you shift the bits to the left by one and fill in the low bit with a zero, it becomes 0100, or four. Shifting left amounts to multiplying by two.

The last category I’ll call out here are logical operators. Also known as truth functions, a logical operator evaluates Boolean operands. Logical operators can be used to link two disjoint statements (such as “if it is raining and if I have a hat,” or “if I play Tetris until sunrise or if I study for my final”) to create a compound truth expression. You can use them to determine if several statements are collectively true or if at least one of them is true. Logical operators help us make decisions in a program based on their outcomes.

If the terms described previously are unfamiliar to you, here’s a word of advice. Take time to research them. Understand how they are commonly used. Digesting this information may seem far removed from writing interesting programs. However, if you’re serious about programming, you’ll find you spend much more time reading code than writing it, and naturally that means reading code other people have written. Understanding any code at this level is essential preparation to becoming a more fluent programmer.


Interpreting Overloaded Operator Symbols
Java uses the same tokens to represent three bitwise and logical operators (&, ^, and |). They are all binary operators, so we have to look at the type of the operands to know which operation is intended. If both operands are integers, the operation is bitwise. If both operands are boolean primitives (or object references), the operation is logical. If you provide operands of either type, the compiler will complain.

Precedence and Associativity

The remaining operators described here should be familiar to anyone who has used a calculator, so I add two more terms to our vocabulary that will help you understand how all operators in one expression relate to each other: precedence and associativity. We learn these terms in arithmetic and they apply more or less the same way to language operators but with a couple differences.

In arithmetic, precedence establishes the order of evaluation for multiple operators in one expression. Without precedence, the result of an expression like the following one below is potentially ambiguous:

3 + 4 × 5

Should this statement evaluate to 35 (by adding first and multiplying second) or 23 (the reverse)? In math, we’re taught that multiplication precedes addition in the order of evaluation, so the answer is 23.

Java applies the same principles of infix notation, in which operators are positioned between their operands. We need rules, therefore, to understand not only which operands belong to which operators, in a compound expression, but also which operators precede others. Not all programming languages do this. Languages like FORTH, for example, use a postfix notation style (also called Reverse Polish Notation) that places the operator after its operands. The previous expression would look like this in a FORTH statement:

5 4 × . 3 +

That is, we input the values 4 and 5, then multiply them. The dot in FORTH retrieves the last value stored (the product of the last operation) to use as input. We input the value 3 and add them.

A computer can process expressions in postfix notation immediately and in the order given, so there’s no precedence to work out. It’s a more efficient scheme. It’s also unfamiliar to anyone trained on infix notation. But much like trying to get the United States to prefer the metric scale of measure, getting programmers to prefer postfix notation is an uphill battle. We’ll need precedence rules for the foreseeable future.

But precedence isn’t sufficient to resolve potential ambiguity. Some operators, such as multiplication and division, have the same precedence. They are, after all, just inverse forms of the same operational process. How should Java evaluate an expression that contains two or more of these operators? Consider the following expression:

3 + 4 × 14 ÷ 7 ÷ 7 - 8

Precedence alone doesn’t yield a single reasonable interpretation. Say we decided that multiplying and dividing from left to right was the most efficient approach: We’d compute -4 (after truncating to get an integral value). If we processed these operations from right to left, we’d compute 51.

The interpretation that prevails is defined by associativity, or grouping rules. In most cases, operators of equal precedence associate from left to right, so the agreed-upon answer here is -4. Some operators do associate from right to left, however. We won’t concern ourselves with the reasons why. They appear in Table 3-1, and we’ll point them out as we go.

It is of course good practice (and a courtesy) to write the simplest readable expressions you can. At the same time, you should keep the rules of precedence and associativity close at hand when you need them. Relying on memory alone for the occasions you have to interpret someone else’s long, complicated or highly technical expression is a bad bet.

certobjective.eps

In support of that advice, I’ll list and discuss the Java operators in their order of precedence. The JLS lists operators by strict precedence because it has to, but many of the fine distinctions between levels of precedence have to make aren’t significant to a beginning programmer. To make the business of learning them all easier, I’ve defined 11 categories:

1. Separators
2. Postfix/Prefix
3. Unary
4. Multiplicative
5. Additive
6. Shifting
7. Relational
8. Equality
9. Bitwise/Logical
10. Conditional
11. Assignment

I’ve also fashioned this list so you can use a mnemonic device to help memorize them. Here’s one: Some PUMAS Relate as Equals to BLack CAts. Or devise your own. Once you get the categories down, there are details for operators in one category you’ll want to commit to memory too. Get the categories down first and absorbing the smaller details will get easier.

Using Separators

Separators take precedence over all other operators. This category, however, does not include braces (for code bodies), the semicolon (for statement terminators), or the comma (for listing multiple elements). None of these symbols have meaning to any operand, and to parse the smaller units (statements and expressions) we must have to sort them out first.


In Java, the comma may separate certain operands but it never operates on them.

Brackets (for array operations), parentheses (for method calls), and dots (member access) are different because they separate the pieces that make up one part of an expression. As the grouping agent for this “subatomic” level, they will generally evaluate first in an expression. There are some cases where they are ignored, such as when short-circuit operators are in play. I’ll explain this effect when we get to them later in this section.

Using Postfix/Prefix Operators

There are two operators, ++ and --, that can increment or decrement an integral variable by one. If the operator prefixes the variable, the value changes before the expression is evaluated. If the operator follows the variable, its value changes after evaluation.

In practice, the high precedence of these operators means the difference between them can be minor or even unnoticeable. Consider the following expression:

i++ + ++i

If i is initialized to zero, this expression will evaluate to 2. To get a different result, the precedence of the addition operator would have to interpose itself between the other two.


Because prefix operators have different precedence and associativity, some people group them with the unary operators.

It’s convenient to discuss postfix and prefix operators together because they do the same kind of work, but they also have important differences. One, postfix operators take precedence over prefix operators. Two, postfix operators associate left to right, while prefix operators associate right to left. In an expression that includes several of both, the postfix operators would all evaluate right to left before the prefix operators, which would then evaluate in the reverse direction! That’s two good reasons not to combine them.

You will often see these operators used as loop counters, which we discuss in Chapter 5, “Using Loop in Java Code.”

Using Unary Operators

Unary operators take one operand; they associate right to left. That’s about all these operators have in common with each other, as Table 3-2 shows.

Table 3-2: Unary operators

OperatorsOperation
+ -Sets the sign of a variable or value
~Bitwise NOT (inverts all bits)
!Logical NOT (inverts true or false)
(type)Casts a reference to the given type
newObject construction keyword

The sign operators (+ and -) will set the sign of any numeric value. The logical NOT operator returns the complement of a Boolean expression. The expression !true evaluates to false, and vice versa.


Search for one’s complement and two’s complement definitions online. These are commonly misunderstood terms.

The bitwise NOT operator returns the complement of an integral type and is a little trickier to discern. It’s not merely the opposite sign of the current value; rather, it inverts (“flips”) the bits that represent the stored value, including the sign bit. The result is what’s called the one’s complement value of the operand.

Integral types in Java do not have a symmetrical positive and negative range, as you may recall from Chapter 2, “Applying Data Types in Java Programming.” They have symmetrical negative and nonnegative ranges, but that doesn’t help much. The distinction may not even seem important—at least not until your calculations are all off by one—but without it you might write a fair number of nearly correct programs.

Java uses the all-zeroes bit representation—technically, a number without a negative sign—to represent zero itself. The bitwise complement of zero is therefore all bits set to 1s, including the sign bit. Java uses this pattern to represent the first number in the negative range, -1. Thus the bitwise representation for decimal 1 in 32-bit form is: 0b0000_0000_0000_0000_0000_0000_0000_0001


In Java SE 7, we can express literal integers in bit form and use underscores to break them up for easy reading: 0b0000_0000_0000_0000_0000_0000_0000_0001.

The bitwise complement of positive 1 is

0b1111_1111_1111_1111_1111_1111_1111_1110

The prefix “0b” is required to indicate we mean a binary representation; without it, the compiler will interpret this as octal (base eight) representation. In the two’s complement scheme, this value stands for negative two. The leftmost bit holds the sign. Therefore, as we increase the numeric scale in the negative range, we gradually replace ones with zeroes. Scaling in the nonnegative range does the opposite. The lowest number in the negative range is all zeroes except for the sign bit:

0b1000_0000_0000_0000_0000_0000_0000_0000

The absolute value of the maximum negative number does not equal the maximum positive number, but they are still bitwise complements. If you have to work with numbers in this form, all you can do is remember and embrace this fact. It’s not something I’ve had to work with in my own experience, but I’ve seen it used in technical interviews and vendor trivia contests at some Java conferences. Programmer beware.


Java uses two’s complement to implement the operators that govern integer types.

The two’s complement scheme intervenes on the programmer’s behalf. It can’t create symmetry where it doesn’t exist, but it can provide a simpler rule: “invert and add one.” That’s it. Take the bit representation of any integer, flip all the bits, and add one and you have the same scalar value with a sign change.

The last element in this group is the casting operator. Casting tries to evaluate an object reference to another type. It is expressed by parentheses that contain the Java class you want to cast. The reference is the operand. For example, if we used an Object reference to point to a String referent, we might then want to cast it back:

String str = new String(“let’s go casting!”);
// Use an Object reference to refer to a String referent
Object obj = str;
// Cast the reference to a String to assign it again
String str2 = (String)obj; 

We need the casting operator for reasons we won’t discuss until Chapter 9, “Inheriting Code and Data in Java.” Until then, don’t assume the casting operator lets you convert one type to any other. It’s useful, not magical.

Using Multiplicative and Additive Operators

These categories include the “basic calculator” functions: multiplication, division, remainder (also known as modulus), addition, and subtraction. They operate on all numeric primitives. Just as in math, the multiplicative operators take precedence over the additive ones.

The + operator has a second use. Given two operands that evaluate to String values, the + operator will concatenate them into a third String: 
public static void main(String args[]) {
       String first = "snicker";
       String second = "doodle";
       System.out.println(first + second);   // prints snickerdoodle
}

What’s less obvious is what occurs when you combine the addition and concatenation operators. To illustrate, you can add the following statements to the main() method immediately above:

int x = 212;
int y = 121;
System.out.println(first + x);   // snicker212
System.out.println(second + y);   // doodle121
System.out.println(second + x + y);   // doodlc21c02e0121
System.out.println(x + second + y);   // 212doodle121
System.out.println(x + y + second);   // 333doodle
System.out.println(x + y + second + x + y);   // 333doodlc21c02e0121

If the + operator appears more than once, it associates left to right, as expected. However, if either operand in the expression evaluates to a String type, concatenation trumps addition. Others values, such as x and y in our example, are silently converted to String literals and then concatenated. Notice in the last two statements that x and y were summed first. The additive operators associate left-to-right, therefore the sum of x and y comes first, then the concatenation of the sum and the value of the reference second.

Using Shift Operators

Shift operators use the bit representation of integral types to double or halve their value. Shift one bit to the left, multiply by two; to the right, divide by two. So if you left-shift the value four using the << operator, you’ll get eight. Here it’s shown using a short’s worth of bits

short stack = 0b0000_0100 << 1; // (will equal 0b0000_1000 or 8)

Do it again to get 16, once more to get 32, and so on. Right-shifting 32 three times, using the >> operator, brings you back to four.

The sign bit is not modified by either of these operators. If you left-shift -8 by one bit, the sign remains in the high-order bit. The result is -16. If you shift once to the right it will be -4.

Shift operators are binary operators, meaning they require two operands. The first operand supplies the value you want shifted. The second operand specifies the distance (number of bit slots) to shift. That means the expression 2 << 3 evaluates to 24 (or 16). 16 >> 1 evaluates to the square root of 16, or 161/2, or four. Bear in mind that bit-shifting is not the same as using exponents. 161 is the same as 16 << 0 and 16 >> 0, that is, no shifting at all. There is no equivalent expression for 160 using shift operators.

If you need to move the sign bit too, Java supplies the >>> (unsigned bit shift right) operator to do that. And if you do come across a valuable use for that operator, please send me an email. I’ve perhaps heard two uses cases for it in all the years I have used Java and I can’t remember either one.

Using Relational Operators

Relational operators let us determine the type of inequality (less than or greater than) that exists between two values. Relational operators are by definition binary operators and especially useful for sorting through a collection of numbers or testing a value as it increases or decreases and approaches a limit.

The operators < (less than) and > (greater than) are the same as we use in math. Like most programming languages, Java also provides the <= (less than or equal to) and >= (greater than or equal to) tokens as conveniences. It’s worthwhile to take a moment and recognize that these are also compound operators. That is, these two operators really make two decisions in a single step. They’re possible because the underlying physical processor has machine-level instructions that let us to pretend just one operation is taking place.


A Java interface is also a legal operand with instanceof. We’ll study them in Chapter 10, “Understanding Java Interfaces and Abstract Classes.”

Object references also have a relational operation in the form of the instanceof keyword. It evaluates to true if the first operand, an object reference, “relates” to the second operand, a Java object type, usually a class. Object references can relate to a type in a couple ways. The simplest case is one whose referent was constructed from the given type, as follows:

public static void main(String args[]) {
       Object obj = new Object();
       System.out.println(obj instanceof Object);
       obj = null;
       System.out.println(obj instanceof Object);
}

The expression obj instanceof Object will evaluates to true in the first test and false in the second. This outcome means the instanceof operator tests the type of the referent, not the type of the variable. You’re not ready to fully appreciate that distinction, so I’ve italicized it. You might want to highlight or annotate it too (“the author is really hung up on this statement, I don’t know why”). You will understand this operator better once you learn about inheritance. We’ll have that discussion in Chapter 9.

Using Equality Operators

Equality is another operator that sounds simpler than it is. Either two operands are equal to each other (==) or they’re not (!=), right? Certainly that conclusion holds true for integral and Boolean values. For floating-point numbers, it’s not as simple to define. Two fractional values you’d call equal in practical terms can nonetheless be unequal in absolute terms.

Consider the values 20.000005 and 20.0000049. Are they equal? In mathematical terms, of course they are not. In effective terms, however, the degree of precision you need in a program may change your perspective. Which is more helpful to your cause, an effective test or an absolute one? We don’t need to sort that issue out in a beginner’s book, but keep in mind there’s more to equality for floating-point values than I’ve said.

Object equality is another matter entirely. What does it mean to test the equality of two object references? As it turns out, there are also two kinds of tests for them, one that is absolute, in a way, and one that is relative (kinda sorta). I devote a section to that subject later in this chapter to make sure I articulate the issue clearly.

Using Bitwise and Logical Operators

As mentioned earlier, Java uses the same three symbols to represent bitwise and logical operators. In order of precedence, the symbols are &, ^, and |. The ampersand (&) is called the AND operator. The caret (^) is called the XOR (eXclusive OR) operator. The pipe (|) is called the OR operator.

Each bitwise operator takes precedence over its logical counterpart. In either context the operators are binary, so we have to consider the operands in order to know which operation was intended. If the operands are integral values, we interpret the operator as bitwise. If both operands are Boolean values, we interpret the operator as logical. If we apply one operand of each sort, we will interpret the compiler’s displeasure.

public final class Bitgical {
public static void main(String args[]) {
      System.out.println(true ^ false);
      System.out.println( 5 ^ 24);
   }
}
$ javac Bitgical.java
$ java Bitgical
true
29

The ^ operator determines if two bits or Boolean operands are different. Bitwise XOR evaluates to 0 if two bits match, 1 if they don’t. The expression 11 ^ 11, for example, evaluates to 0 (all bits are the same, which appears like subtraction). The expression 11 ^ 4 evaluates to 15 (all bits are different, which appears like addition). Likewise, logical ^ returns true if the operands are different or false if they are the same.

Interesting to note: The expression 11 ^ 15 evaluates to 4, and the expression 15 ^ 4 evaluates to 11. If you combine the result of an XOR with one of the original operands, it will always produce the remaining operand.

The bitwise AND operator compares the bit arrangement of two integral values, testing if both values are 1. If both numbers have the same bit set to 1, the result for that comparison is 1. In all other cases, the result is 0. Logical AND operates similarly for Booleans; if both operands evaluate to true, the operation evaluates to true. Otherwise, it evaluates to false as shown here:

public final class And {
public static void main(String args[]) {
      System.out.println(true & false);
      System.out.println(5 & 24);
   }
}
$ javac And.java
$ java And
false
0

Notice the bitwise result is zero. That’s because no bits in the comparison were both set to 1.

This isn’t much of a revelation in math, but it’s very useful when treating integers as bit fields, a collection of properties encoded by their position in a multibit storage type. You can think of an int as a way to store 31 different flags, or state values that are on/off, enabled/disabled, running/stopped, whatever. It’s cheap, efficient storage, but you also then need bitwise operators to extract these values and code to interpret their meaning.

As with most solutions in programming, there’s a trade-off. These days, in general-purpose programming, physical memory isn’t the precious commodity it once was. As a result, the benefits of a bitset seem minimal. When you do have to fit a program into a small memory space, however, a bitset is an effective tactic.

The bitwise | operator tests whether either of two bits is 1. If so, the result is 1. The result is 0 only if both bits are 0. Similarly, the logical | operation result is true if either operand is true. The result is false only if both operands are false.

public final class Or {
public static void main(String args[]) {
      System.out.println(true | false);
      System.out.println(5 | 24);
   }
}
$ javac Or.java
$ java Or
true
29

Using Conditional Operators

There are three conditional operators. Each one has its own level of precedence. The first two, conditional AND and conditional OR, are binary operators. They are represented by doubling the symbol of their logical counterparts. The conditional AND token is &&. The OR token is ||. These two are also called short-circuit operators because they will ignore the second operand if it doesn’t affect the outcome of the whole expression.

With conditional AND, for example, evaluation stops if the first operand evaluates to false; the result of the second operand is moot. Using conditional OR, evaluation stops if the first operand evaluates to true. You can use this effect to write tests for the second operand that would normally make the compiler complain. If the compiler infers such operands never have to be evaluated, it won’t verify them. Here’s an example:

public static void main(String args[]) {
int x;
if ( true || x > 5 ) System.out.println("See?");
}

We haven’t covered the if statement yet (it’s coming soon), but the idea here is plain. Since the first operand is literally always true, the second one will never come into play. Consequently, the compiler ignores the fact that x hasn’t even been initialized. Along with other subtle side effects I’ve noted, this one is prime material for exams that want to test your vigilance in analyzing code. You have been warned.

The third operator in this group is just called the conditional operator. It is also called “the” ternary operator because it’s the only one that takes three operands. It has an arcane form that is easiest to understand by example:

String msg = (x == 15) ? ("Happy!") : ("Sad.");

The first operand is a Boolean expression. If it evaluates to true, the expression following the question mark is evaluated. If it evaluates to false, the expression following the colon evaluates next.

This terse form appeals to programmers who prize brevity. In the C programming language, it is a popular device in code obfuscation contests, where the goal is to write the least-readable working code. Fortunately, Java does not allow a programmer the same license with the ternary operator that C does.

When long expressions are involved, however, it’s still easy to abuse. Here’s an example statement using one level of nesting:

String msg = (guess > jellybeans) ? 
"Sorry, over the top." : 
(guess == jellybeans) ?
 "Winner!" :
 "Close, but no cigar.";

If the value guess exceeds the value of jellybeans, we assign the literal String “Sorry” to msg. If guess is equal to jellybeans, we assign the literal “Winner!. Otherwise, the remaining message is assigned. You can imagine what additional levels of nesting would look like. Few people will thank you if you give them a creation like this to read.

Using Assignment Operators

There are 12 assignment operations altogether, making the most populous category. The simple operator (=) takes precedence over the others, which are called compound operators:

*= /= %= += -= <<= >>= >>>= &= ^= |=

Each one of these tokens combines assignment with a multiplicative, additive, shifting, or bitwise operator. Instead of writing x = x * 5, for example, you can write x *= 5. It may seem like a small convenience, but if you do a lot of code work manipulating numbers, you’ll come to appreciate it.

And if you think that minor convenience is all that’s going on, you’re in for a truly inscrutable surprise. Check this out:

public final class Compound {
public static void main(String args[] ) {
int i;
float j;
float w = 3.14f;
i += w;
      j = i + w;
i = i + w;
   }
}

$ javac Compound.java
Compound.java:8: error: possible loss of precision
      i = i + w;
            ^
  required: int
  found:    float
1 error

Let’s walk this code carefully. We declare the variables i and m as integers and j and w as floating-point numbers. We use the compound assignment operator to set i equal to i plus w. We then assign to both j and i the sum of the two variables.

Two of these statements are fine. One fails. Why?

Let’s first ask why the second statement doesn’t fail. There is a property in Java at play here known as implicit casting. If you assign a primitive value of one type to another with equal or greater storage, the compiler allows it. You can assign an integer value to a float (or double) variable without a problem. Implicit casting follows this progression:

byte > short > char > int > long > float > double

It’s allowed because there is no risk of losing the precision of the value. If instead you try to assign a long value to an int, even if the value at hand is within int range, the compiler reports an error. The term possible loss of precision means that allowing this assignment might truncate the value at hand, making it “less precise” than it was. Even though int and long have the same capacity, a long is considered more precise.

Variable j stores the sum of i and w as a float. The type of i gets lost in the implied casting, but the value maintains precision. To assign the same result to i, you’d have to tell the compiler you accept the possible loss of precision with an explicit cast, like this:

i = (int)(i + w);

The casting operation forces the result of addition into an int form, a mandate the compiler accepts.

So why does i += w compile? It includes an explicit cast as part of its makeup. How would you know that? By reading the JLS, which tells you this behavior is included. Why is it included? Well, you could read the appropriate section of the JLS:

http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.26.2

It, however, doesn’t say why, it just declares the rule. I’ll steer clear of inferring the motive, at least in print.

Using Parentheses to Control Operators

Some PUMAS Relate Equally to BLack CAts: separators, post/prefix, unary, multiplicative, additive, shifting, relational, equality, bitwise, logical, comparison, assignment. Memorize that much and you’ll be able recall the order of precedence more quickly when you need it. Also, remember that unary, conditional, and assignment operators associate right to left. All other operators (of equal precedence) associate left to right.

certobjective.eps

Now let’s use an expression from the previous section to consider how to alter the order of evaluation:

3 + 4 × 14 ÷ 7 ÷ 7 - 8

Let’s say you don’t want normal associativity to order the evaluation of the multiplicative operators. Instead, you first want the two sevens reduced to one. To effect this, you need a means to override the default. Just as in math, you can use parentheses to do that. Let’s rewrite the expression as follows:

3 + 4 × 14 ÷ (7 ÷ 7) - 8

Now the last division in the expression will evaluate first. The whole expression then evaluates to 51.

Parentheses can override precedence as well as associativity. Let’s say you wanted the additive operators to evaluate first, then the rest. You can achieve that with the following rewrite:

(3 + 4) × 14 ÷ 7 ÷ 7 - 8

Now the expression reduces to 7 × 14 ÷ 7 ÷ 7 - 8, or -6.

If you’re not careful, you can achieve complexity now by nesting parentheses and using all its various meanings—casting, method invocation, and evaluation overrides—all one expression. Consider the following made-up statement:

Widget pie = (Widget)(factory.fillCircle((22/7) * (circle.radius << 1));

Assume for the sake of discussion that the factory object reference fills a circle for you if you supply its area as a parameter. In this example you calculate it for a square with a radius of one using the approximation 22/7 in place of pi times the radius squared.

This statement uses parentheses three different ways: first, to cast the result to a Widget type; second, to invoke a method; and third, to order the calculation with explicit grouping. Statements like this aren’t at all unrealistic. Reading them correctly and quickly takes some practice.

The language designers naturally want to use your knowledge from math and even your intuition to help you decipher such a statement. That’s one reason to prefer infix notation over postfix notation, even if the latter is more efficient. Still, to be sure of your results, you must learn the rules of operation by heart. Remember, you can sometimes interpret them correctly only by knowing the number and type of operands necessary.


If I have any role in writing the exam, you can be certain you’ll see several questions that follow this format.

Test questions commonly present you with a few code statements and ask you to determine (a) which statement option is illegal, if any; (b) if the code will compile; (c) if the code will run; and sometimes even (d) how much of the code will run before it fails. By the end of this book, you’ll have enough information to manage such questions for straightforward cases. The challenge is all about the accuracy, and to some degree speed, of your analysis.

Understanding Object Equality

Testing equality between two integral numbers is simple. When it comes to floating-point values, however, the precision of the operands may complicate the test. I mentioned earlier the idea of effective equality: two numbers that are close enough to be equal according to the needs of your program.

Let’s say we’re calculating interest on two money market accounts with slightly different balances. What’s the difference between a calculation of $1.5800003 on one and $1.5800007 on the other? They’re the same, practically speaking, but they’re not mathematically equal. To make them practically equal, we might truncate the values. There’s nothing wrong with that, but it does require we write code and a set of informing principles by which a calling programmer will understand how that code is designed to work.

certobjective.eps

A similar issue arises when you want to test object references for equality. Given two references as operands, the equality operator (==) will tell you if they refer to the same referent. In a sense, this is like integer equality: Seven always equals seven. Anything that stores a seven must be considered equal to seven. Any reference that stores a location must be equal to any other reference storing the same location.

We expect this equality to resolve to true when one object reference has been assigned to an existing referent, as in this example:

public static void main(String args[]) {
   Object ab = new Object();
   Object cd = ab;
boolean sameRef = (ab == cd);
}

The test ab == cd will resolve to true, which is then assigned to the sameRef variable. Any two references, despite their differences in name or scope, are deemed equal if they access the same referent. This rule bears a resemblance to integer equality. And while it’s an essential test, it doesn’t let us define effective equality when we need it.

Let’s say we constructed two String objects with the same literal value. We test them with the == operator and the result is false.

public final class Equality {
public static void main(String args[]) {
      String jack = new String("water!");
      String jill = new String("water!");
      System.out.println(jack == jill);
   }
}
$ javac Equality.java
$ java Equality
false

Our code tells us that much. But say we really wanted to know whether the referents pointed to by jack and jill had the same content. We need another way to define that effective equality. That’s not a small need, by the way. It’s easy to point out that content equality, so called, is far more important than referent equality for many needs. Fortunately, the Java language designers agree and provide the Object.equals() method to offer that flexibility.

Well, sort of. The source code for the Object.equals() method is similar to the following:

public boolean equals(Object obj) {
return this == obj;
}

The Object class just wraps the equality operator in a method. If you compare any two Object references, you’ll get the same result whether you use == or the equals() method. How do you get effective equality out of that?

The Object class supports what it calls the narrowest—meaning the least inclusive—test for equality. The narrowest possible sense of equality occurs when a referent is equal to itself (such as two references pointing to the same referent). For many Java classes, it would be better if you could compare the contents of two referents, using logic that determines if two different referents have the same value.

Notice what happens if we construct and compare two String objects with the same content and test with the equals() method instead of the equality operator:

public static void main(String args[]) {
   String str1 = new String("abc");
   String str2 = new String("abc");
boolean strictEq = (str1 == str2);
boolean effectiveEq = str1.equals(str2);
   System.out.println("strict: " + strictEq);
   System.out.println("effective: " + effectiveEq);
}

Should you compile and run this code, you’ll get the following result:

strict: false
effective: true

There is something hidden this time, but it’s not magic. The String class overrides the equals() method, defining equality as any two String references that have the same value (where two references pointing to the same referent is merely the narrowest case). In practice, this kind of effective equality, as shown in Figure 3-1, is what you need most of the time.

Figure 3-1: Effective equality between objects

c03f001.eps

Here you see two reference and referent pairs, and what we’d like to know more often than not is whether they have the same values, not whether they have the same referent. With strict equality, as we are calling it, you can only test the relationship shown in Figure 3-2.

Figure 3-2: Strict quality between objects

c03f002.eps

Here the reference str3 has the same content but could not pass a strict equality test. In general, testing whether two references point to the same referent is a query on the state of the references. An effective test instead is a query on whether data, or state, is shared between two objects. The concern here usually derives from asking whether two objects have achieved the same state at the same time, and that is, generally speaking, the question you more often need an answer to in a program.

In all the example classes you’ve seen in this guide, we’ve inherited the equals() method as is from the Object class. In other words, we can test for strict equality only until we use (or write) classes that override the equals() method to support effective equality.

Implementing it, it turns out, is a rigorous exercise. There are several rules you must adhere to for reliable, accurate testing. That kind of programming is well beyond us for now. In Chapter 9 we will discuss the technique for overriding inherited methods and how to apply it, but you still will not have the foundation you need to implement effective equality on your own.

Using if and if/else Statements

The logical operators you’ve learned wouldn’t be much use if you didn’t use them to make decisions in your code. The ternary conditional operator was originally devised for that purpose: Based on the result of the first operand, only one of two paths of execution follows.

Reading a conditional operator in code gets harder the more you try to do with it, as you’ve seen. It’s better for maintaining code if the language syntax helps you read it. Every programmer, through practice, learns to strike a balance between code that is clear and code that is brief. Just as terse code, full of bitwise and logical operators, can become cryptic, so can overly simplified code, saddled with many simple statements, become tedious to browse and digest.

When it comes to choosing among multiple paths of execution, the first syntax-friendly constructs to learn are if and if/else statements. Their simplest form looks like this:

if (boolean_expression) 
statement1;

In this context, the parentheses contain (or separate) the Boolean expression. If it evaluates to true, statement1 executes; otherwise, program control transfers to the statement delimiter. If you want to execute multiple statements, you use code block separators to contain them:

if (boolean expression) {
statement1;
statement2;
statementN;
}

Whether the expression evaluates to true or false, any statements that follow the terminating code block separator will execute normally. If you want mutually exclusive paths of execution, append an else statement to the if statement:

if (boolean_expression) 
statement1;
else
   statement2;

To incorporate multiple statements for either statement, use code block separators:

if (boolean_expr) {
   statement1;
   statement2;
} else {
   statementA;
   statementB;
}

Now you can achieve a similar effect to the ternary conditional operator and use multiple statements without making a spaghetti mess. The indenting I’ve shown promotes readability only, by the way; it’s not required. Combined with the syntax, it makes the code much easier to browse.

Then there are times when you need to execute statements where there are more than two outcomes to a decision. Those outcomes, while they are related in some sense that derives from the program logic you’re writing, probably don’t reduce nicely into a number of either-or paths.

Here’s an example concept: “If it is Monday, I’ll write some code. Otherwise, if it’s Tuesday, I’ll test my operator knowledge. Otherwise, if it’s Wednesday, I’ll review the current chapter.” For some reason, as this list of statements implies, you want to consider Wednesday’s plan only if Monday and Tuesday can be ruled out.

To support this nested logic approach, you can use the else statement to create another if statement, as shown here:

if (boolean_expression) 
	statement1;
else if (boolean_expression)
	statement2;

The process of revising code without breaking its callers or dependencies is called refactoring.

You can nest if statements indefinitely, but this technique also becomes harder to read the further you take it. Deeply nested if statements are often a sign that the logic hasn’t been thought through. It’s a common outcome when programmers add conditional cases to an existing structure instead of changing the structure to a simpler or more appropriate form. It’s a bit like placing dishes on a growing stack in the sink. If you’re going to clean one, you might as well clean them all, but no one wants the job everyone else left behind.

If you do have a range of outcomes that don’t rely on a chain of nested decisions, a switch statement is a better choice. We’ll discuss that next.

Using a switch Statement

The switch statement makes it easy to list several paths from a single decision point. It’s only slightly more complex than an if/else statement:

switch (test_expression) {
case (matching_expression1) :
statementA;
case (matching_expression2):
statementB;
statementC;
case (matching_expressionN):
statementX;
statementY;
default: 
statementZ;
}

The test expressions also accept a Java type called enum. We describe enums in Chapter 10, “Understanding Java Interfaces and Abstract Classes.”

The test expression may evaluate to any integral type—char, byte, short, int—except long. It may also evaluate to an instance of their namesake classes—Character, Byte, Short, and Integer. It does not evaluate boolean or Boolean types because the if statement is sufficient to manage those. The switch structure gives you what the if statement cannot: a plain way to handle multiple outcomes, or cases, and a way to expand those cases over time without additional syntactic complexity.

Starting with Java SE 7, you can also use string values for the test and matching expressions. You probably won’t appreciate this new feature nearly as much as your more experienced colleagues will. Let them tell you how awesome it is that we can finally use strings with a switch statement.

There are two kinds of labels that a switch statement allows. It can have zero or more case labels and zero or one default labels. Each case label has a value, or matching expression, whose type is any legal type for a switch expression. The case labels do not have to have the same type. That’s the law. Having said that, creating a mix of types for a switch statement is better for an obfuscation contest or Java trivia game than it is for clear, readable code.

When the switch expression matches a case label, program control transfers immediately to that label. No two labels may share a matching value. Any statements listed from that point forward execute. If no match occurs and a default label exists, the default label’s statements execute. Otherwise, control falls to the end of the switch statement itself and continues from there.

Execution in a switch statement cascades by design. Once control passes to a matching label and its statements execute, all statements that follow in all subsequent labels will also execute. Consider the following example:

public static void main(String args[]) {
   switch (args[0]) {
      case "a":
         System.out.println("A");
      case "b":
         System.out.println("B");
      default:
         System.out.println("No match");
      case "c":
         System.out.println("C");
      }
}

If args[0] equals the value “a”, this code will print:

A
B
No match
C

There are times this behavior works to your advantage. Let’s say you want to distribute awards for an airline loyalty program. For flying 100,000 miles or more, customers get an airplane pin. For 1,000,000 miles or more, they get an airplane paperweight. For 10,000,000 miles, they get an airplane. If any customer wins a special drawing and receives 10,000,000 miles for a prize, they get all the gifts. With those rules in mind, writing a switch statement to handle all four cases should be simple.

But it doesn’t always make sense to work this way. Sometimes you still want mutually exclusive logic of the if/else variety but also more than two cases to manage. At the very least, executing both a case label and the default label isn’t what you normally want. While you could think of the default label as a list of statements you always want to execute at the end of the switch, it’s far more likely you’ll use it to capture cases you haven’t specified.

For that reason, putting the default label last is not just practical advice. You will rarely, if ever, see it placed anywhere else, even though the compiler allows it. With that convention in mind, you can use break statements to jump around it, like this:

switch (some_integer) {
case x:
      doSomething();
case y:
      doSomethingElse();
break;
default:
      complain();
}

Or you can add a break statement at the end of every case. Each matching case will then execute and transfer control to the end of the switch statement, ignoring all other cases along the way. Now you have exclusive execution for each case.


The Essentials and Beyond

In this chapter, we covered what the Java operators do and some of their key properties, including precedence and associativity. We discussed the concepts of absolute (or strict) and effective equality and how the latter applies specifically to object references. We also discussed two decision-making constructs—the if/else and switch statements—we can use to selectively execute code.
Additional Exercises
1. Throughout the chapter there are code examples shown in a main() method body. Wrap these examples in a class so you can compile and run them. Keep them in a directory on your computer for reference.
2. Using the airline loyalty program example, write a program that prints out a prize statement for award level achieved. Use only the conditional operator.
3. Using the airline loyalty program example, write a program that prints out a prize statement for award level achieved. Use only if or if/else statements.
4. Write a program that shifts the value -32768 a distance of three to the right using both the signed and unsigned shifting operators. Print out each result.
5. Search the Internet for at least two different operator precedence and associativity tables you can print, and compare them.
Review Questions
1. What is the bitwise complement of an int whose value is 231 -1?
A. -(231 - 1)
B. -231
C. -231 + 1
D. 0
2. Which option lists categories of precedence in correct order?
A. Separators, Postfix/Prefix, Associative, Multiplicative
B. Bitwise, Logical, Equality, Concatenate
C. Multiplicative, Additive, Shifting, Ternary
D. Relational, Equality, Conditional, Assignment
3. Which operator category does not have compound assignment tokens?
A. Unary
B. Additive
C. Bitwise
D. Multiplicative
4. True or false: A switch statement may have zero case labels and no default label. It will still compile.
5. What is the result of this expression?

(3 + 4) + 5 × 6 << 1

A. 144
B. 67
C. 74
D. 187
6. Given an integer x that has been assigned 1, what is the result of the statement int y = --x++?
A. y is assigned 2; x will remain unchanged.
B. y is assigned 1; x will be assigned 2.
C. This is not a legal statement.
D. y is assigned 2; x is equal to 2 before the statement completes, equal to 1 after the statement completes.
7. Which statement describes an effect that Java parentheses don’t support?
A. They contain zero or more parameters for method calls.
B. They contain a boolean expression for switch statements.
C. They function as a type-casting operator.
D. They contain a truth expression for if statements.
8. True or false: For the Object class, there is no difference between strict equality and effective quality, nor can there be.
9. True or false: The conditional (ternary) operator can return a result of any type.
10. True or false: All Java statements must end with a semicolon.

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

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