by Joe Weber with Scott Williams, Jay Cross, and Mike Afergan
Expressions--combinations of operators and operands--are one of the key building blocks of the Java language, as they are of many programming languages. Expressions allow you to perform arithmetic calculations, concatenate strings, compare values, perform logical operations, and manipulate objects. Without expressions, a programming language is dead--useless and lifeless.
You've already seen some expressions, mostly fairly simple ones, in other chapters in this book. Chapter 8, "Data Types and Other Tokens," in particular showed you that operators--one of the two key elements in an expression--form one of the main classifications of Java tokens, along with such things as keywords, comments, and so on. In this chapter, you take a closer look at how you can use operators to build expressions--in other words, how to put operators to work for you.
There are all kinds of technical definitions of what an expression is, but at its simplest, an expression is what results when operands and operators are joined together. Expressions are usually used to perform operations--manipulations--on variables or values. In Table 9.1, you see several legal Java expressions.
Name of Expression | Example |
Additive expression | x+5 |
Assignment expression | x=5 |
Array indexing | sizes[11] |
Method invocation | Triangle.RotateLeft(50) |
When an expression is simple, like those shown in Table 9.1, figuring out the result of the expression is easy. When the expression becomes more detailed and more than one operator is used, things get more complicated.
In Chapter 7, "Data Types and Other Tokens," you learned that expressions are just combinations of operators and operands. And while that definition may be true, it's not always very helpful. Sometimes you need to create and use pretty complex expressions--maybe to perform some kind of complicated calculation or other involved manipulation. To do this, you need a deeper understanding of how Java expressions are created and evaluated. In this section, you look at three major tools that will help you in your work with Java expressions: operator associativity, operator precedence, and order of evaluation.
The easiest of the expression rules is associativity. All the arithmetic operators are said to associate left-to-right. This means that if the same operator appears more than once in an expression--as the plus in a+b+c does--then the leftmost occurrence is evaluated first, followed by the one to its right, and so on. Consider the following assignment statement:
x = a+b+c;
In this example, the value of the expression on the right of the = is calculated and assigned to the variable x on the left. In calculating the value on the right, the fact that the + operator associates left-to-right means that the value of a+b is calculated first, and the result is then added to c. The result of that second calculation is what is assigned to x. So if you were to write it using explicit parentheses, the line would read:
x=((a+b)+c);
NOTE: Notice that in the previous example, a+b+c, the same operator appears twice. It's when the same operator appears more than once--as it does in this case--that you apply the associativity rule.
You would use the associativity rule in evaluating the right sides of each of the following assignment statements:
volume = length * width * height ; OrderTotal = SubTotal + Freight + Taxes ; PerOrderPerUnit = Purchase / Orders / Units ;
Of these expressions, only the last one would result in a different way if you associated the expression incorrectly. The correct answer for this expression is
(Purchase / Orders)/ Units
However, evaluated incorrectly the result would be
Purchase / (Orders/Units)
which can also be written as
(Purchase * Units)/ Orders
which is obviously not the same as the correct expression.
When you have an expression that involves different operators, the associativity rule doesn't apply, because the associativity rule only helps figure out how combinations of the same operator would be evaluated. Now you need to know how expressions using combinations of different operators are evaluated.
Precedence helps to determine which operator to act on first. If you write A+B*C, by standard mathematics you would first multiply B and C and then add the result to A. Precedence helps the computer to do the same thing. The multiplicative operators (*, /, and %) have higher precedence than the additive operators (+ and -). So, in a compound expression that incorporates both multiplicative and additive operators, the multiplicative operators are evaluated first.
Consider the following assignment statement, which is intended to convert a Fahrenheit temperature to Celsius:
Celsius = Fahrenheit - 32 * 5 / 9;
The correct conversion between Celsius and Fahrenheit is that the degrees Celsius are equal degrees Fahrenheit -32 all of that times 5/9. However, in the equation, because the * and / operators have higher precedence, the sub-expression 32*5/9 is evaluated first (yielding the result 17) and that value is subtracted from the Fahrenheit variable.
To correctly write this equation, and whenever you need to change the order of evaluation of operators in an expression, you can use parentheses. Any expression within parentheses is evaluated first. To perform the correct conversion for the preceding example, you would write:
Celsius = ( Fahrenheit - 32 ) * 5 / 9;
NOTE: Interestingly, there are some computer languages that do not use rules of precedence. Some languages, like APL for example, use a straight left-to-right or right-to-left order of evaluation, regardless of the operators involved.
NewAmount = (Savings + Cash) * ExchangeRate ; TotalConsumption = (Distance2 - Distance1) * ConsumptionRate ;
The precedence of the unary arithmetic operators--in fact all unary operators--is very high; it's above all the other arithmetic operators. In the following example, you multiply the value -5 times the value of Xantham, and not Xantham times five negated (although the results are the same):
Ryman = -5 * Xantham;
Table 9.2 is what is known as the precedence table. The operators with the highest precedence are at the top. Operators on the same line are of equal precedence.
All these operators associate left-to-right, except the unary operators, assignments, and the conditional. For any single operator, operand evaluation is strictly left-to-right, and all operands are evaluated before operations are performed.
Description | Operators |
High Precedence | . [] () |
Unary | + - ~ ! ++ - - instance of |
Multiplicative | * / % |
Additive | + - |
Shift | << >> >>> |
Relational | < <= >= > > |
Equality | == != |
Bitwise AND | & |
Bitwise XOR | ^ |
Bitwise OR | | |
Conditional-AND | && |
Conditional-OR | || |
Conditional | ?: |
Assignment | = op= |
Many people, when they first learn a language, confuse the issue of operator precedence with order of evaluation. The two are actually quite different. The precedence rules help you determine which operators come first in an expression and what the operands are for an operator. For example, in the following line of code, the operands of the * operator are a and (b+c):
d = a * (b+c) ;
The order of evaluation rules, on the other hand, help you to determine not when operators are evaluated, but when operands are evaluated.
Here are three rules that should help you remember how an expression is evaluated:
Because Java is an evolutionary outgrowth of C and C++, it's understandable that the expression syntax for the three languages is so similar. If you already know C, it's important that you keep in mind that the three languages are only similar--not identical.
One very important difference is that order of evaluation is guaranteed in Java, and is generally undefined or implementation-specific in C.
In Java, the remainder (%), increment (++), and decrement (- -) operators are defined for all primitive data types (except Boolean); in C, they are defined only for integers.
Relational and equality operators in Java produce a Boolean result; in C they produce results of type int. Furthermore, the logical operators in Java are restricted to Boolean operands.
Java supports native operations on strings--including string concatenation and string assignment. C does not have this support for strings.
In C, using the right-shift operator (>>) on a signed quantity results in implementation-specific behavior. Java avoids this confusion by using two different right-shift operators--one which pads with zeroes and the other that does sign-extension.
If you have a number, such as 0x0F2 (which is a hexadecimal number equal to 242), do you know how to get rid of just the 2? Do you know how to find out which of the bits of 0x0F2 are set the same as they are for the number 0x0A1? Bitwise operators allow you to solve these problems easily. (To answer the question: 0x0F2&0x0F0 and 0x0F2&0x0A1).
The bitwise operators are a set of operators that are either very important or completely unimportant to you depending on what you are doing. When you need a bitwise operator, it is rarely the case that you can substitute any other operation to easily reproduce the same results. But, at the same time, it's highly likely that most of the work you will do will not require you to perform such esoteric calculations.
So what are bitwise operators? Bitwise operators work on the fundamental level of how values are stored in a computer. Numbers are stored in sequences of on and off, known as bits, which are most often translated to the binary numbers 1 and 0. A typical variable such as an int has 32 of these 1s and 0s in order to make up a complete number. It is often helpful to be able to manipulate these values directly and bitwise operators are the means to do that.
Let's consider a simple example using bytes. A byte comprises eight bits of memory. Each of the eight bits can have the value of 0 or 1, and the value of the whole quantity is determined by using base 2 arithmetic, meaning that the rightmost bit represents a value of 0 or 1; the next bit represents the value of 0 or 2; the next represents the value 0 or 4, and so on, where each bit has a value of 0 and 2n and n is the bit number. Table 9.3 shows the binary representation of several numbers.
Base 10 | Value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
17 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
63 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
75 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
131 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
16+1 = 17
The numeric quantities in Table 9.3 are all positive integers, and that is on purpose. Negative numbers are a little more difficult to represent. For any integer quantity in Java, except char, the leftmost bit is reserved for the sign-bit. If the sign-bit is 1, then the value is negative. The rest of the bits in a negative number are also determined a little differently, in what is known as two's-complement, but don't worry about that now. Floating-point numbers also have their own special binary representation, but that's beyond the scope of this book.
The three binary bitwise operators perform the logical operations of AND, OR, and Exclusive OR (sometimes called XOR) on each bit in turn. The three operators are:
Each of the operators produces a result based on what is known as a truth table. Each of the operators has a different truth table, and the next three tables show them.
To determine the results of a bitwise operator, it is necessary to take a look at each of the operands as a set of bits and compare the bits to the appropriate truth table.
First Value (A) | Second Value (B) | Resulting Value (A&B) |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
First Value (a) | Second Value (b) | Resulting Value (A|B) |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
First Value (a) | Second Value (b) | Resulting Value (A ^ B) |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Table 9.4 shows the results of each of these operations performed on two sample values. First, you see the Boolean values of the two numbers 11309 and 798, and then the resulting bit sequences after the various bit operators are applied.
Expression | Binary Representation |
11309 | 0010 1100 0010 1101 |
798 | 0000 0011 0001 1110 |
11309 & 798 | 0000 0000 0000 1100 |
11309 | 798 | 0010 1111 0011 1111 |
11309 ^ 798 | 0010 1111 0011 0011 |
There are three shift operators in Java, as follows:
The shift operators move (shift) all of the bits in a number to the left or the right. The left operand is the value to be shifted, while the right operand is the number of bits to shift by, so in the equation
17<<2
the number 17 will be shifted two bits to the left. The left shift and the unsigned right shift populate the vacated spaces with zeroes. The signed right shift populates the vacated spaces with the sign bit. The following table shows two 8-bit quantities, 31 and -17, and what happens when they are shifted:
Quantity | x | x<<2 | x>>2 | x>>>2 |
31 | 00011111 | 01111100 | 00000111 | 00000111 |
-17 | 11101111 | 10111100 | 11111011 | 00111011 |
One very critical aspect of types in general in any language is how they interrelate. In other words, if you have a float such as 1.2, how does that relate to, say, an integer? How does the language handle a situation where a byte (8 bits) is added to an int (32 bits)? To deal with these problems, Java performs type conversions. Java is called a strongly typed language, because at compile time the type of every variable is known. Java performs extensive type-checking (to help detect programmer errors) and imposes strict restrictions on when values can be converted from one type to another.
There are really two different kinds of conversions:
Briefly then, casting and converting are the way that Java allows the use of a
variable of one type to be used in an expression of another type.
NOTE: In C, almost any data type can be converted to almost any other across an assignment statement. This is not the case in Java, and implicit conversions between numeric data types are only performed if they do not result in loss of precision or magnitude. Any attempted conversion that would result in such a loss produces a compiler error, unless there is an explicit cast.
Java performs a number of implicit type conversions when evaluating expressions, but the rules are simpler and more controlled than in the case of C or even C++.
For unary operators (such as ++ or - -), the situation is very simple: Operands of type byte or short are converted to int, and all other types are left as is.
For binary operators, the situation is only slightly more complex. For operations involving only integer operands, if either of the operands is long, then the other is also converted to long; otherwise, both operands are converted to int. The result of the expression is an int, unless the value produced is so large that a long is required. For operations involving at least one floating-point operand, if either of the operands is double, then the other is also converted to double and the result of the expression is also a double; otherwise, both operands are converted to float, and the result of the expression is also a float. Consider the expressions in Listing 9.1.
Fortunately, implicit conversions take place almost always without your wanting or needing to know. The compiler handles all the details of adding bytes and ints together so you don't have to.
short Width; long Length, Area; double TotalCost, CostPerFoot; // In the multiplication below, Width will be converted to a // long, and the result of the calculation will be a long. Area = Length * Width; // In the division below, Area will be converted to a double, // and the result of the calculation will be a double. CostPerFoot = TotalCost / Area ;
Normally with implicit conversion, the conversion is so natural that you don't even notice. Sometimes, though, it is important to make sure a conversion occurs between two types. Doing this type of conversion requires an explicit cast, by using the cast operator.
The cast operator consists of a type name within round brackets. It is a unary operator with high precedence and comes before its operand, the result of which is a variable of the type specified by the cast, but which has the value of the original object. The following example shows an example of an explicit cast:
float x = 2.0; float y = 1.7; x - ( (int)(x/y) * y)
When x is divided by y in this example, the type of the result is a floating-point number. However, value of x/y is explicitly converted to type int by the cast operator, resulting in a 1, not 1.2. So the end result of this equation is that x equals 1.7.
Not all conversions are legal. For instance, Boolean values cannot be cast to any other type, and objects can only be converted to a parent class.
See "Declaring a Class," Chapter 11
NOTE: Because casting involves an unconditional type conversion (if the conversion is legal), it is also sometimes known as type coercion.
The four integer types can be cast to any other type except Boolean. However, casting into a smaller type can result in a loss of data, and a cast to a floating-point number (float or double) will probably result in the loss of some precision, unless the integer is a whole power of two (for example, 1, 2, 4, 8,...).
Characters can be cast in the same way 16-bit (short) integers are cast; that is, you can cast it to be anything. But, if you cast into a smaller type (byte), you lose some data. In fact, even if you convert between a character and a short, you can lose some data.
If you are using the Han character set (Chinese, Japanese, or Korean), you can lose data by casting a char into a short (16-bit integer), because the top bit will be lost.
There are not any direct ways to cast or convert a Boolean to any other type. However, if you are intent on getting an integer to have a 0 or 1 value based on the current value of a Boolean, use an if-else statement, or imitate the following code:
int j; boolean tf; ... j = tf?1:0; // Integer j gets 1 if tf is true, and 0 otherwise.
Conversion the other way can be done with zero to be equal to false, and anything else equal to true as follows:
int j; boolean tf; ... tf = (j!=0); // Boolean tf is true if j is not 0, false otherwise.
Before you can finally leave the subject of operators, it is important to also cover a special use of the addition operator as it relates to strings.
In Java, the concatenation of strings is supported using the + operator. The behavior of the + operator with strings is just what you'd expect, if you're familiar with C++. The first and second string are concatenated to produce a string that contains the values of both. In the following expression, the resulting string would be "Hello World":
"Hello" + " World"
If a non-string value is added to a string, it is first converted to a string using implicit typecasting before the concatenation takes place. This means, for example, that a numeric value can be added to a string. The numeric value is converted to an appropriate sequence of digit characters, which are concatenated to the original string. All the following are legal string concatenations:
"George " + "Burns" "Burns" + " and " + "Allen" "Fahrenheit" + 451 "Answer is: " + true