Chapter 3 Operators

OCP exam objectives covered in this chapter:

  • Using Operators and Decision Constructs

    • Use Java operators including the use of parentheses to override operator precedence
  • Working With Java Primitive Data Types and String APIs

    • Declare and initialize variables (including casting and promoting primitive data types)

In the previous chapter, we talked a lot about defining variables, but what can you do with a variable once it is created? This chapter introduces operators and shows how you can use them to combine existing values and create new values. We’ll show you how to apply operators to various primitive data types, including introducing you to operators that can be applied to objects.

Understanding Java Operators

Before we get into the fun stuff, let’s cover a bit of terminology. A Java operator is a special symbol that can be applied to a set of variables, values, or literals-referred to as operands-and that returns a result. The term operand, which we’ll use throughout this chapter, refers to the value or variable the operator is being applied to. The output of the operation is simply referred to as the result. For example, in a + b, the operator is the addition operator (+), and values a and b are the operands. If we then store the result in a variable c, such as c = a + b, then the variable c and the result of a + b become the new operands for our assignment operator (=).

We’re sure you have been using the addition (+) and subtraction (-) operators since you were a little kid. Java supports many other operators that you need to know for the exam. While many should be review for you, some (such as the compound assignment operators) may be new to you.

Types of Operators

In general, three flavors of operators are available in Java: unary, binary, and ternary. These types of operators can be applied to one, two, or three operands, respectively. For the exam, you’ll need to know a specific subset of Java operators, how to apply them, and the order in which they should be applied.

Java operators are not necessarily evaluated from left-to-right order. For example, the second expression of the following Java code is actually evaluated from right to left given the specific operators involved:


int cookies = 4;
double reward = 3 + 2 * --cookies;
System.out.print("Zoo animal receives: "+reward+" reward
points");

In this example, you would first decrement cookies to 3, then multiply the resulting value by 2, and finally add 3. The value would then be automatically promoted from 9 to 9.0 and assigned to reward. The final values of cookies and reward would be 9.0 and 3, respectively, with the following printed:


Zoo animal receives: 9.0 reward points

If you didn’t follow that evaluation, don’t worry. By the end of this chapter, solving problems like this should be second nature.

Operator Precedence

When reading a book or a newspaper, some written languages are evaluated from left to right, while some are evaluated from right to left. In mathematics, certain operators can override other operators and be evaluated first. Determining which operators are evaluated in what order is referred to as operator precedence. In this manner, Java more closely follows the rules for mathematics. Consider the following expression:


var perimeter = 2 * height + 2 * length;

The multiplication operator (*) has a higher precedence than the addition operator (+), so the height and length are both multiplied by 2 before being added together. The assignment operator (=) has the lowest order of precedence, so the assignment to the perimeter variable is performed last.

Unless overridden with parentheses, Java operators follow order of operation, listed in Table 3.1, by decreasing order of operator precedence. If two operators have the same level of precedence, then Java guarantees left-to-right evaluation. For the exam, you only need to know the operators shown in bold in Table 3.1.

Table 3.1 Order of operator precedence
Table 3.1 Order of operator precedence

We recommend that you keep Table 3.1 handy throughout this chapter. For the exam, you need to memorize the order of precedence in this table. Note that you won’t be tested on some operators, like the shift operators, although we recommend that you be aware of their existence.

Applying Unary Operators

By definition, a unary operator is one that requires exactly one operand, or variable, to function. As shown in Table 3.2, they often perform simple tasks, such as increasing a numeric variable by one or negating a boolean value.

Table 3.2 Unary operators
Table 3.2 Unary operators

Even though Table 3.2 includes the casting operator, we’ll postpone discussing casting until the “Assigning Values” section later in this chapter, since that is where it is commonly used.

Logical Complement and Negation Operators

Since we’re going to be working with a lot of numeric operators in this chapter, let’s get the boolean one out of the way first. The logical complement operator (!) flips the value of a boolean expression. For example, if the value is true, it will be converted to false, and vice versa. To illustrate this, compare the outputs of the following statements:


boolean isAnimalAsleep = false;
System.out.println(isAnimalAsleep);   // false
isAnimalAsleep = !isAnimalAsleep;
System.out.println(isAnimalAsleep);   // true

Likewise, the negation operator, -, reverses the sign of a numeric expression, as shown in these statements:


double zooTemperature = 1.21;
System.out.println(zooTemperature);   // 1.21
zooTemperature = -zooTemperature;
System.out.println(zooTemperature);   // -1.21
zooTemperature = -(-zooTemperature);
System.out.println(zooTemperature);   // -1.21

Notice that in the last example we used parentheses, (), for the negation operator, -, to apply the negation twice. If we had instead written --, then it would have been interpreted as the decrement operator and printed -2.21. You will see more of that decrement operator shortly.

Based on the description, it might be obvious that some operators require the variable or expression they’re acting upon to be of a specific type. For example, you cannot apply a negation operator (-) to a boolean expression, nor can you apply a logical complement operator (!) to a numeric expression. Be wary of questions on the exam that try to do this, as they’ll cause the code to fail to compile. For example, none of the following lines of code will compile:


int pelican = !5;          // DOES NOT COMPILE
boolean penguin = -true;   // DOES NOT COMPILE
boolean peacock = !0;      // DOES NOT COMPILE

The first statement will not compile because in Java you cannot perform a logical inversion of a numeric value. The second statement does not compile because you cannot numerically negate a boolean value; you need to use the logical inverse operator. Finally, the last statement does not compile because you cannot take the logical complement of a numeric value, nor can you assign an integer to a boolean variable.

NOTE

Keep an eye out for questions on the exam that use the logical complement operator or numeric values with boolean expressions or variables. Unlike some other programming languages, in Java, 1 and true are not related in any way, just as 0 and false are not related.

Increment and Decrement Operators

Increment and decrement operators, ++ and --, respectively, can be applied to numeric variables and have a high order of precedence, as compared to binary operators. In other words, they often get applied first in an expression.

Increment and decrement operators require special care because the order in which they are attached to their associated variable can make a difference in how an expression is processed. If the operator is placed before the operand, referred to as the pre-increment operator and the pre-decrement operator, then the operator is applied first and the value returned is the new value of the expression. Alternatively, if the operator is placed after the operand, referred to as the postincrement operator and the post-decrement operator, then the original value of the expression is returned, with operator applied after the value is returned.

The following code snippet illustrates this distinction:


int parkAttendance = 0;
System.out.println(parkAttendance);     // 0
System.out.println(++parkAttendance);   // 1
System.out.println(parkAttendance);     // 1
System.out.println(parkAttendance--);   // 1
System.out.println(parkAttendance);     // 0

The first pre-increment operator updates the value for parkAttendance and outputs the new value of 1. The next post-decrement operator also updates the value of parkAttendance but outputs the value before the decrement occurs.

NOTE

For the exam, it is critical that you know the difference between expressions like parkAttendance++ and ++parkAttendance. The increment and decrement operators will be in multiple questions, and confusion about which value is returned could cause you to lose a lot of points on the exam.

One common practice in a certification exam, albeit less common in the real world, is to apply multiple increment or decrement operators to a single variable on the same line:


int lion = 3;
int tiger = ++lion * 5 / lion--;
System.out.println("lion is " + lion);
System.out.println("tiger is " + tiger);

This one is more complicated than the previous example because lion is modified two times on the same line. Each time it is modified, as the expression moves from left to right, the value of lion changes, with different values being assigned to the variable. As you’ll recall from our discussion on operator precedence, order of operation plays an important part in evaluating this example.

So how do you read this code? First, lion is incremented and returned to the expression, which is multiplied by 5. We can simplify this:


int tiger = 4 * 5 / lion--;   // lion assigned value of 4

Next, lion is decremented, but the original value of 4 is used in the expression, leading to this:


int tiger = 4 * 5 / 4;   // lion assigned value of 3

Finally, we evaluate multiplication and division from left to right. The product of the first two numbers is 20. The divisor 4 divides 20 evenly, resulting in an assignment of 5 to tiger. The result is then printed:


lion is 3
tiger is 5

Working with Binary Arithmetic Operators

Next, we move on to operators that take two arguments, called binary operators. Binary operators are by far the most common operators in the Java language. They can be used to perform mathematical operations on variables, create logical expressions, and perform basic variable assignments. Binary operators are often combined in complex expressions with other binary operators; therefore, operator precedence is very important in evaluating expressions containing binary operators.

In this section, we’ll start with binary arithmetic operators, shown in Table 3.3. In the following sections, we’ll expand to other binary operators that you need to know for the exam.

Table 3.3 Binary arithmetic operators
Table 3.3 Binary arithmetic operators

Arithmetic Operators

Arithmetic operators are often encountered in early mathematics and include addition (+), subtraction (-), multiplication (*), division (/), and modulus (%). If you don’t know what modulus is, don’t worry- we’ll cover that shortly. Arithmetic operators also include the unary operators, ++ and --, which we covered already. As you may have noticed in Table 3.1, the multiplicative operators (*, /, %) have a higher order of precedence than the additive operators (+, -). Take a look at the following expression:


int price = 2 * 5 + 3 * 4 - 8;

First, you evaluate the 2 * 5 and 3 * 4, which reduces the expression to this:


int price = 10 + 12 - 8;

Then, you evaluate the remaining terms in left-to-right order, resulting in a value of price of 14. Make sure you understand why the result is 14 because you’ll likely see this kind of operator precedence question on the exam.

NOTE

All of the arithmetic operators may be applied to any Java primitives, with the exception of boolean. Furthermore, only the addition operators + and += may be applied to String values, which results in String concatenation. You will learn more about these operators and how they apply to String values in Chapter 5, “Core Java APIs.”

Adding Parentheses

You might have noticed we said “Unless overridden with parentheses” prior to presenting Table 3.1 on operator precedence. That’s because you can change the order of operation explicitly by wrapping parentheses around the sections you want evaluated first.

Changing the Order of Operation

Let’s return to the previous price example. The following code snippet contains the same values and operators, in the same order, but with two sets of parentheses added:


int price = 2 * ((5 + 3) * 4 - 8);

This time you would evaluate the addition operator 5 + 3, which reduces the expression to the following:


int price = 2 * (8 * 4 - 8);

You can further reduce this expression by multiplying the first two values within the parentheses:


int price = 2 * (32 - 8);

Next, you subtract the values within the parentheses before applying terms outside the parentheses:


int price = 2 * 24;

Finally, you would multiply the result by 2, resulting in a value of 48 for price.

Parentheses can appear in nearly any question on the exam involving numeric values, so make sure you understand how they are changing the order of operation when you see them.

Verifying Parentheses Syntax

When working with parentheses, you need to make sure they are always valid and balanced. Consider the following examples:


long pigeon = 1 + ((3 * 5) / 3;        // DOES NOT COMPILE
int blueJay = (9 + 2) + 3) / (2 * 4;   // DOES NOT COMPILE
short robin = 3 + [(4 * 2) + 4];       // DOES NOT COMPILE

The first example does not compile because the parentheses are not balanced. There is a left-parenthesis with no matching rightparenthesis. The second example has an equal number of left and right parentheses, but they are not balanced properly. When reading from left to right, a new right-parenthesis must match a previous leftparenthesis. Likewise, all left-parentheses must be closed by rightparentheses before the end of the expression. The last example does not compile because Java, unlike some other programming languages, does not allow brackets, [], to be used in place of parentheses. If you replace the brackets with parentheses, the last example will compile just fine.

Division and Modulus Operators

Although we are sure you have seen most of the arithmetic operators before, the modulus operator, %, may be new to you. The modulus operator, often called the remainder operator, is simply the remainder when two numbers are divided. For example, 9 divided by 3 divides evenly and has no remainder; therefore, the result of 9 % 3 is 0.

On the other hand, 11 divided by 3 does not divide evenly; therefore, the result of 11 % 3, is 2.

The following examples illustrate this distinction:


System.out.println(9 / 3); // 3
System.out.println(9 % 3); // 0
System.out.println(10 / 3); // 3
System.out.println(10 % 3); // 1
System.out.println(11 / 3); // 3
System.out.println(11 % 3); // 2
System.out.println(12 / 3); // 4
System.out.println(12 % 3); // 0

As you can see, the division results increase only when the value on the left side goes from 11 to 12, whereas the modulus remainder value increases by 1 each time the left side is increased until it wraps around to zero. For a given divisor y, which is 3 in these examples, the modulus operation results in a value between 0 and (y - 1) for positive dividends. This means that the result of this modulus operation is always 0, 1, or 2.

Be sure to understand the difference between arithmetic division and modulus. For integer values, division results in the floor value of the nearest integer that fulfills the operation, whereas modulus is the remainder value. If you hear the phrase floor value, it just means the value without anything after the decimal point. For example, the floor value is 4 for each of the values 4.0, 4.5, and 4.9999999. Unlike rounding, which we’ll cover in Chapter 5, you just take the value before the decimal point, regardless of what is after the decimal point.

NOTE

The modulus operation is not limited to positive integer values in Java; it may also be applied to negative integers and floating-point numbers. For example, if the divisor is 5, then the modulus value of a negative number is between -4 and 0. For the exam, though, you are not required to be able to take the modulus of a negative integer or a floating-point number.

Numeric Promotion

Now that you understand the basics of arithmetic operators, it is vital to talk about primitive numeric promotion, as Java may do things that seem unusual to you at first. As we showed in Chapter 2, “Java Building Blocks,” each primitive numeric type has a bit-length. You don’t need to know the exact size of these types for the exam, but you should know which are bigger than others. For example, you should know that a long takes up more space than an int, which in turn takes up more space than a short, and so on.

You need to memorize certain rules Java will follow when applying operators to data types:

  1. Numeric Promotion Rules If two values have different data types, Java will automatically promote one of the values to the larger of the two data types.
  2. If one of the values is integral and the other is floating-point, Java will automatically promote the integral value to the floating-point value’s data type.
  3. Smaller data types, namely, byte, short, and char, are first promoted to int any time they’re used with a Java binary arithmetic operator, even if neither of the operands is int.
  4. After all promotion has occurred and the operands have the same data type, the resulting value will have the same data type as its promoted operands.

The last two rules are the ones most people have trouble with and the ones likely to trip you up on the exam. For the third rule, note that unary operators are excluded from this rule. For example, applying ++ to a short value results in a short value.

Let’s tackle some examples for illustrative purposes:

  • What is the data type of x * y?

    
    int x = 1;
    long y = 33;
    var z = x * y;
    
    
  • If we follow the first rule, since one of the values is long and the other is int and since long is larger than int, then the int value is promoted to a long, and the resulting value is long.

  • What is the data type of x + y?

    
    double x = 39.21;
    float y = 2.1;
    var z = x + y;
    
    
  • This is actually a trick question, as this code will not compile! As you may remember from Chapter 2, floating-point literals are assumed to be double, unless postfixed with an f, as in 2.1f. If the value of y was set properly to 2.1f, then the promotion would be similar to the previous example, with both operands being promoted to a double, and the result would be a double value.

  • What is the data type of x * y?

    
    short x = 10;
    short y = 3;
    var z = x * y;
    
    
  • On the last line, we must apply the third rule, namely, that x and y will both be promoted to int before the binary multiplication operation, resulting in an output of type int. If you were to try to assign the value to a short variable without casting, the code would not compile. Pay close attention to the fact that the resulting output is not a short, as we’ll come back to this example in the upcoming “Assigning Values” section.

  • What is the data type of w * x / y?

    
    short w = 14;
    float x = 13;
    double y = 30;
    var z = w * x / y;
    
    
  • In this case, we must apply all of the rules. First, w will automatically be promoted to int solely because it is a short and it is being used in an arithmetic binary operation. The promoted w value will then be automatically promoted to a float so that it can be multiplied with x. The result of w * x will then be automatically promoted to a double so that it can be divided by y, resulting in a double value.

When working arithmetic operators in Java, you should always be aware of the data type of variables, intermediate values, and resulting values. You should apply operator precedence and parentheses and work outward, promoting data types along the way. In the next section, we’ll discuss the intricacies of assigning these values to variables of a particular type.

Assigning Values

Compilation errors from assignment operators are often overlooked on the exam, in part because of how subtle these errors can be. To master the assignment operators, you should be fluent in understanding how the compiler handles numeric promotion and when casting is required. Being able to spot these issues is critical to passing the exam, as assignment operators appear in nearly every question with a code snippet.

Assignment Operator

An assignment operator is a binary operator that modifies, or assigns, the variable on the left side of the operator, with the result of the value on the right side of the equation. The simplest assignment operator is the = assignment, which you have seen already:


int herd = 1;

This statement assigns the herd variable the value of 1.

Java will automatically promote from smaller to larger data types, as you saw in the previous section on arithmetic operators, but it will throw a compiler exception if it detects that you are trying to convert from larger to smaller data types without casting. Table 3.4 lists the first assignment operator that you need to know for the exam. We will present additional assignment operators later in this section.

Table 3.4 Simple assignment operator
Table 3.4 Simple assignment operator

Casting Values

Seems easy so far, right? Well, we can’t really talk about the assignment operator in detail until we’ve covered casting. Casting is a unary operation where one data type is explicitly interpreted as another data type. Casting is optional and unnecessary when converting to a larger or widening data type, but it is required when converting to a smaller or narrowing data type. Without casting, the compiler will generate an error when trying to put a larger data type inside a smaller one.

Casting is performed by placing the data type, enclosed in parentheses, to the left of the value you want to cast. Here are some examples of casting:


int fur = (int)5;
int hair = (short) 2;
String type = (String) "Bird";
short tail = (short)(4 + 10);
long feathers = 10(long);   // DOES NOT COMPILE

Spaces between the cast and the value are optional. As shown in the second-to-last example, it is common for the right side to also be in parentheses. Since casting is a unary operation, it would only be applied to the 4 if we didn’t enclose 4 + 10 in parentheses. The last example does not compile because the type is on the wrong side of the value.

On the one hand, it is convenient that the compiler automatically casts smaller data types to larger ones. On the other hand, it makes for great exam questions when they do the opposite to see whether you are paying attention. See if you can figure out why none of the following lines of code compile:


float egg = 2.0 / 9;         // DOES NOT COMPILE
int tadpole = (int)5 * 2L;   // DOES NOT COMPILE
short frog = 3 - 2.0;        // DOES NOT COMPILE

All of these examples involve putting a larger value into a smaller data type. Don’t worry if you don’t follow this yet; we will be covering many examples like these in this part of the chapter.

In this chapter, casting is primarily concerned with converting numeric data types into other data types. As you will see in later chapters, casting can also be applied to objects and references. In those cases, though, no conversion is performed, as casting is allowed only if the underlying object is already a member of the class or interface.

And during the exam, remember to keep track of parentheses and return types any time casting is involved!

Reviewing Primitive Assignments

Let’s return to some examples similar to what you saw in Chapter 2 to show how casting can resolve these issues:


int fish = 1.0;                      // DOES NOT COMPILE
short bird = 1921222;                // DOES NOT COMPILE
int mammal = 9f;                     // DOES NOT COMPILE
long reptile = 192301398193810323;   // DOES NOT COMPILE

The first statement does not compile because you are trying to assign a double 1.0 to an integer value. Even though the value is a mathematic integer, by adding .0, you’re instructing the compiler to treat it as a double. The second statement does not compile because the literal value 1921222 is outside the range of short and the compiler detects this. The third statement does not compile because of the f added to the end of the number that instructs the compiler to treat the number as a floating-point value, but the assignment is to an int. Finally, the last statement does not compile because Java interprets the literal as an int and notices that the value is larger than int allows. The literal would need a postfix L or l to be considered a long.

Applying Casting

We can fix the previous set of examples by casting the results to a smaller data type. Remember, casting primitives is required any time you are going from a larger numerical data type to a smaller numerical data type, or converting from a floating-point number to an integral value.


int trainer = (int)1.0;
short ticketTaker = (short)1921222; // Stored as 20678
int usher = (int)9f;
long manager = 192301398193810323L;

Overflow and Underflow

The expressions in the previous example now compile, although there’s a cost. The second value, 1,921,222, is too large to be stored as a short, so numeric overflow occurs and it becomes 20,678. Overflow is when a number is so large that it will no longer fit within the data type, so the system “wraps around” to the lowest negative value and counts up from there, similar to how modulus arithmetic works. There’s also an analogous underflow, when the number is too low to fit in the data type, such as storing -200 in a byte field.

This is beyond the scope of the exam, but something to be careful of in your own code. For example, the following statement outputs a negative number:


System.out.print(2147483647+1);   // -2147483648

Since 2147483647 is the maximum int value, adding any strictly positive value to it will cause it to wrap to the smallest negative number.

Let’s return to a similar example from the “Numeric Promotion” section earlier in the chapter.


short mouse = 10;
short hamster = 3;
short capybara = mouse * hamster; // DOES NOT COMPILE

Based on everything you have learned up until now about numeric promotion and casting, do you understand why the last line of this statement will not compile? As you may remember, short values are automatically promoted to int when applying any arithmetic operator, with the resulting value being of type int. Trying to assign a short variable with an int value results in a compiler error, as Java thinks you are trying to implicitly convert from a larger data type to a smaller one.

We can fix this expression by casting, as there are times that you may want to override the compiler’s default behavior. In this example, we know the result of 10 * 3 is 30, which can easily fit into a short variable, so we can apply casting to convert the result back to a short.


short mouse = 10;
short hamster = 3;
short capybara = (short)(mouse * hamster);

By casting a larger value into a smaller data type, you are instructing the compiler to ignore its default behavior. In other words, you are telling the compiler that you have taken additional steps to prevent overflow or underflow. It is also possible that in your particular application and scenario, overflow or underflow would result in acceptable values.

Last but not least, casting can appear anywhere in an expression, not just on the assignment. For example, let’s take a look at a modified form of the previous example:


short mouse = 10;
short hamster = 3;
short capybara = (short)mouse * hamster; // DOES NOT COMPILE
short gerbil = 1 + (short)(mouse * hamster); // DOES NOT COMPILE

So, what’s going on in the last two lines? Well, remember when we said casting was a unary operation? That means the cast in the first line is applied to mouse, and mouse alone. After the cast is complete, both operands are promoted to int since they are used with the binary multiplication operator (*), making the result an int and causing a compiler error.

In the second example, casting is performed successfully, but the resulting value is automatically promoted to int because it is used with the binary arithmetic operator (+).

Compound Assignment Operators

Besides the simple assignment operator (=) Java supports numerous compound assignment operators. For the exam, you should be familiar with the compound operators in Table 3.5.

Table 3.5 Compound assignment operators
Table 3.5 Compound assignment operators

Complex operators are really just glorified forms of the simple assignment operator, with a built-in arithmetic or logical operation that applies the left and right sides of the statement and stores the resulting value in the variable on the left side of the statement. For example, the following two statements after the declaration of camel and giraffe are equivalent when run independently:


int camel = 2, giraffe = 3;
camel = camel * giraffe;   // Simple assignment operator
camel *= giraffe;          // Compound assignment operator

The left side of the compound operator can be applied only to a variable that is already defined and cannot be used to declare a new variable. In this example, if camel were not already defined, then the expression camel *= giraffe would not compile.

Compound operators are useful for more than just shorthand-they can also save us from having to explicitly cast a value. For example, consider the following example. Can you figure out why the last line does not compile?


long goat = 10;
int sheep = 5;
sheep = sheep * goat;   // DOES NOT COMPILE

From the previous section, you should be able to spot the problem in the last line. We are trying to assign a long value to an int variable. This last line could be fixed with an explicit cast to (int), but there’s a better way using the compound assignment operator:


long goat = 10;
int sheep = 5;
sheep *= goat;

The compound operator will first cast sheep to a long, apply the multiplication of two long values, and then cast the result to an int. Unlike the previous example, in which the compiler reported an error, in this example we see that the compiler will automatically cast the resulting value to the data type of the value on the left side of the compound operator.

Assignment Operator Return Value

One final thing to know about assignment operators is that the result of an assignment is an expression in and of itself, equal to the value of the assignment. For example, the following snippet of code is perfectly valid, if not a little odd-looking:


long wolf = 5;
long coyote = (wolf=3);
System.out.println(wolf);     // 3
System.out.println(coyote);   // 3

The key here is that (wolf=3) does two things. First, it sets the value of the variable wolf to be 3. Second, it returns a value of the assignment, which is also 3.

The exam creators are fond of inserting the assignment operator (=) in the middle of an expression and using the value of the assignment as part of a more complex expression. For example, don’t be surprised if you see an if statement on the exam similar to the following:


boolean healthy = false;
if(healthy = true)
System.out.print("Good!");

While this may look like a test if healthy is true, it’s actually assigning healthy a value of true. The result of the assignment is the value of the assignment, which is true, resulting in this snippet printing Good!. We’ll cover this in more detail in the upcoming “Equality Operators” section.

Comparing Values

The last set of binary operators revolves around comparing values. They can be used to check if two values are the same, check if one numeric value is less than or greater than another, and perform boolean arithmetic. Chances are you have used many of the operators in this section in your development experience.

Equality Operators

Determining equality in Java can be a nontrivial endeavor as there’s a semantic difference between “two objects are the same” and “two objects are equivalent.” It is further complicated by the fact that for numeric and boolean primitives, there is no such distinction.

Table 3.6 lists the equality operators. The equals operator (==) and not equals operator (!=) compare two operands and return a boolean value determining whether the expressions or values are equal or not equal, respectively.

Table 3.6 Equality operators
Table 3.6 Equality operators

The equality operators are used in one of three scenarios:

  • Comparing two numeric or character primitive types. If the numeric values are of different data types, the values are automatically promoted. For example, 5 == 5.00 returns true since the left side is promoted to a double.
  • Comparing two boolean values
  • Comparing two objects, including null and String values

The comparisons for equality are limited to these three cases, so you cannot mix and match types. For example, each of the following would result in a compiler error:


boolean monkey = true == 3; // DOES NOT COMPILE
boolean ape = false != "Grape"; // DOES NOT COMPILE
boolean gorilla = 10.2 == "Koko"; // DOES NOT COMPILE

Pay close attention to the data types when you see an equality operator on the exam. As we mentioned in the previous section, the exam creators also have a habit of mixing assignment operators and equality operators.


boolean bear = false;
boolean polar = (bear = true);
System.out.println(polar); // true

At first glance, you might think the output should be false, and if the expression were (bear == true), then you would be correct. In this example, though, the expression is assigning the value of true to bear, and as you saw in the section on assignment operators, the assignment itself has the value of the assignment. Therefore, polar is also assigned a value of true, and the output is true.

For object comparison, the equality operator is applied to the references to the objects, not the objects they point to. Two references are equal if and only if they point to the same object or both point to null. Let’s take a look at some examples:


File monday = new File("schedule.txt");
File tuesday = new File("schedule.txt");
File wednesday = tuesday;
System.out.println(monday == tuesday);      // false
System.out.println(tuesday == wednesday);   // true

Even though all of the variables point to the same file information, only two references, tuesday and wednesday, are equal in terms of == since they point to the same object.

NOTE

Wait, what’s the File class? In this example, as well as during the exam, you may be presented with class names that are unfamiliar, such as File. Many times you can answer questions about these classes without knowing the specific details of these classes. In the previous example, you should be able to answer questions that indicate monday and tuesday are two separate and distinct objects because the new keyword is used, even if you are not familiar with the data types of these objects.

In some languages, comparing null with any other value is always false, although this is not the case in Java.


System.out.print(null == null);   // true

In Chapter 5, we’ll continue the discussion of object equality by introducing what it means for two different objects to be equivalent. We’ll also cover String equality and show how this can be a nontrivial topic.

Relational Operators

We now move on to relational operators, which compare two expressions and return a boolean value. Table 3.7 describes the relational operators you need to know for the exam.

Table 3.7 Relational operators
Table 3.7 Relational operators

Numeric Comparison Operators

The first four relational operators in Table 3.7 apply only to numeric values. If the two numeric operands are not of the same data type, the smaller one is promoted as previously discussed.

Let’s look at examples of these operators in action:


int gibbonNumFeet = 2, wolfNumFeet = 4, ostrichNumFeet = 2;
System.out.println(gibbonNumFeet < wolfNumFeet);      // true
System.out.println(gibbonNumFeet <= wolfNumFeet);     // true
System.out.println(gibbonNumFeet >= ostrichNumFeet);  // true
System.out.println(gibbonNumFeet > ostrichNumFeet);   // false

Notice that the last example outputs false, because although gibbonNumFeet and ostrichNumFeet have the same value, gibbonNumFeet is not strictly greater than ostrichNumFeet.

instanceof Operator

The final relational operator you need to know for the exam is the instanceof operator, shown in Table 3.7. It is useful for determining whether an arbitrary object is a member of a particular class or interface at runtime.

Why wouldn’t you know what class or interface an object is? As we will get into in Chapter 8, “Class Design,” Java supports polymorphism. For now, that just means some objects can be passed around using a variety of references. For example, all classes inherit from java.lang.Object. This means that any instance can be assigned to an Object reference. For example, how many objects are created and used in the following code snippet?


Integer zooTime = Integer.valueOf(9);
Number num = zooTime;
Object obj = zooTime;

In this example, there is only one object created in memory but three different references to it because Integer inherits both Number and Object. This means that you can call instanceof on any of these references with three different data types and it would return true for each of them.

Where polymorphism often comes into play is when you create a method that takes a data type with many possible subclasses. For example, imagine we have a function that opens the zoo and prints the time. As input, it takes a Number as an input parameter.


public void openZoo(Number time) {}

Now, we want the function to add O'clock to the end of output if the value is a whole number type, such as an Integer; otherwise, it just prints the value.


public static void openZoo(Number time) {
  if(time instanceof Integer)
    System.out.print((Integer)time + " O'clock");
  else
    System.out.print(time);
}

We now have a method that can intelligently handle both Integer and other values. A good exercise left for the reader is to add checks for other numeric data types

Notice that we cast the Integer value in this example. It is common to use casting and instanceof together when working with objects that can be various different types, since it can give you access to fields available only in the more specific classes. It is considered a good coding practice to use the instanceof operator prior to casting from one object to a narrower type.

Invalid instanceof

One area the exam might try to trip you up on is using instanceof with incompatible types. For example, Number cannot possibly hold a String value, so the following would cause a compilation error:


public static void openZoo(Number time) {
  if(time instanceof String) // DOES NOT COMPILE
  ...

It gets even more complicated as the previous rule applies to classes, but not interfaces. Don’t worry if this is all new to you; we will go into more detail when we discuss polymorphism in Chapter 9, “Advanced Class Design.”

null and the instanceof operator

What happens if you call instanceof on a null variable? For the exam, you should know that calling instanceof on the null literal or a null reference always returns false.


System.out.print(null instanceof Object);

Object noObjectHere = null;
System.out.print(noObjectHere instanceof String);

The preceding examples both print false. It almost doesn’t matter what the right side of the expression is. We say “almost” because there are exceptions. The last example does not compile, since null is used on the right side of the instanceof operator:


System.out.print(null instanceof null);   // DOES NOT COMPILE

Logical Operators

If you have studied computer science, you may have already come across logical operators before. If not, no need to panic-we’ll be covering them in detail in this section.

The logical operators, (&), (|), and (^), may be applied to both numeric and boolean data types; they are listed in Table 3.8. When they’re applied to boolean data types, they’re referred to as logical operators. Alternatively, when they’re applied to numeric data types, they’re referred to as bitwise operators, as they perform bitwise comparisons of the bits that compose the number. For the exam, though, you don’t need to know anything about numeric bitwise comparisons, so we’ll leave that educational aspect to other books.

Table 3.8 Logical operators
Table 3.8 Logical operators

You should familiarize yourself with the truth tables in Figure 3.1, where x and y are assumed to be boolean data types.

Figure 3.1 The logical truth tables for &, |, and ^
Figure 3.1 The logical truth tables for &, |, and ^

Here are some tips to help you remember this table:

  • AND is only true if both operands are true.
  • Inclusive OR is only false if both operands are false.
  • Exclusive OR is only true if the operands are different.

Let’s take a look at some examples:


boolean eyesClosed = true;
boolean breathingSlowly = true;

boolean resting = eyesClosed | breathingSlowly;
boolean asleep = eyesClosed & breathingSlowly;
boolean awake = eyesClosed ^ breathingSlowly;
System.out.println(resting);  // true
System.out.println(asleep);   // true
System.out.println(awake);    // false

You should try these out yourself, changing the values of eyesClosed and breathingSlowly and studying the results.

Short-Circuit Operators

Next, we present the conditional operators, && and ||, which are often referred to as short-circuit operators and are shown in Table 3.9.

Table 3.9 Short-circuit operators
Table 3.9 Short-circuit operators

The short-circuit operators are nearly identical to the logical operators, & and |, except that the right side of the expression may never be evaluated if the final result can be determined by the left side of the expression. For example, consider the following statement:


int hour = 10;
boolean zooOpen = true || (hour < 4);
System.out.println(zooOpen); // true

Referring to the truth tables, the value zooOpen can be false only if both sides of the expression are false. Since we know the left side is true, there’s no need to evaluate the right side, since no value of hour will ever make this code print false. In other words, hour could have been -10 or 892; the output would have been the same. Try it yourself with different values for hour!

Avoiding a NullPointerException

A more common example of where short-circuit operators are used is checking for null objects before performing an operation. In the following example, if duck is null, then the program will throw a NullPointerException at runtime:


if(duck!=null & duck.getAge()<5) {  // Could throw a NullPointerException
  // Do something
}

The issue is that the logical AND (&) operator evaluates both sides of the expression. We could add a second if statement, but this could get unwieldy if we have a lot of variables to check. An easy-to-read solution is to use the short-circuit AND operator (&&):


if(duck!=null && duck.getAge()<5) {
  // Do something
}

In this example, if duck was null, then the short-circuit prevents a NullPointerException from ever being thrown, since the evaluation of duck.getAge() < 5 is never reached.

Checking for Unperformed Side Effects

Be wary of short-circuit behavior on the exam, as questions are known to alter a variable on the right side of the expression that may never be reached. This is referred to as an unperformed side ef ect. For example, what is the output of the following code?


int rabbit = 6;
boolean bunny = (rabbit >= 6) || (++rabbit <= 7);
System.out.println(rabbit);

Because rabbit >= 6 is true, the increment operator on the right side of the expression is never evaluated, so the output is 6.

Making Decisions with the Ternary Operator

The final operator you should be familiar with for the exam is the conditional operator, ? :, otherwise known as the ternary operator. It is notable in that it is the only operator that takes three operands. The ternary operator has the following form:


booleanExpression ? expression1 : expression2

The first operand must be a boolean expression, and the second and third operands can be any expression that returns a value. The ternary operation is really a condensed form of a combined if and else statement that returns a value. We will be covering if/else statements in a lot more detail in Chapter 4, “Making Decisions,” so for now we will just use simple examples.

For example, consider the following code snippet that calculates the food amount for an owl:


int owl = 5;
int food;
if(owl < 2) {
  food = 3;
} else {
  food = 4;
}
System.out.println(food);  // 4

Compare the previous code snippet with the following ternary operator code snippet:


int owl = 5;
int food = owl < 2 ? 3 : 4;
System.out.println(food); // 4

These two code snippets are equivalent to each other. Note that it is often helpful for readability to add parentheses around the expressions in ternary operations, although it is certainly not required.


int food = (owl < 2) ? 3 : 4;

For the exam, you should know that there is no requirement that second and third expressions in ternary operations have the same data types, although it does come into play when combined with the assignment operator. Compare the two statements following the variable declaration:


int stripes = 7;
System.out.print((stripes > 5) ? 21 : "Zebra");
int animal = (stripes < 9) ? 3 : "Horse"; // DOES NOT COMPILE

Both expressions evaluate similar boolean values and return an int and a String, although only the first one will compile. System.out.print() does not care that the expressions are completely different types, because it can convert both to Object values and call toString() on them. On the other hand, the compiler does know that "Horse" is of the wrong data type and cannot be assigned to an int; therefore, it will not allow the code to be compiled.

Ternary Expression and Unperformed Side Effects

Like we saw with the short-circuit operator, a ternary expression can contain an unperformed side effect, as only one of the expressions on the right side will be evaluated at runtime. Let’s illustrate this principle with the following example:


int sheep = 1;
int zzz = 1;
int sleep = zzz<10 ? sheep++ : zzz++;
System.out.print(sheep+","+zzz); // 2,1

Notice that since the left-hand boolean expression was true, only sheep was incremented. Contrast the preceding example with the following modification:


int sheep = 1;
int zzz = 1;
int sleep = sheep>=10 ? sheep++ : zzz++;
System.out.print(sheep+","+zzz); // 1,2

Now that the left-hand boolean expression evaluates to false, only zzz was incremented. In this manner, we see how the expressions in a ternary operator may not be applied if the particular expression is not used.

For the exam, be wary of any question that includes a ternary expression in which a variable is modified in one of the right-hand side expressions.

Summary

This chapter covered a wide variety of Java operator topics for unary, binary, and ternary operators. Hopefully, most of these operators were review for you. If not, you’ll need to study them in detail. It is important that you understand how to use all of the required Java operators covered in this chapter and know how operator precedence and parentheses influence the way a particular expression is interpreted.

There will likely be numerous questions on the exam that appear to test one thing, such as StringBuilder or exception handling, when in fact the answer is related to the misuse of a particular operator that causes the application to fail to compile. When you see an operator involving numbers on the exam, always check that the appropriate data types are used and that they match each other where applicable.

Operators are used throughout the exam, in nearly every code sample, so the better you understand this chapter, the more prepared you will be for the exam.

Exam Essentials

Be able to write code that uses Java operators. This chapter covered a wide variety of operator symbols. Go back and review them several times so that you are familiar with them throughout the rest of the book.

Be able to recognize which operators are associated with which data types. Some operators may be applied only to numeric primitives, some only to boolean values, and some only to objects. It is important that you notice when an operator and operand(s) are mismatched, as this issue is likely to come up in a couple of exam questions.

Understand when casting is required or numeric promotion occurs. Whenever you mix operands of two different data types, the compiler needs to decide how to handle the resulting data type. When you’re converting from a smaller to a larger data type, numeric promotion is automatically applied. When you’re converting from a larger to a smaller data type, casting is required.

Understand Java operator precedence. Most Java operators you’ll work with are binary, but the number of expressions is often greater than two. Therefore, you must understand the order in which Java will evaluate each operator symbol.

Be able to write code that uses parentheses to override operator precedence. You can use parentheses in your code to manually change the order of precedence.

Review Questions

  1. Which of the following Java operators can be used with boolean variables? (Choose all that apply.)

    1. ==
    2. +
    3. --
    4. !
    5. %
    6. <=
    7. Cast with (boolean)
  2. What data type (or types) will allow the following code snippet to compile? (Choose all that apply.)

    
    byte apples = 5;
    short oranges = 10;
    _______ bananas = apples + oranges;
    
    
    1. int
    2. long
    3. boolean
    4. double
    5. short
    6. byte
  3. What change, when applied independently, would allow the following code snippet to compile? (Choose all that apply.)

    
    3: long ear = 10;
    4: int hearing = 2 * ear;
    
    
    1. No change; it compiles as is.
    2. Cast ear on line 4 to int.
    3. Change the data type of ear on line 3 to short.
    4. Cast 2 * ear on line 4 to int.
    5. Change the data type of hearing on line 4 to short.
    6. Change the data type of hearing on line 4 to long.
  4. What is the output of the following code snippet?

    
    3: boolean canine = true, wolf = true;
    4: int teeth = 20;
    5: canine = (teeth != 10) ^ (wolf=false);
    6: System.out.println(canine+", "+teeth+", "+wolf);
    
    
    1. true, 20, true
    2. true, 20, false
    3. false, 10, true
    4. false, 20, false
    5. The code will not compile because of line 5.
    6. None of the above
  5. Which of the following operators are ranked in increasing or the same order of precedence? Assume the + operator is binary addition, not the unary form. (Choose all that apply.)

    1. +, *, %, --
    2. ++, (int), *
    3. =, ==, !
    4. (short), =, !, *
    5. *, /, %, +, ==
    6. !, ||, &
    7. ^, +, =, +=
  6. What is the output of the following program?

    
    1: public class CandyCounter {
    2: static long addCandy(double fruit, float vegetables)
    {
    3: return (int)fruit+vegetables;
    4: }
    5:
    6: public static void main(String[] args) {
    7: System.out.print(addCandy(1.4, 2.4f) + "-");
    8: System.out.print(addCandy(1.9, (float)4) + "-");
    9: System.out.print(addCandy((long)(int)(short)2,
    (float)4)); } }
    
    
    1. 4-6-6.0
    2. 3-5-6
    3. 3-6-6
    4. 4-5-6
    5. The code does not compile because of line 9.
    6. None of the above
  7. What is the output of the following code snippet?

    
    int ph = 7, vis = 2;
    boolean clear = vis > 1 & (vis < 9 || ph < 2);
    boolean safe = (vis > 2) && (ph++ > 1);
    boolean tasty = 7 <= --ph;
    System.out.println(clear+"-"+safe+"-"+tasty);
    
    
    1. true-true-true
    2. true-true-false
    3. true-false-true
    4. true-false-false
    5. false-true-true
    6. false-true-false
    7. false-false-true
    8. false-false-false
  8. What is the output of the following code snippet?

    
    4: int pig = (short)4;
    5: pig = pig++;
    6: long goat = (int)2;
    7: goat -= 1.0;
    8: System.out.print(pig + " - " + goat);
    
    
    1. 4 - 1
    2. 4 - 2
    3. 5 - 1
    4. 5 - 2
    5. The code does not compile due to line 7.
    6. None of the above
  9. What are the unique outputs of the following code snippet? (Choose all that apply.)

    
    int a = 2, b = 4, c = 2;
    System.out.println(a > 2 ? --c : b++);
    System.out.println(b = (a!=c ? a : b++));
    System.out.println(a > b ? b < c ? b : 2 : 1);
    
    
    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. The code does not compile.
  10. What are the unique outputs of the following code snippet? (Choose all that apply.)

    
    short height = 1, weight = 3;
    short zebra = (byte) weight * (byte) height;
    double ox = 1 + height * 2 + weight;
    long giraffe = 1 + 9 % height + 1;
    System.out.println(zebra);
    System.out.println(ox);
    System.out.println(giraffe);
    
    
    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. The code does not compile.
  11. What is the output of the following code?

    
    1: public class ArithmeticSample {
    2: public static void main(String[] args) {
    3: int sample1 = (2 * 4) % 3;
    4: int sample2 = 3 * 2 % 3;
    5: int sample3 = 5 * (1 % 2);
    6: System.out.println(sample1+"-"+sample2+"-"+sample3);
    7: }}
    
    
    1. 0-0-5
    2. 1-2-10
    3. 2-1-5
    4. 2-0-5
    5. 3-1-10
    6. 3-2-6
    7. The code does not compile.
  12. The ________ operator increases a value and returns the original value, while the ________ operator decreases a value and returns the new value.

    1. post-increment, post-increment
    2. pre-decrement, post-decrement
    3. post-increment, post-increment
    4. post-increment, pre-decrement
    5. pre-increment, pre-decrement
    6. pre-increment, post-decrement
  13. What is the output of the following code snippet?

    
    boolean sunny = true, raining = false, sunday = true;
    boolean goingToTheStore = sunny & raining ^ sunday;
    boolean goingToTheZoo = sunday && !raining;
    boolean stayingHome = !(goingToTheStore &&
    goingToTheZoo);
    System.out.println(goingToTheStore + "-" + goingToTheZoo
    + "-" +stayingHome);
    
    
    1. true-false-false
    2. false-true-false
    3. true-true-true
    4. false-true-true
    5. false-false-false
    6. true-true-false
    7. None of the above
  14. Which of the following statements are correct? (Choose all that apply.)

    1. The return value of an assignment operation expression can be void.
    2. The inequality operator (!=) can be used to compare objects.
    3. The equality operator (==) can be used to compare a boolean value with a numeric value.
    4. During runtime, the && and | operators may cause only the left side of the expression to be evaluated.
    5. The return value of an assignment operation expression is the value of the newly assigned variable.
    6. In Java, 0 and false may be used interchangeably.
    7. The logical complement operator (!) cannot be used to flip numeric values.
  15. Which operators take three operands or values? (Choose all that apply.)

    1. =
    2. &&
    3. *=
    4. ? :
    5. &
    6. ++
    7. /
  16. How many lines of the following code contain compiler errors?

    
    int note = 1 * 2 + (long)3;
    short melody = (byte)(double)(note *= 2);
    double song = melody;
    float symphony = (float)((song == 1_000f) ? song * 2L :
    song);
    
    
    1. 0
    2. 1
    3. 2
    4. 3
    5. 4
  17. Given the following code snippet, what is the value of the variables after it is executed? (Choose all that apply.)

    
    int ticketsTaken = 1;
    int ticketsSold = 3;
    ticketsSold += 1 + ticketsTaken++;
    ticketsTaken *= 2;
    ticketsSold += (long)1;
    
    
    1. ticketsSold is 8
    2. ticketsTaken is 2
    3. ticketsSold is 6
    4. ticketsTaken is 6
    5. ticketsSold is 7
    6. ticketsTaken is 4
    7. The code does not compile.
  18. Which of the following can be used to change the order of operation in an expression? (Choose all that apply.)

    1. [ ]
    2. < >
    3. ( )
    4. /
    5. { }
    6. " "
  19. What is the result of executing the following code snippet? (Choose all that apply.)

    
    3: int start = 7;
    4: int end = 4;
    5: end += ++start;
    6: start = (byte)(Byte.MAX_VALUE + 1);
    
    
    1. start is 0
    2. start is -128
    3. start is 127
    4. end is 8
    5. end is 11
    6. end is 12
    7. The code does not compile.
    8. The code compiles but throws an exception at runtime.
  20. Which of the following statements about unary operators are true? (Choose all that apply.)

    1. Unary operators are always executed before any surrounding binary or ternary operators.
    2. The - operator can be used to flip a boolean value.
    3. The pre-increment operator (++) returns the value of the variable before the increment is applied.
    4. The post-decrement operator (--) returns the value of the variable before the decrement is applied.
    5. The ! operator cannot be used on numeric values.
    6. None of the above
UP

LIMITED OFFER: GET 30% Discount

This is ONE TIME OFFER

ExamSnap Discount Offer
Enter Your Email Address to Receive Your 30% Discount Code

A confirmation link will be sent to this email address to verify your login. *We value your privacy. We will not rent or sell your email address.

Download Free Demo of VCE Exam Simulator

Experience Avanset VCE Exam Simulator for yourself.

Simply submit your e-mail address below to get started with our interactive software demo of your free trial.

Free Demo Limits: In the demo version you will be able to access only first 5 questions from exam.