Java Data and Operators

Site: Saylor Academy
Course: CS101: Introduction to Computer Science I
Book: Java Data and Operators
Printed by: Guest user
Date: Wednesday, May 14, 2025, 8:46 PM

Description

This chapter goes into more depth about relational and logical operators. You will have to use these concepts to write complex programs that other people can read and follow.

1. Introduction

OBJECTIVES

After studying this chapter, you will 

  • Understand the role that data play in effective program design. 
  • Be able to use all of Java’s primitive types and their operators.
  • Appreciate the importance of information hiding. 
  • Be able to use class constants and class methods. 
  • Know how to use Java’s Math and NumberFormat classes. 
  • Be able to perform various kinds of data conversions.

This chapter has two primary goals. One is to elaborate on Java's primitive data types, which were first introduced in Chapter 1. We will cover boolean, integer, character, and real number data types, including the various operations that you can perform on these types. We will provide examples, including several modifications of the OneRowNim class, to show typical uses of the various data types. 

Our second goal is to illustrate the idea that programming is a matter of choosing an appropriate way to represent a problem as well as choosing an appropriate sequence of actions to solve the problem. Programming is a form of problem solving that can be viewed as a two-part process: representation and action.

Representation means finding a way to look at the problem. This might involve seeing the problem as closely related to a known problem or seeing that parts of the problem can be broken up into smaller problems that you already know how to solve. In terms of programming problems, representation often means choosing the right kinds of objects and structures. 

Action is the process of taking well-defined steps to solve a problem. Given a particular way of representing the problem, what steps must we take to arrive at its solution? 

Choosing an appropriate representation is often the key to solving a problem. For example, consider this problem: Can a chess board, with its top-left and bottom-right squares removed, be completely tiled by dominoes that cover two squares at a time? 

One way to solve this problem might be to represent the chess board and dominoes as shown in Figure 5.1. If we represent the board in this way, then the actions needed to arrive at a solution involve searching for a tiling that completely covers the board. In other words, we can try one way of placing the dominoes on the board. If that doesn't work, we try another way. And so on. This process will be very time consuming, because there are millions of different ways of trying to tile the board.

Figure 5.1: Can the chess board be tiled with dominoes?

Figure 5.1

An alternative way to represent this problem comes from seeing that the top-left and bottom-right squares of the board are both white. If you remove them, you'll have a board with 62 squares, 32 black and 30 white. Because each domino must cover one white and one black square, it is impossible to tile a board with an unequal number of black and white squares.

Thus, by representing the problem as the total number of black and white squares, the actions required to solve it involve a very simple reasoning process. This representation makes it almost trivial to find the solution. On the other hand, the brute force representation presented first - trying all possible combinations - made it almost impossible to solve the problem.


Source: R. Morelli and R. Walde, Trinity College
Creative Commons License This work is licensed under a Creative Commons Attribution 4.0 License.

2. Boolean Data and Operators

George Boole

As we have learned, the boolean type is one of Java's primitive types. For this type, there are only two possible values, true and false. The boolean type is derived from the work of British mathematician George Boole, who in the 1850s, developed an algebra to process logical expressions such as p and q. Such boolean expressions produce a value that is either true or false. Every modern programming language provides some means of representing boolean expressions.


Conditional statement

The boolean type has several important uses. As we saw in Chapter 1, expressions of the form num == 7 and 5<7 have boolean values. Similarly, as we saw in Chapter 3, the boolean type is also used to represent the condition in the if statement:

if (boolean expression)
statement ;


Boolean flag

For this reason, boolean expressions are also called conditions. Along these same lines, a boolean variable can be used as a flag or a signal to "remember" whether or not a certain condition holds. For example, in the following code fragment, we use isDone to mark when a particular process is completed:

boolean isDone = false;        // Initialize the flag
...                            // Do some processing task
isDone = true;                 // Set flag when the task done
...                            // Do some other stuff
if (isDone)                    // Check if finished the task
...                            // If so, do something
else                                      
...                           // Or, do something else

2.1. Boolean (or Logical) Operations

Data and operations

Like all the other simple data types, the boolean type consists of certain data - the values true and false - and certain actions or operations that can be performed on those data. For the boolean type there are four basic operations: AND (&&), OR (||), EXCLUSIVE-OR (^), and NOT (!). These are defined in the truth table shown in Table 5.1. A truth tables defines boolean operators by giving their values in all possible situations. The first two columns of the table give possible boolean values for two operands, o1 and o2. An operand is a value used in an operation. Note that each row gives a different value assignment to the two operands, so that all possible assignments are represented. The remaining columns give the values that result for the various operators given the assignment of values to o1 and o2.

TABLE 5.1 Truth-table definitions of the boolean operators: AND (&&), OR (||), EXCLUSIVE-OR (^), and NOT (!)

o1  o2  o1 && o2
o1 || o2
o1 ^ o2
 !o1
true
 true  true true false  false
true  false  false true true  false
false  true  false true true  true
false  false  false false false  true

Binary operator

To see how to read this table, let's look at the AND operation, which is defined in column 3. The AND operator is a binary operator - that is, it Binary operator requires two operands, o1 and o2. If both o1 and o2 are true, then (o1 && o2) is true (row1). If either o1 or o2 or both o1 and o2 are false, then the expression (o1 && o2) is false (rows 2 and 3). The only case in which (o1 && o2) is true is when both o1 and o2 are true (row 4).

The boolean OR operation (column 4 of Table 5.1) is also a binary operation. If both o1 and o2 are false, then (o1 || o2) is false (row 4). If either o1 or o2 or both o1 and o2 are true, then the expression (o1 || o2) is true (rows 1-3). Thus, the only case in which (o1 || o2) is false is when both o1 and o2 are false.

The boolean EXCLUSIVE-OR operation (column 5 of Table 5.1) is a binary operation, which differs from the OR operator in that it is true when either o1 or o2 is true (rows 2 and 3), but it is false when both o1 and o2 are true (row 1).


Unary operator

The NOT operation (the last column of Table 5.1) is a unary operator - it takes only one operand - and it simply reverses the truth value of its operand. Thus, if o1 is true, !o1 is false, and vice versa.

2.2. Precedence and Associativity

In order to evaluate complex boolean expressions, it is necessary to understand the order in which boolean operations are carried out by the computer. For example, what is the value of the following expression?

true ||   true && false

The value of this expression depends on whether we evaluate the || first or the && first. If we evaluate the || first, the expression's value will be false; if we evaluate the && first, the expression's value will be true. In the following example, we use parentheses to force one operation to be done before the other:

EXPRESSION EVALUATION
( true II true ) && false ==> true && false => false
true I I ( true && false ) ==> true I I false => true

As these evaluations show, we can use parentheses to force one operator or the other to be evaluated first. However, in Java, the && operator has higher precedence than the || operator. Therefore, the second alternative corresponds to the default interpretation that Java would apply to the expression that has no parentheses. In other words, given the expression true || true && false, the AND operation would be evaluated before the OR operation even though the OR operator occurs first (i.e., to the left) in the unparenthesized expression.

TABLE 5.2 Precedence order of the boolean operators
Precedence Order Operator Operation
1 ( ) Parentheses
2 ! NOT
3 A EXCLUSIVE-OR
4 && AND
5 I I OR

Parentheses supersede

As this example illustrates, the boolean operators have a built-in precedence order which is used to determine how boolean expressions are to be evaluated (Table 5.2). A simple method for evaluating an expression is to parenthesize the expression and then evaluate it. For example, to evaluate the complex expression

true ||   ! false ^ false && true  

we would first parenthesize it according to the precedence rules set out in Table 5.2, which gives the following expression:

true ||   ((( false) ^ false) && true)  

We can then evaluate this fully parenthesized expression, step by step, starting at the innermost parentheses:

Step 1. true ||   ((true ^ false) && true)
Step 2. true || (true && true)
Step 3. true || true
Step 4. true

JAVA PROGRAMMING TIP Parentheses. Parentheses can (and should) be used to clarify any expression that appears ambiguous or to override Java's default precedence rules.

In addition to operator precedence, it is necessary to know about an operator's associativity in order to evaluate boolean expressions of the form (op1 || op2 || op3). Should this expression be evaluated as ((op1 || op2) || op3) or as (op1 || (op2 || op3))? The binary boolean operators all associate from left to right. Thus, the expressions

true true true // Same as: (true ^ true) ^  true
true && true && true // Same as: (true &&  true) &&  true
true || true || true // Same as: (true || true) || true


would be evaluated as follows:

EXPRESSION EVALUATION
(true ^ true) ^ true => false ^ true ==> true
(true && true) && true => true && true ==> true
(true II true) II true => true II true ==> true

2.3. Short-Circuit Evaluation

Another important feature of the boolean operators is that they utilize a form of evaluation known as short-circuit evaluation. In short-circuit evaluation, a boolean expression is evaluated from left to right, and the evaluation is discontinued as soon as the expression's value can be determined, regardless of whether it contains additional operators and operands. For example, in the expression

expr1 && expr2

if expr1 is false, then the AND expression must be false, so expr2 need not evaluated. Similarly, in the expression

expr1 || expr2

if expr1 is true, then the OR expression must be true, so expr2 need not evaluated.


In addition to being a more efficient form of evaluating boolean expressions, short-circuit evaluation has some practical uses. For example, we can use short-circuit evaluation to guard against null pointer exceptions. Recall from Chapter 2 that a null pointer exception results when you try to use an uninstantiated reference variable - that is, a reference variable that has not been assigned an object. For example, if we declare a OneRowNim variable without instantiating it and then try to use it, a null pointer exception will result:

OneRowNim game; // Uninstantiated Reference
if (! game .gameOver ( ))  // Null pointer exception
game. takeSticks (num);

In this code, a null pointer exception results when we use game in the method call game.gameOver(). We can use short-circuit evaluation to prevent the exception from occurring:

if (( game  !=  null ) && ( ! game .gameOver()))
game.takeSticks (num );

In this case, because game != null is false, neither method call involving game is made, thus avoiding the exception.

2.4. Are We Computers?

George Boole published his seminal work, An Investigation of the Laws of Thought, in 1854. His achievement was in developing an algebra for logic - that is, a purely abstract and symbolic system for representing the laws of logic. Boole's was not the first attempt to explore the relationship between the human mind and an abstract system of computation. Back in 1655, Thomas Hobbes had already claimed that all thought was computation.

It is estimated that the human brain contains (1012 = 10,000,000,000,000) neurons, and each neuron contains something like 10,000 dendrites, the fibers that connect one neuron to another. Together, the neurons and dendrites make up a web of enormous complexity. Since the 1840s it has been known that the brain is primarily electrical, and by the 1940s scientists had developed a pretty good model of the electrical interactions among neurons. According to this model, neurons emit short bursts of electricity along their axons, which function like output wires. The bursts leap over the gap separating axons and dendrites, which function like the neurons' input wires.

In 1943, just before the first digital computers were developed, Warren McCulloch, a neurophysiologist, and Walter Pitts, a mathematician, published a paper titled, "A Logical Calculus of the Ideas Imminent in Nervous Activity". In this paper, they showed that all of the boolean operators - AND, OR, NOT, and EXCLUSIVE-OR - could be represented by the behavior of small sets of neurons. For example, they showed that three neurons could be connected together in such a way that the third neuron fired if and only if both of the other two neurons fired. This is exactly analogous to the definition of the boolean AND operator.

A few years later, when the first computers were built, many scientists and philosophers were struck by the similarity between the logic elements that made up the computer's circuits and the neuronal models that McCulloch and Pitts had developed.

The area of neural networks is a branch of artificial intelligence (one of the applied areas of computer science) and is based on this insight by McCulloch and Pitts. Researchers in this exciting and rapidly advancing field develop neural network models of various kinds of human thinking and perception.

2.5. Using Booleans in OneRowNim

Now that we have introduced the boolean data type, let's use it to improve the OneRowNim class, the latest version of which, from Chapter 3, is given in Figure 3.16 . Previously we used an int variable, player, to represent who's turn it is. For a two-person game, such as One Row Nim, a boolean variable is well suited for this purpose, because it can toggle between true and false. For example, let's declare a variable, onePlaysNext, and initialize it to true, to represent the fact that player one will play first:

private boolean  onePlaysNext =   true ; 

When onePlaysNext is true, it will be player one's turn. When it is false, it will be player two's turn. Note that we are deliberately remaining uncommitted as to whether one or the other player is the computer.

Given this new variable, it is necessary to redefine the methods that had previously used the player variable. The first method that needs revision is the constructor:

public  OneRowNim (int sticks, int starter)
{    nSticks = sticks;
    onePlaysNext = (starter == 1); } // OneRowNim () constructor3

In the constructor, the starter parameter is used with a value of 1 or 2 to set which player goes first. Note how we use an assignment statement to set onePlaysNext to true if starter equals 1; otherwise it is set to false. The assignment statement first evaluates the expression on its right hand side (starter == 1). Because this is a boolean expression, it will have a value of true or false, which will be assigned to onePlaysNext. Thus, the assignment statement is equivalent to the following if/else statement:

if
  (player==1)
onePlaysNext = true;
else
onePlaysNext =false;
,

The remaining changes are shown in Figure 5.2 . There are only two instance methods that need revision to accommodate the use of boolean  variables. The takeSticks() method contains two revisions. The first uses the boolean OR operator to test whether a move is valid:

public booleantakeSticks (int num)
{   if (num < 1 || num >3 || num > nSticks)
return false; //Error
else //Valid move
{ nSticks = nSticks - num;
onePlaysNext = !onePlaysNext;
return true;
} // else
} // takeSticks ()

It also uses the boolean NOT operator to toggle the value of onePlaysNext, to switch to the other player's turn:

onePlaysNext = !onePlaysNext; 

Finally, the getPlayer() method now uses a if/else statement to return either 1 or 2 depending on who's turn it is:

public int getPlayer ()

{ if (onePlaysNext)

return 1;

else return2;

} // getPlayer ()

3. Numeric Data and Operators

Java has two kinds of numeric data: integers, which have no fractional part, and real numbers or floating-point numbers, which contain a fractional component. Java recognizes four different kinds of integers: byte, short, int, and long, which are distinguished by the number of bits used to represent them. A binary digit, or bit, is a 0 or a 1. (Recall that computers read instructions as series of 0s and 1s.) Java has two different kinds of real numbers, float and double, which are also distinguished by the number of bits used to represent them. See Table 5.3.

TABLE 5.3 Java's numeric types
Type Bits Range of Values
byte 8 -128 to +127
short 16 -32768 to 32767
int 32 -2147483648 to 2147483647
long 64 -263 to 263 -1
float 32 -3.40292347E+38 to + 3.40292347E +38
double 64 -1.79769313486231570E +308 to + 1.79769313486231570E+308

public class OneRowNim
{     private int nSticks = 7;
      private boolean onePlaysNext = true;
      public OneRowNim()                   {
      } //OneRowNim() constructor1
      public OneRowNim(int sticks)
     { nSticks = sticks;     } // OneRowNim() constructor2
      public OneRowNim(int sticks, int starter)
      { nSticks = sticks;
       onePlaysNext = (starter - 1); } // OneRowNim() constructor3
     public boolean takeSticks(int num)
    { if (num < 1 num > 3 II num > nSticks)
            return false;     // Error
       else                    // valid move
      {  nSticks = nSticks num;
         onePlaysNext = ! onePlaysNext;
         return true;
        } // else } // takeSticks ()     
    public int getSticks ()
    { return nSticks; } {\ color{cyan} // getSticks
    public int getPlayer ()
    { if (onePlaysNext) return 1;
      else return 2;                
   } // getPlayer ()        
   public boolean gameOver()
  { return (nSticks <= 0);
  } // gameOver ()
  public int getWinner ()
  { if (nSticks < 1) return getPlayer ();
    else return 0; // game is not over
  } // getWinner ()
  public void report ()
  { System. out. println ("Number of sticks left :                   +  getSticks ());
System. out . println ("Next turn by player"
                         + getPlayer ());  
   } // report ()               
  } // OneRowNim class

Figure 5.2: The revised OneRowNim uses a boolean variable to keep track of who's turn it is.

The more bits a data type has, the more values it can represent. One bit can represent two possible values, 1 and 0, which can be used to stand for true and false, respectively. Two bits can represent four possible values: 00, 01, 10, and 11; three bits can represent eight possible values: 000, 001, 010, 100, 101, 110, 011, 111. And, in general, an n-bit quantity can represent 2n different values.


Integer data types

As illustrated in Table 5.3, the various integer types represent positive or negative whole numbers. Perhaps the most commonly used integer type in Java is the int type, which is represented in 32 bits. This means that Java can represent 232 different int values, which range from 2,147,483,648 to 2,147,483,647, that is, from - 231 to (231 -1). Similarly, an 8-bit integer, a byte, can represent 28 or 256 different values, ranging Integer data types from 128 to +127. A 16-bit integer, a short, can represent 216 different values, which range from 32768 to 32767. And a 64-bit integer, a long, can represent whole number values ranging from 263 to 263 -1.

For floating-point numbers, a 32-bit float type can represent 232 different real numbers and a 64-bit double value can represent 264 different real numbers.

JAVA EFFECTIVE DESIGN Platform Independence. In Java, a data type's size (number of bits) is part of its definition and, therefore, remains consistent across all platforms. In C and C++, the size of a data type is dependent on the compiler.


Data types are abstractions

It is worth noting that just as model airplanes are representations of real airplanes, Java's numeric types are representations or models of the numbers we deal with in mathematics. In designing Java's data types, various trade-offs have been made in order to come up with practical implementations.


Representation trade-offs

One trade-off is that the set of integers is infinite, but Java's int type can only represent a finite number of values. Similarly, Java cannot represent the infinite number of values that occur between, say, 1.111 and 1.112. So, certain real numbers cannot be represented at all. For example, because Java uses binary numbers to represent its numeric types, one number that cannot be represented exactly is 1/10 . This inability to exactly represent a value is known as round-off error. Being unable to represent certain values can cause problems in a program. For example, it might be difficult to represent dollars and cents accurately in a program.


Round-off error

Another source of problems in dealing with numeric data is due to limits in their precision. For example, a decimal number represented as a double value can have a maximum of 17 significant digits, and a float can have a maximum 8. A significant digit is one that contributes to the number's value. If you tried to store values such as 12345.6789 or 0.123456789 in a float variable, they would be rounded off to 12345.679 and 0.12345679, respectively, causing a possible error.

JAVA DEBUGGING TIP Significant Digits. In using numeric data, be sure the data type you choose has enough precision to represent the values your program needs.

3.1. Numeric Operations

Numeric operators

The operations that can be done on numeric data include the standard algebraic operations: addition (+), subtraction (-), multiplication (*), division (/), as well as the modulus (%) operator. Note that in Java, the multiplication symbol is * and not the x. The arithmetic operators are binary operators, meaning that they each take two operands. Table 5.4 compares expressions involving the Java operators with their standard algebraic counterparts.

TABLE 5.4 The standard arithmetic operators in Java
Operation Operator Java Algebra
Addition + x+2 x+2
Subtraction - m -2 m-2
Multiplication * m*2 2m or 2 x m
Division / x/y x\( \div\) or \(\frac{x}{y}\)
Modulus to x%y A modulo y (for integers x and y)

Although these operations should seem familiar, there are some important differences between their use in algebra and their use in a Java program. Consider the following list of expressions:

3 / 2
=> value 1 An integer result  
3.0 / 2.0  
=> value 1.5
A floating - point result
3 / 2.0
=> value 1.5
A floating - point result
3.0 / 2
=> value 1.5
A floating - point result

Integer division gives an integer result

In each of these cases we are dividing the quantity 3 by the quantity 2. However, different results are obtained depending on the type of the operands involved. When both operands are integers, as in (3/2), the result must also be an integer. Hence, (3/2) has the value 1, an integer. Because integers cannot have a fractional part, the 0.5 is simply discarded. Integer division gives an integer Integer division (/) always gives an integer result. Thus, the value of (6/2) result is 3 and the value of (7/2) is also 3. Because 3.5 is not an integer, the result of dividing 7 by 2 cannot be 3.5.

JAVA DEBUGGING TIP Integer Division. A common source of error among beginning programmers is forgetting that integer division always gives an integer result.

On the other hand, when either operand is a real number, as in the last three cases, the result is a real number. Thus, while the same symbol (/) is used for dividing integers and real numbers, there are really two different operations involved here: integer division and floating-point division. Using the same symbol (/) for different operations (integer division and real division) is known as operator overloading. It is similar to method overloading, which was discussed in Chapter 3.


Modular arithmetic

What if you want to keep the remainder of an integer division? Java Modular arithmetic provides the modulus operator (%), which takes two operands. The expression (7 % 5) gives the remainder after dividing 7 by 5 - 2 in this case.

In general, the expression (m % n) (read m mod n) gives the remainder after m is divided by n. Here are several examples:

7% 5 --=> 7 mod 5 equals 2
5% 7 ==> 5 mod 7 equals 5
-7 % 5 ==> -7 mod 5 equals -2
7 % -5 ==> 7 mod -5 equals 2

The best way to interpret these examples is to perform long division on the operands keeping both the quotient and the remainder. For example, when you do long division on -7 ÷ 5, you get a quotient of -1 and a remainder of -2. The quotient is the value of -7/5 and the remainder is the value of -7%5. When you do long division on 7÷ -5, you get a quotient of -1 and a remainder of 2. The quotient is the value of 7/ -  5 and the remainder is the value of 7% - 5.

We will encounter many practical uses for the modulus operator in our programs. For a simple example, we use it when we want to determine whether an integer is even or odd. Numbers that leave a 0 remainder when divided by 2 are even:

if (N % 2 == 0)

System . out . println (N + " is even" );

More generally, we could use the mod operator to define divisibility by 3, 4, 10, or by any number.


Numeric Promotion Rules

Expressions have a type

Java is considered a strongly typed language because all expressions in Java, such as (3/2), have a type associated with them. In cases where one arithmetic operand is an integer and one is a floating-point number, Java promotes the integer into a floating-point value and performs a floating-point operation.

Promotion is a matter of converting one type to another type. For example, in the expression (5 + 4.0), the value 5 must be promoted to 5.0 before floating-point addition can be performed on (5.0 + 4.0). Generally speaking, automatic promotions such as these are allowed in Java whenever it is possible to perform the promotion without loss of information. Because an integer (5) does not have a fractional component, no information will be lost in promoting it to a real number (5.0). On the other hand, you cannot automatically convert a real number (5.4) to an integer (5) because that might lead to loss of information. This leads to the following rule:

JAVA LANGUAGE RULE  Integer Promotion. In an operation that contains an integer and a floating-point operand, the integer is promoted to a floating-point value before the operation is performed.

This rule is actually an instance of a more general rule, for whenever an expression involves operands of different types, some operands must be converted before the expression can be evaluated. Consider the following example:

byte n = 125;
short m = 32000;
n * m;

In this case, (n * m) involves two different integer types, byte and short. Before evaluating this expression Java must first promote the byte to a short and carry out the operation as the multiplication of two shorts. Conversion of short to byte would not be possible because there's no way to represent the value 32000 as a byte.


Promotion is automatic

It is important to note that this conversion rule applies regardless of the actual values of the operands. In applying the rule, Java looks at the operand's type, not its value. So even if m were assigned a value that Promotion is automatic could be represented as a byte (for example, 100), the promotion would still go from smaller to larger type. This leads to following the general rule:

JAVA LANGUAGE RULE  Type Promotion. In general, when two different types are involved in an operation, the smaller type – the one with fewer bits  – is converted to the larger type before the operation is performed. To do otherwise would risk losing information.

Table 5.5 summarizes the actual promotion rules used by Java in evaluating expressions involving mixed operands. Note that the last rule implies that integer expressions involving byte or short or int are performed as int. This explains why integer literals - such as 56 or 108 - are represented as int types in Java.

Table 5.5 Java promotion rules for mixed arithmetic operators. If two rules apply, choose the one that occurs first in this table.

If either operand is  The other is promoted to
double  double 
float float
long long
byte or short int

3.2. Operator Precedence

The built-in precedence order for arithmetic operators is shown in Table 5.6. 

Table 5.6 Precedence order of the arithmetic operators

Precedence Order  Operator  Operation 
1 () Parentheses
2 * / % Multiplication, Division, Modulus
3 + - Addition, Subtraction

Parenthesized expressions have highest precedence and are evaluated first. Next come the multiplication, division, and modulus operators, followed by addition and subtraction. When we have an unparenthesized expression that involves both multiplication and addition, the multiplication would be done first, even if it occurs to the right of the plus sign. Operators at the same level in the precedence hierarchy are evaluated from left to right. For example, consider the following expression:

9 + 6 - 3 * 6 / 2

In this case, the first operation to be applied will be the multiplication (*), followed by division (/), followed by addition (+), and then finally the subtraction (). We can use parentheses to clarify the order of evaluation. A parenthesized expression is evaluated outward from the innermost set of parentheses:

Step 1 ((9+6) - ((3*6) / 2))
Step 2 ((9+6) - (18/2))
Step 3 ((9+6) - 9)
Step 4 (15 - 9)
Step 5 6

Parentheses can (and should) always be used to clarify the order of operations in an expression. For example, addition will be performed before multiplication in the following expression:

( a + b) * c

Another reason to use parentheses is that Java's precedence and promotion rules will sometimes lead to expressions that look fine but contain subtle errors. For example, consider the following expressions:

System. out. println (5/ 3/ 2.0);           // 0.5

System. out. println (5/(3 / 2.0));        // 3.33

The first gives a result of 0.5, but the use of parentheses in the second gives a result of 3.33. If the second is the expected interpretation, then the parentheses here helped avoid a subtle semantic error.

JAVA PROGRAMMING TIP Parenthesize! To avoid subtle bugs caused by Java's precedence and promotion rules, use parentheses to specify the order of evaluation in an expression.


3.3. Increment and Decrement Operators

Java provides a number of unary operators that are used to increment or decrement an integer variable. For example, the expression k++ uses the increment operator ++ to increment the value of the integer variable k. The expression k++ is equivalent to the following Java statements:

int k;
k = k + 1; // Add 1 to k and assign the result back to k


Preincrement and postincrement

The unary ++ operator applies to a single integer operand, in this case to the variable k. It increments k's value by 1 and assigns the result back to k. Preincrement and postincrement It may be used either as a preincrement or a postincrement operator. In the expression k++, the operator follows the operand, indicating that it is being used as a postincrement operator. This means that the increment operation is done after the operand's value is used.

Contrast that with the expression ++k in which the ++ operator precedes its operand. In this case, it is used as a preincrement operator, which means that the increment operation is done before the operand's value is used.

When used in isolation, there is no practical difference between k++ and ++k. Both are equivalent to k=k+1. However, when used in conjunction with other operators, there is a significant difference between preincrement and postincrement. For example, in the following code segment,

int j = 0, k = 0;  // Initially both j and k are 0
j = ++k;  // Final values of both j and k are 1


Precedence order

the variable k is incremented before its value is assigned to j. After execution of the assignment statement, j will equal 1 and k will equal 1. The sequence is equivalent to

int j = 0, k = 0        // Initially both j and k are 0

k = k + 1;

j = k;    // Final values of both j and k are  1

However, in the following example,

int i = 0, k = 0;       // Initially both i and k are 0

i = k++;               // Final value of i is 0 and k is  1

the variable k is incremented after its value is assigned to i. After execution of the assignment statement, i will have the value 0 and k will have the value 1. The preceding sequence is equivalent to

int i = 0, k = 0     // Initially both i and k are 0

i = k;

k = k + 1;           // Final value of i is 0 and k is  1


Predecrement and postdecrement

In addition to the increment operator, Java also supplies the decrement operator --, which can also be used in the predecrement and postdecrement forms. The expression --k will first decrement k's value by 1 and then use k in any expression in which it is embedded. The expression k-- will use the current value of k in the expression in which k is contained and then it will decrement k's value by 1. Table 5.7 summarizes the increment and decrement operators. The unary increment and decrement operators have higher precedence than any of the binary arithmetic operators.

Table 5.7 Java's increment and decrement operators

Expression  Operation  Interpretation 
j = ++k Preincrement  k = k +1; j = k;
j = k++ Postincrement  j = k; k = k+1;
j = --k Predecrement k = k-1; j = k;
j = k-- Postdecrement  j = k; k = k-1;


JAVA LANGUAGE RULE Pre- and Postincrement/Decrement. If an expression like ++k or  –  – k occurs in an expression, k is incremented or decremented before its value is used in the rest of the expression. If an expression like k++ or k-- occurs in an expression, k is incremented or decremented after its value is used in the rest of the expression.

JAVA PROGRAMMING TIP Increment and Decrement Operators. Because of their subtle behavior, be careful in how you use the unary increment and decrement operators. They are most appropriate and useful for incrementing and decrementing loop variables, as we'll see later.


3.4. Assignment Operators

In addition to the simple assignment operator (=), Java supplies a number of shortcut assignment operators that allow you to combine an arithmetic operation and an assignment in one operation. These operations can be used with either integer or floating-point operands. For example, the += operator allows you to combine addition and assignment into one expression. The statement

k += 3;

is equivalent to the statement

k = k + 3;

Similarly, the statement

r += 3.5 +2.0 * 9.3 ;

is equivalent to

r = r + (3.5 + 2.0 * 9.3); // i.e., r = r + 22.1;

As these examples illustrate, when using the += operator, the expression on its right-hand side is first evaluated and then added to the current value of the variable on its left-hand side.

Table 5.8 lists the other assignment operators that can be used in combination with the arithmetic operators. 

Table 5.8 Java's assignment operators

Operator  Operation  Example  Interpretation 
= Simple assignment  m=n; m=n;
+= Addition then assignment  m+=3; m=m+3;
-= Subtraction then assignment m-=3; m=m-3;
*= Multiplication then assignment  m*=3; m=m*3;
/= Division then assignment m/=3; m=m/3;
%= Remainder then assignment m%=3; m=m%3;

For each of these operations, the interpretation is the same: Evaluate the expression on the right-hand side of the operator and then perform the arithmetic operation (such as addition or multiplication) to the current value of the variable on the left of the operator.

3.5. Relational Operators

There are several relational operations that can be performed on integers: <, >, <=, >=, ==, and !=. These correspond to the algebraic operators <, >, , , =, and 6=. Each of these operators takes two operands (integer or real) and returns a boolean result. They are defined in Table 5.9.

Table 5.9 Relational operators

Operator  Operation  Java Expression 
< Less than 5<10
> Greater than  10>5
<= Less than or equal to  5<=10
>= Greater than or equal to  10>=5
== Equal to 5==5
!= Not equal to  5 ! = 4

Note that several of these relational operators require two symbols in Java. Thus, the familiar equals sign (=) is replaced in Java by ==. This is so the equality operator can be distinguished from the assignment operator.

Also, less than or equal to (<=), greater than or equal to (>=), and not equal to (!=) require two symbols, instead of the familiar , , and 6= from algebra. In each case, the two symbols should be consecutive. It is an error in Java for a space to appear between the < and = in <=.

JAVA DEBUGGING TIP 
Equality and Assignment. A common semantic error among beginning programmers is to use the assignment operator (=) when the equality operator (==) is intended.

Among the relational operators, the inequalities (<, >, <=, and >=) have higher precedence than the equality operators (== and !=). In an expression that involves both kinds of operators, the inequalities would be evaluated first. Otherwise, the expression is evaluated from left to right.

Taken as a group the relational operators have lower precedence than the arithmetic operators. Therefore, in evaluating an expression that involves both arithmetic and relational operators, the arithmetic operations are done first. Table 5.10 includes all of the numeric operators introduced so far.

Table 5.10 Numeric operator precedence including relations

Precedence
Order
Operator Operation
1 ()     Parentheses
2 ++ --   Increment, decrement
3 * / %   Multiplication, division, modulus
4 + —     Addition, subtraction
5 < > <= >= Relational operators
6       Equality operators

To take an example, let us evaluate the following complex expression:

9 + 6 <= 25 * 4 + 2

To clarify the implicit operator precedence, we first parenthesize the expression

(9 + 6) <= ((25 * 4) + 2)

and then evaluate it step by step:


Step 1. 
(9+6) <= ((25 * 4) +2)
Step 2. (9+6) <= (100+2)
Step 3. 15 <= 102
Step 4. true

The following expression is an example of an ill-formed expression:

9+6 <= 25 * 4 == 2

That the expression is ill formed becomes obvious if we parenthesize it and then attempt to evaluate it:


Step 1.
((9+6) <= (25 * 4)) == 2
Step 2. (15 <= 100) == 2
Step 3. true == 2 // Syntax error results here

The problem here is that the expression true == 2 is an attempt to compare an int and a boolean value, which can't be done. As with any other Strong typing binary operator, the == operator requires that both of its operands be of the same type. This is another example of Java's strong type checking

4. From the Java Library

THE java.lang.Math class provides many common mathematical functions that will prove useful in performing numerical computations. As an element of the java.lang package, it is included implicitly in all Java programs. Table 5.11 lists some of the most commonly used Math class methods.

Table 5.11 A selection of Math class methods

Method Description Examples
int abs(int x) Absolute value of x if x >=0 abs(x) is x
long abs(long x) if x<0 abs(x) is -x
float abs(float x)
int ceil(double x) Rounds x to the smallest ceil(8.3) is 9
integer not less than x ceil(-8.3) is -8
int floor(double x) Rounds x to the largest floor(8.9) is 8
integer not greater than x floor(-8.9) is -9
double log(double x) Natural logarithm of x  log(2.718282) is 1.0
double pow(double x, double y) x raised to the y power (xy) pow(3,4) is 81.0
pow(16.0,0.5) is 4.0
double random() Generates a random random() is 0.5551

number in the interval (0,1) random() is 0.8712
long round(double x) Rounds x to an integer round(26.51) is 27
round(26.499) is 26
double sqrt(double x) Square root of x sqrt(4.0) is 2.0

All Math methods are static class methods and are, therefore, invoked through the class name. For example, we would calculate 24 as Math.pow(2,4), which evaluates to 16. Similarly, we compute the square root of 225.0 as Math.sqrt(225.0), which evaluates to 15.0. Indeed, Java's Math class cannot be instantiated and cannot be subclassed. Its basic definition is

public final class Math // Final , can't subclass 

{                private Math() {} // Private , can't invoke 

...

public static native double sqrt (double a) 

throws ArithmeticException;

}


By declaring the Math class public final, we indicate that it can be accessed (public) but it cannot be extended or subclassed (final). By declaring its default constructor to be private, we prevent this class from being instantiated. The idea of a class that cannot be subclassed and cannot be instantiated may seem a little strange at first. The justification for it here is that it provides a convenient and efficient way to introduce helpful math functions into the Java language.

Defining the Math class in this way makes it easy to use its methods, because you don't have to create an instance of it. It is also a very efficient design because its methods are static elements of the java.lang package. This means they are loaded into memory at the beginning of your program's execution, and they persist in memory throughout your program's lifetime. Because Math class methods do not have to be loaded into memory each time they are invoked, their execution time will improve dramatically.

JAVA EFFECTIVE DESIGN Static Methods. A method should be declared static if it is intended to be used whether or not there is an instance of its class.


5. Numeric Processing Examples

In this section we consider several numeric programming examples. They are carefully chosen to illustrate different issues and concepts associated with processing numeric data.

5.1. Example: Rounding to Two Decimal Places

As an example of how to use Math class methods, let's consider the problem of rounding numbers. When dealing with applications that involve monetary values – dollars and cents – it is often necessary to round a calculated result to two decimal places. For example, suppose a program computes the value of a certificate of deposit (CD) to be 75.19999. Before we output this result, we would want to round it to two decimal places – to 75.20. The following algorithm can be used to accomplish this:  Algorithm design

1. Multiply the number by 100, giving 7519.9999.
2. Add 0.5 to the number giving 7520.4999.
3. Drop the fractional part giving 7520
4. Divide the result by 100, giving 75.20 

Step 3 of this algorithm can be done using the Math.floor(R) method, which rounds its real argument, R, to the largest integer not less than R (from Table 5.11). If the number to be rounded is stored in the double variable R, then the following expression will round R to two decimal places:

R = Math.floor(R * 100.0 + 0.5) / 100.0;

Alternatively, we could use the Math.round() method (Table 5.11). This method rounds a floating-point value to the nearest integer. For example, Math.round(65.3333) rounds to 65 and Math.round(65.6666) rounds to 66. The following expression uses it to round to two decimal places:

R = Math.floor(R * 100.0 + 0.5) / 100.0;

Note that it is important here to divide by 100.0 and not by 100. Otherwise, the division will give an integer result and we'll lose the two decimal places.


JAVA DEBUGGING TIP

Division. Using the correct type of literal in division operations is necessary to ensure that you get the correct type of result.


5.2. Example: Converting Fahrenheit to Celsius

To illustrate some of the issues that arise in using numeric data, let's design a program that performs temperature conversions from Fahrenheit to Celsius and vice versa.

Problem Decomposition

Figure 5.3 Interacting objects: The user interacts with the user interface (TempeartureUI), which interacts with the Temperature object.

Figure 5.3

Class Design: Temperature

The purpose of the Temperature class is to perform the temperature conversions. To convert a Celsius temperature to Fahrenheit or vice versa, it is not necessary to store the temperature value. Rather, a conversion method could take the Celsius (or Fahrenheit) temperature as a parameter, perform the conversion, and return the result. Therefore, the Temperature class does not need any instance variables. Note that in this respect the Temperature class resembles the Math class. Unlike OneRowNim , which stores the game's state – the number of sticks remaining and whose turn it is – the Math and Temperature classes are stateless.

Thus, following the design of the Math class, the Temperature class will have two public static methods: one to convert from Fahrenheit to Celsius and one to convert from Celsius to Fahrenheit. Recall that static methods are associated with the class rather than with its instances. Therefore, we needn't instantiate a Temperature object to use these methods. Instead, we can invoke the methods through the class itself.

The methods will use the standard conversion formulas: F = 9/5 C + 32 and C = 5/9 (F - 32). Each of these methods should have a single parameter to store the temperature value that is being converted.

Because we want to be able to handle temperatures such as 98.6, we should use real-number data for the methods' parameters. Generally speaking, because Java represents real literals such as 98.6 as doubles, the double type is more widely used than float. Because doubles are more widely used in Java, using double wherever a floating point value is needed will cut down on the number of implicit data conversions that a program would have to perform. Therefore, each of our conversion methods should take a double parameter and return a double result. 


JAVA PROGRAMMING TIP

Numeric Types . Java uses the int type for integer literals and double for real-number literals. When possible, using int and double for numeric variables and parameters reduces the number of implicit conversions a program would have to perform.


Implementation: Temperature

The implementation of the Temperature class is shown in Figure 5.5 . Note that because celsToFahr() uses the double value temp in its calculation, it uses floating-point literals (9.0, 5.0, and 32.0) in its conversion expression. This helps to reduce the reliance on Java's built-in promotion rules, which can lead to subtle errors. For example, to the design shown in Figure 5.4 , suppose we had written what looks like an equivalent expression using integer literals:

return(9 / 5 * temp + 32); // Error: equals (temp + 32)

Figure 5.4: The Temperature class. Note that static elements are underlined in UML. (NEEDS REVISION)

Figure 5.4

Because 9 divided by 5 gives the integer result 1, this expression is always equivalent to temp + 32, which is not the correct conversion formula. This kind of subtle semantic error can be avoided if you avoid mix types wherever possible.



JAVA PROGRAMMING TIP

Don't Mix Types. You can reduce the incidence of semantic errors caused by implicit type conversions if, whenever possible, you explicitly change all the literals in an expression to the same type.


Testing and Debugging 

The next question to be addressed is how should this program be tested? As always, you should test the program in a stepwise fashion. As each method is coded, you should test it both in isolation and in combination with the other methods, if possible. 

Also, you should develop appropriate test data. It is not enough to just plug in any values. The values you use should test for certain potential problems. For this program, the following tests are appropriate: 

  • Test converting 0 degrees C to 32 degrees F. 
  • Test converting 100 degrees C to 212 degrees F. 
  • Test converting 212 degrees F to 100 degrees C. 
  • Test converting 32 degrees F to 0 degrees C. 

The first two tests use the celsToFahr() method to test the freezing point and boiling point temperatures, two boundary values for this problem. A boundary value is a value at the beginning or end of the range of values that a variable or calculation is meant to represent. The second pair of tests performs similar checks with the fahrToCels() method. One advantage of using these particular values is that we know what results the methods should return.

public class Temperature 


public Temperature() {} 

public static double fahrToCels(double temp) 

return (5.0 * (temp  –  32.0) / 9.0); 

public static double celsToFahr(double temp) 

return (9.0 • temp / 5.0 + 32.0); 

}

// Temperature

Figure 5.5: The Temperature class.



JAVA EFFECTIVE DESIGN

Test Data. Developing appropriate test data is an important part of program design. One type of test data should check the boundaries of the particular calculations you are making.


JAVA DEBUGGING TIP

Test, Test, Test! The fact that your program runs correctly on some data is no guarantee of its correctness. The more testing, and the more careful the testing you do, the better.



The TemperatureUI Class 

The purpose of the TemperatureUI class is to serve as a user interface – that is, as an interface between the user and a Temperature object. It will accept a Fahrenheit or Celsius temperature from the user, pass it to one of the public methods of the Temperature object for conversion, and display the result that is returned. 

As we discussed in Chapter 4, the user interface can take various forms, ranging from a command-line interface to a graphical interface. Figure 5.6 shows a design for the user interface based on the command-line interface developed in Chapter 4. The TemperatureUI uses a KeyboardReader to handle interaction with the user and uses static methods in the Temperature class to perform the temperature conversions. 

Figure 5.6: A command-line user interface.


SELF-STUDY EXERCISES 

EXERCISE 5.1 Following the design in Figure 5.6, implement the TemperatureUI class and use it to test the methods in Temperature class. The run() method should use an input-process-output algorithm: Prompt the user for input, perform the necessary processing, and output the result. Note that because Temperature's conversion methods are class methods, you do not need to instantiate a Temperature object in this project. You can invoke the conversion methods directly through the Temperature class: 

double fahr = Temperature.celsToFahr (98.6); 

EXERCISE 5.2 Following the design for the GUI developed in Chapter 4, implement a GUI to use for testing the Temperature class. The GUI should have the layout shown in Figure 5.7. Figure 5.7 Figure 5.7: Layout design of a GUI that performs temperature conversions.

5.3. Example: Using Class Constants

As we noted in Chapter 0, in addition to instance variables, which are associated with instances (objects) of a class, Java also allows class variables, which are associated with the class itself. One of the most common uses of such variables is to define named constants to replace literal values. A named constant is a variable that cannot be changed once it has been given an initial value. In this section, we use our running example, OneRowNim, to illustrate using class constants.

Recall that methods and variables that are associated with a class must be declared with the static modifier. If a variable is declared static, there is exactly one copy of that variable created no matter how many times its class is instantiated. To turn a variable into a constant, it must be declared with the final modifier. Thus, the following would be examples of a class constants, constant values that are associated with the class rather than with its instances:

public static final int PLAYER_ONE = 1;
public static final int PLAYER_TWO = 2;
public static final int MAX_PICKUP = 3;
public static final int MAX_STICKS = 7;

The final modifier indicates that the value of a variable cannot be changed. When final is used in a variable declaration, the variable must be assigned an initial value. After a final variable is properly declared, it is a syntax error to attempt to try to change its value. For example, given the preceding declarations, the following assignment statement would cause a compiler error:

PLAYER_ONE = 5; // Syntax error; PLAYER_ONE is a constant

Note how we use uppercase letters and underscore characters ( ) in the names of constants. This is a convention that professional Java programmers follow, and its purpose is to make it easy to distinguish the constants from the variables in a program. This makes the program easier to read and understand.

JAVA PROGRAMMING TIP

Readability. To make your programs more readable, use uppercase font for constant identifiers.


Another way that named constants improve the readability of a program is by replacing the reliance on literal values. For example, for the OneRowNim class, compare the following two if conditions:


if (num <
1 || num > 3 || num > nSticks) ...
if (num < 1 || num > MAX_PICKUP || num > nSticks) ...

Clearly, the second condition is easier to read and understand. In the first condition, we have no good idea what the literal value 3 represents. In the second, we know that MAX PICKUP represents the most sticks a player can pick up.

Thus, to make OneRowNim more readable, we should replace all occurrences of the literal value 3 with the constant MAX PICKUP. This same principle would apply to some of the other literal values in the program. Thus, instead of using 1 and 2 to represent the two players, we could use PLAYER ONE and PLAYER TWO to make methods such as the following easier to read and understand:

public int getPlayer ()

{ if (onePlaysNext)

return PLAYER_ONE;

else return PLAYER_TWO;

} // getPlayer ()



JAVA PROGRAMMING TIP

Readability. To make your programs more readable, use named constants instead of literal values.



Another advantage of named constants (over literals) is that their use makes the program easier to modify and maintain. For example, suppose that we decide to change OneRowNim so that the maximum number of sticks that can be picked up is 4 instead of 3. If we used literal values, we would have to change all occurrences of 4 that were used to represent the maximum pick up. If we used a named constant, we need only change its declaration to:

public static final int MAX_PICKUP = 4;

JAVA EFFECTIVE DESIGN

Maintainability. Constants should be used instead of literal values in a program. This will make the program easier to modify and maintain.


So far, all of the examples we have presented show why named constants (but not necessarily class constants) are useful. Not all constants are class constants. That is, not all constants are declared static. However, the idea of associating constants with a class makes good sense. In addition to saving memory resources, by creating just a single copy of the constant, constants such as MAX STICKS and PLAYER ONE make more conceptual sense to associate with the class itself rather than with any particular OneRowNim instance.

Class constants are used extensively in the Java class library. For example, as we saw in Chapter 2, Java's various built-in colors are represented as constants of the java.awt.Color class—Color.blue and Color.red. Similarly, java.awt.Label uses int constants to specify how a label's text should be aligned: Label.CENTER.

Another advantage of class constants is that they can be used before instances of the class exist. For example, a class constant (as opposed to an instance constant) may be used during object instantiation:

OneRowNim game = new OneRowNim (OneRowNim.MAX_STICKS);

Note how we use the name of the class to refer to the class constant. Of course, MAX STICKS has to be a public variable in order to be accessible outside the class. To use MAX STICKS as a constructor argument it has to be a class constant because at this point in the program there are no instances of OneRowNim. A new version of OneRowNim that uses class constants is shown in Figure 5.8.

It is important to note that Java also allows class constants to be referenced through an instance of the class. Thus, once we have instantiated game, we can refer to MAX STICKS with either OneRowNim.MAX STICKS or game.MAX STICKS.

SELF-STUDY EXERCISE

EXERCISE 5.3 Implement a command-line interface class named KBTestOneRowNim, that uses our new version of OneRowNim . Make use of the MAX STICKS and MAX PICKUP in the user interface.


5.4. OBJECT-ORIENTED DESIGN: Information Hiding

The fact that our new versions of OneRowNim – we've developed two new versions in this chapter – are backward compatible with the previous version is due in large part to the way we have divided up its public and private elements. Because the new versions still present the same public interface, programs that use the OneRowNim class, such as the OneRowNimApp from Chapter 4 (Fig. 4.24), can continue to use the class without changing a single line of their own code. To confirm this, see the Self-Study Exercise at the end of this section.

public class OneRowNim 

{ public static final int PLAYER.ONE = 1; 

public static final int PLAYER:IWO= 2; 

public static final int MAX_PICKUP= 3; 

public static final int MAXSTICKS = 11; 

public static final boolean GAMEDVER = false; 

private int nSticks = MAX_SIICKS;

private boolean onePlaysNext = true;                     

public OneRowl'Sm() { //OneRowNim() constructorl 

public OneRowlstun(int sticks)

{ nSticks = sticks;

} // OneRowNim() constructor2 

public OneRowbrun(int sticks, int starter)

{ nSticks = sticks;

onePlaysNext = (starter == PLAYERDNE);

} // OneRowNim() constructor3 

public boolean takeSticks(int num)

{ if (num < 1 I num > MA)U'ICKUP I I num > nSticks )

return false; // Error else // Valid move

{ nSticks = nSticks — rum;

onePlaysNext = !onePlaysNext;

return true;

} //else

} // takeSticks() 

public int getSticks()

{ return nSticks; }

}//getSticks 

public int getPlayer()

{ if (onePlaysNext)

return PLAYER_ONE;

else return PLAYER_TWO;

} //getPlayer() 

public boolean gameOver()

{ return (nSticks <= 0); }

} // gameOver () 

public int getWinner()

{ if (nSticks < 1)

return getPlayer ();

else return 0; // Came is not over

} // getWinner() 

public String report()

{ return ("Number of sticks left : " + getSticks()

+ "\ nNext turn by player " + getPlayer () + " \ n" );

} // report ()

} // OneRowNim class 

Figure 5.8: This version of OneRowNim uses named constants.

Although we have made significant changes to the underlying representation of OneRowNim, the implementation details – its data and algorithms – are hidden from other objects. As long as OneRowNim's public interface remains compatible with the old version, changes to its private elements won't cause any inconvenience to those objects that were dependent on the old version. This ability to change the underlying implementation without affecting the outward functionality of a class is one of the great benefits of the information hiding principle.

JAVA EFFECTIVE DESIGN

Information Hiding. In designing a class, other objects should be given access just to the information they need and nothing more.


The lesson to be learned here is that the public parts of a class should be restricted to just those parts that must be accessible to other objects. Everything else should be private. Things work better, in Java programming and in the real world, when objects are designed with the principle of information hiding in mind.


SELF-STUDY EXERCISE 

EXERCISE 5.4 To confirm that our new version of OneRowNim still works correctly with the user interfaces we developed in Chapter 4, compile and run it with OneRowNimApp (Fig. 4.24).

5.5. Example: A Winning Algorithm for One Row Nim

Now that we have access to numeric data types and operators, lets develop an algorithm that can win the One Row Nim game. Recall that in Chapter 4 we left things such that when the computer moves, it always takes 1 stick. Let's replace that strategy with a more sophisticated approach.

If you have played One Row Nim, you have probably noticed that in a game with 21 sticks, you can always win the game if you leave your opponent with 1, 5, 9, 13, 17, or 21 sticks. This is obvious for the case of 1 stick. For the case where you leave your opponent 5 sticks, no matter what the opponent does, you can make a move that leaves the other player with 1 stick. For example, if your opponent takes 1 stick, you can take 3; if your opponent takes 2, you can take 2; and, if your opponent takes 3, you can take 1. In any case, you can win the game by making the right move, if you have left your opponent with 5 sticks. The same arguments apply for the other values: 9, 13, 17, and 21.

What relationship is common to the numbers in this set? Notice that if you take the remainder after dividing each of these numbers by 4 you always get 1:

1 % 4 == 1
5 % 4 == 1
9 % 4 == 1
13 % 4 == 1
17 % 4 == 1
21 % 4 == 1


Thus, we can base our winning strategy on the goal of leaving the opponent with a number of sticks, N, such that N%  4 equals 1.

To determine how many sticks to take in order to leave the opponent with N, we need to use a little algebra. Let's suppose that sticksLeft represents the number of sticks left before our turn. The first thing we have to acknowledge is that if sticksLeft % 4 == 1, then we have been left with 1, 5, 9, 13, and so on, sticks, so we cannot force a win. In that case, it doesn't matter how many sticks we pick up. Our opponent should win the game.

So, let's suppose that sticksLeft % 4 != 1 , and let M be the number of sticks to pickup in order to leave our opponent with N , such that N % 4 == 1. Then we have the following two equations:

sticksLeft 
- M == N
N% 4 == 1

We can combine these into a single equation, which can be simplified as follows:

(sticksLeft - M) %4 == 1
If sticksLeft - M leaves a remainder of 1 when divided by 4, that means that sticksLeft - M is equal some integer quotient, Q times 4 plus 1:

(sticksLeft - M) == (Q * 4) + 1

By adding M to both sides and subtracting 1 from both sides of this equation, we get:

(sticksLeft - 1) == (Q * 4) + M

This equation is saying that  (sticksLeft - 1) % 4 == M. That is, that when you divide sticksLeft-1 by 4, you will get a remainder of M, which is the number of sticks you should pick up. Thus, to decide how many sticks to take, we want to compute:

M == (sticksLeft -1) % 4

To verify this, let's look at some examples:

sticksLeft
Before
(sticksLeft -1) % 4 sticksLeft
After
9 (9-1) % 4 == 0 Illegal Move
8 (8-1) % 4 == 3 5
7 (7-1) % 4 == 2 5
6 (6-1) % 4 == 1 5
5 (5-1) % 4 == 0 Illegal Move


The examples in this table show that when we use (sticksLeft-1 % 4) to calculate our move, we always leave our opponent with a losing situation. Note that when sticksLeft equals 9 or 5, we can't apply this strategy because it would lead to an illegal move.

Let's now convert this algorithm into Java code. In addition to incorporating our winning strategy, this move() method makes use of two important Math class methods:

public int move() 

{ int sticksLeft = nim . getSticks // Get number of sticks 

if (sticksLeft % (nim.MAXPICKUP + 1) != 1)// If winnable 

return (sticksLeft — 1) % (nim.MPJCPICKUP +1); 

else // Else pick random 

int maxPickup = Math.min(nim.MAX.PICKUP, sticksLeft); 

return 1 + (int )(Math.random() s maxPickup); 

}

}

The move() method will return an int representing the best move possible. It begins by getting the number of sticks left from the OneRowNim object, which is referred to as nim in this case. It then checks whether it can win by computing (sticksLeft-1) % 4. However, note that rather than use the literal value 4, we use the named constant MAX PICKUP , which is accessible through the nim object. This is an especially good use for the class constant because it makes our algorithm completely general – that is, our winning strategy will continue to work even if the game is changed so that the maximum pickup is 5 or 6. The then clause computes and returns (sticksLeft-1) % nim.MAX PICKUP+1, but here again it uses the class constant.

The else clause would be used when it is not possible to make a winning move. In this case we want to choose a random number of sticks between 1 and some maximum number. The maximum number depends on how many sticks are left. If there are more than 3 sticks left, then the most we can pick up is 3, so we want a random number between 1 and 3. However, if there are 2 sticks left, then the most we can pick up is 2 and we want a random number between 1 and 2. Note how we use the Math.min() method to decide the maximum number of sticks that can be picked up:

int maxPickup = Math.min(nim .MAX_PICKUP, sticksLeft);

The min() method returns the minimum value between its two arguments.

Finally, note how we use the Math.random() method to calculate a random number between 1 and the maximum:

1 + (int) (Math. random() * maxPickup);

The random() method returns a real number between 0 and 0.999999 – that is, a real number between 0 and 1 but not including 1:

0 <= Math.random () < 1.0

If we multiply Math.random() times 2, the result would be a value between 0 and 1.9999999. Similarly, if we multiplied it by 3, the result would be a value between 0 and 2.9999999. In order to use the random value, we have to convert it into an integer, which is done by using the (int) cast operator:

(int) (Math.random () * maxPickup);

Recall that when a double is cast into an int , Java just throws away the fractional part. Therefore, this expression will give us a value between 0 and maxPickup-1. If maxPickup is 3, this will give a value between 0 and 2, whereas we want a random value between 1 and 3. To achieve this desired value, we merely add 1 to the result. Thus, using the expression

1 + (int) (Math.random () * maxPickup)

gives us a random number between 1 and maxPickup, where maxPickup is either 1, 2, or 3, depending on the situation of the game at that point.

SELF-STUDY EXERCISE

EXERCISE 5.5  Implement a class named NimPlayer that incorporates the move() method designed in this section. The class should implement the design shown in Figure 5.9. That is, in addition to the move() method, it should have an instance variable, nim , which will serve as a reference to the OneRowNim game. Its constructor method should take a OneRowNim parameter, allowing the NimPlayer to be given a reference when it is instantiated.

NimPlayer
- nim:OneRowNim
+ NimPlayer(g:OneRowNim)
+ move():int

Figure 5.9: The NimPlayer class.

EXERCISE 5.6 Modify OneRowNim's command-line interface to play One Row Nim between the user and the computer, where the NimPlayer implemented in the previous exercise represents the computer.



6. From the Java Library

java.text.NumberFormat

ALTHOUGH the Math.round() method is useful for rounding numbers, it is not suitable for business applications. Even for rounded values, Java will drop trailing zeroes. So a value such as $10,000.00 would be output as $10000.0. This wouldn't be acceptable for a business report.

NumberFormat
+ getInstance():NumberFormat
+ getCurrencyInstance():NumberFormat
+ getPercentInstance():NumberFormat
+ format(in n:double):String
+ format(in n:long):String

Fortunately, Java supplies the java.text.NumberFormat class precisely for the task of representing numbers as dollar amounts, percentages, and other formats (Fig. 5.10).

The NumberFormat class is an abstract class, which means that it cannot be directly instantiated. Instead, you would use its static getInstance() methods to create an instance that can then be used for the desired formatting tasks.

Once a NumberFormat instance has been created, its format() method can be used to put a number into a particular format. The setMaximumFractionDigits() and setMaximumIntegerDigits() methods can be used to control the number of digits before and after the decimal point.

For example, the following statements can be used to format a decimal number as a currency string in dollars and cents:

NumberFormat dollars = NumberFormat. getCurrencylnstance 0; 

System. out . println ( dollars . format (10962.555));

These statements would cause the value 10962.555 to be shown as $10,962.56. Similarly, the statements,

NumberFormat percent = NumberFormat. getPercentlnstance (); 

percent. setMaximumFractionDigits (2); 

System. out . println (percent . format (6.55));

would display the value 6.55 as 6.55%. The utility of the Math and NumberFormat classes illustrates the following principle:

JAVA EFFECTIVE DESIGN
Code Reuse. Often the best way to solve a programming task is to find the appropriate methods in the Java class library.

SELF-STUDY EXERCISE 

EXERCISE 5.7 A Certificate of Deposit (CD) is an investment instrument that accumulates interest at a given rate for an initial principal over Figure 5.11: The BankCD class. a fixed number of years. The formula for compounding interest is shown in Table 5.11. It assumes that interest is compounded annually. For daily compounding, the annual rate must be divided by 365, and the compounding period must be multiplied by 365, giving: a = p(1+r/365)365n . Implement a BankCD class that calculates the maturity value of a CD. Figure 5.11 gives the design of the class. It should have three instance variables for the CD's principal, rate, and years. Its constructor method sets the initial values of these variables when a BankCD is instantiated. Its two public methods calculate the maturity value using yearly and daily compounding interest, respectively. Use the Math.pow() method to calculate maturity values. For example, the following expression calculates maturity value with annual compounding:

BankCD
- principal: double
- rate: double
- years: double
+ BankCD(p:double,r:double,
y:double)
+ calcYearly():double
+ calcDaily():double

Figure 5.11: The BankCD class.

principal * Math.pow(1 + rate, years)

TABLE 5.12 Formula for calculating compound interest
\(a = p(I +r)2\) where
•a is the CD's value at the end of the nth year
•p is the principal or original investment amount
•r is the annual interest rate
•ri is the number of years or the compounding period

EXERCISE 5.8 Design a command-line user interface to the BankCD class that lets the user input principal, interest rate, and years, and reports the CD's maturity value with both yearly and daily compounding. Use NumberFormat objects to display percentages and dollar figures in an appropriate format. The program's output should look something like the following (user's inputs are in cyan): 

************************ OUTPUT ******************** 

Compare daily and annual compounding for a Bank CD. Input CD initial principal, e.g. 1000.55 > 2500 Input CD interest rate, e.g. 6.5 > 7.8 Input the number of years to maturity, e.g., 10.5 > 5 For Principal = $2,500.00 Rate= 7.8% Years= 5.0 The maturity value compounded yearly is $3,639.43 The maturity value compounded daily is: $3,692.30 

************************ OUTPUT ********************


7. Character Data and Operators

Another primitive data type in Java is the character type, char. A character in Java is represented by a 16-bit unsigned integer. This means that a total of 216 or 65536 different Unicode characters can be represented, corresponding to the integer values 0 to 65535. The Unicode character set is an international standard that has been developed to enable computer languages to represent characters in a wide variety of languages, not just English. Detailed information about this encoding can be obtained at

http://www.unicode.org/

It is customary in programming languages to use unsigned integers to represent characters. This means that all the digits (0,...,9), alphabetic letters (a,...,z,A,...,Z), punctuation symbols (such as . ; , " '' ! -), and nonprinting control characters (LINE FEED, ESCAPE, CARRIAGE RETURN, ...) that make up the computer's character set are represented in the computer's memory by integers. A more traditional set of characters is the ASCII
(American Standard Code for Information Interchange) character set. ASCII is based on a 7-bit code and, therefore, defines 27 or 128 different characters, corresponding to the integer values 0 to 127. In order to make Unicode backward compatible with ASCII systems, the first 128 Unicode characters are identical to the ASCII characters. Thus, in both the ASCII and Unicode encoding, the printable characters have the integer values shown in Table 5.13.
TABLE 5.13 ASCII codes for selected characters
Code 32 33 34 35 36 37 38 39 40 41 42 43 44
Char SP ! " # $ % & ( ) * + ,
Code 48 49 50 51 52 53 54 55 56 57
Char 0 1 2 3 4 5 6 7 8 9
Code 58 59 60 61 62 63 64
Char : ; < = > ? @
Code 65 66 67 68 69 70 71 72 73 74 75 76 77
Char A B C D E F G H I J K L M
Code 78 79 80 81 82 83 84 85 86 87 88 89 90
Char N O P Q R S T U V W X Y Z
Code 91 92 93 94 95 96
Char [ \ ] ^ -
Code 97 98 99 100 101 102 103 104 105 106 107 108 109
Char a b c d e f g h i j k l m
Code 110 111 112 113 114 115 116 117 118 119 120 121 122
Char n o p q r s t u v w x y z
Code 123 124 125 126
Char { | } -


7.1. Character to Integer Conversions

Is 'A' a character or an integer? The fact that character data are stored as integers in the computer's memory can cause some confusion about whether a given piece of data is a character or an integer. In other words, when is a character, for example 'A', treated as the integer (65) instead of as the character 'A'? The rule in Java is that a character literal – 'a' or 'A' or '0' or '?' – is always treated as a character, unless we explicitly tell Java to treat it as an integer. So if we display a literal's value

System.out.printIn('a');

the letter 'a' will be displayed. Similarly, if we assign 'a' to a
char variable and then display the variable's value,

char ch = 'a';
System.out.printIn(ch);     // Displays 'a'

the letter 'a' will be shown. If, on the other hand, we wish to output a character's integer value, we must use an explicit cast operator as follows:

System.out.printIn((int)'a');     // Displays 97

A cast operation, such as (int), converts one type of data ('a') into another (97). This is known as a type conversion. Similarly, if we wish to store a character's integer value in a variable, we can cast the char into an  int as follows:

int k = (int)'a';           //  Converts 'a' to 97

System.out.printIn(k);  // Displays 97


As these examples show, a cast is a type conversion operator. Java allows a wide variety of both explicit and implicit type conversions. Certain conversions (for example, promotions) take place when methods are invoked, when assignment statements are executed, when expressions are evaluated, and so on.

Type conversion in Java is governed by several rules and exceptions. In some cases Java allows the programmer to make implicit cast conversions. For example, in the following assignment a char is converted to an int even though no explicit cast operator is used:


char ch;
int k;
k = ch;    //   convert a char into an int

Java permits this conversion because no information will be lost. A character char is represented in 16 bits whereas an  int is represented in 32 bits. This is like trying to put a small object into a large box. Space will be left over, but the object will fit inside without being damaged. Similarly, storing a 16-bit char in a 32-bit  int will leave the extra 16 bits unused. This widening primitive conversion  changes one primitive type (char) into a wider one (int ), where a type's  width is the number of bits used in its representation.

On the other hand, trying to assign an int value to a char variable leads to a syntax error:

char ch;
int k;
ch = k;          // Syntax error:  can't assign int to char

Trying to assign a 32-bit int to 16-bit char is like trying to fit a big object into an undersized box. The object won't fit unless we shrink it in some way. Java will allow us to assign an int value to a char variable, but only if we perform an explicit cast on it:

ch = (char)k;    //   Explicit cast of int k into char ch

The (char) cast operation performs a careful "shrinking" of the int by lopping off the last 16 bits of the int. This can be done without loss of information provided that k's value is in the range 0 to 65535 – that is, in Narrowing conversion the range of values that fit into a  char variable. This narrowing primitive conversion changes a wider type (32-bit int) to a narrower type (16-bit char). Because of the potential here for information loss, it is up to the programmer to determine that the cast can be performed safely.

JAVA LANGUAGE RULE
Type Conversion. Java permits implicit type conversions from a narrower type to a wider type. A cast operator must be used when converting a wider type into a narrower type.

The cast operator can be used with any primitive type. It applies to the variable or expression that immediately follows it. Thus, parentheses must be used to cast the expression m+n into a char:

char ch = (char)(m + n);

The following statement would cause a syntax error because the cast operator would only be applied to m:

char ch = (char)m + n; // Error: right side is an int

In the expression on the right-hand side, the character produced by (char)m will be promoted to an int because it is part of an integer operation whose result will still be an int. Therefore, it cannot be assigned to a char without an explicit cast.

7.2. Lexical Ordering

The order in which the characters of a character set are arranged, their lexical order, is an important feature of the character set. It especially comes into play for such tasks as arranging strings in alphabetical order.

Although the actual integer values assigned to the individual characters by ASCII and UNICODE encoding seem somewhat arbitrary, the characters are, in fact, arranged in a particular order. For example, note that various sequences of digits, '0'...'9', and letters, 'a'...'z' and 'A'...'Z', are represented by sequences of integers (Table 5.11). This makes it possible to represent the lexical order of the characters in terms of the less than relationship among integers. The fact that 'a' comes before 'f' in alphabetical order is represented by the fact that 97 (the integer code for 'a') is less than 102 (the integer code for 'f'). Similarly, the digit '5' comes before the digit '9' in an alphabetical sequence because 53 (the integer code for '5') is less than 57 (the integer code for '9').

This ordering relationship extends throughout the character set. Thus, it is also the case that 'A' comes before 'a' in the lexical ordering because 65 (the integer code for 'A') is less than 97 (the integer code for 'a'). Similarly, the character '[' comes before '}' because its integer code (91) is less than 125, the integer code for '}'.

7.3. Relational Operators

Given the lexical ordering of the char type, the following relational operators can be defined: <, >, <=, >=, ==, !=. Given any two characters, ch1 and ch2, the expression ch1 < ch2 is true if and only if the integer value of ch1 is less than the integer value of ch2. In this case we say that ch1 precedes ch2 in lexical order. Similarly, the expression ch1 > ch2 is true if and only if the integer value of ch1 is greater than the integer value of ch2. In this case we say that ch1 follows ch2. And so on for the other relational operators. This means that we can perform comparison operations on any two character operands (Table 5.14).

Table 5.14  Relational operations on characters

Operation Operator Java True Expression
Precedes < ch1 < ch2 'a' < 'b'
Follows > ch1 > ch2 'c' > 'a'
Precedes or equals <= ch1 <= ch2 'a' <= 'a'
Follows or equals >= ch2 >= ch1 'a' >= 'a'
Equal == ch1 == ch2 'a' == 'a'
Not equal to != ch1 != ch2 'a' != 'b'


8. Example: Character Conversions

Another interesting implication of representing the characters as integers is that we can represent various character operations in terms of integer operations. For example, suppose we want to capitalize a lowercase letter. Table 5.13 shows that the entire sequence of lowercase letters ('a' ... 'z') is displaced by 32 from the sequence of uppercase letters ('A' ... 'Z') , so we can convert any lowercase letter into its corresponding uppercase letter by subtracting 32 from its integer value, provided we perform an explicit cast on the result. When we perform the cast (char) ('a' - 32 ), the resulting value is 'A', as the following example shows:

(char)('a' - 32)       ==> 'A'

Recall that in evaluating 'a' - 32 Java will promote 'a' to an int and then perform the subtraction. Thus, a step-by-step evaluation of the expression would go as follows:

Step 1. (char)((int)'a' — 32) // Promote 'a' to int
Step 2. (char)(97 — 32) // Subtract
Step 3. (char) (65)  // Cast result to a char
Step 4. 'A'  // Results in 'A'

Similarly, we can convert an uppercase letter into the corresponding lowercase letter by simply adding 32 to its integer code and casting the result back to a char:

(char)('J' + 32)        ==>  'j'

We can group these ideas into a method that performs conversion from lowercase to uppercase:

char toUpperCase(char ch) { 

if ((ch >= 'a') && (ch C= 5:1)) 

return ch — 32 ; 0 Error : can 't return an int

return ch;

}

This method takes a single char parameter and returns a char value. It begins by checking if ch is a lowercase letter—that is, if ch falls between 'a' and 'z' inclusive. If so, it returns the result of subtracting 32 from ch. If not, it returns ch unchanged. However, the method contains a syntax error Type error that becomes apparent if we trace through its steps. If we invoke it with the expression toUpperCase('b'), then since 'b' is between 'a' and 'z', the method will return 'b'- 32. Because the integer value of 'b' is 98, it will return 98 - 32 or 66, which is the integer code for the character ' B'. However, the method is supposed to return a char, so this last statement will generate the following syntax error:

Incompatible type for return. An explicit cast needed to convert int to char.

>> return ch - 32 ;

>> ^

In order to avoid this error, the result must be converted back to char before it can be returned:

char toUppercase (char ch) {

if ((ch >= 'a') && (ch <= 'z'))

return (char) (ch - 32); // Explicit cast

return ch;

}

Another common type of conversion is to convert a digit to its corresponding integer value. For example, we convert the character '9' to the integer 9 by making use of the fact that the digit '9' is 9 characters beyond the digit '0' in the lexical order. Therefore, subtracting '0' from '9' gives integer 9 as a result:

('9' - '0') ==> (57 - 48) ==>  9

More generally, the expression ch - '0' will convert any digit, ch, to its integer value. We can encapsulate these ideas into a method that converts any digit into its corresponding integer value:

int digitToInteger (char ch) {

if (( ch >= '0') && (chy <= '9'))

return ch - '0';

return -1;

}

This method takes a single char parameter and returns an int. It first checks that ch is a valid digit, and if so, it subtracts the character '0' from it. If not, the method just returns -1, which indicates that the method received an invalid input parameter. Obviously, when an object invokes this method, it should first make sure that the value it passes is in fact a digit.

The Java application program shown in Figure 5.12 illustrates several of the ideas discussed in this section. Note that both the digitToInteger() and toUpperCase() are declared static. This allows us to call them directly from the (static) main() method, as useful and justifiable shortcut if, as in this case, we are just testing the methods.


9. Problem Solving = Representation + Action

As you have seen in this chapter, designing classes involves a careful interplay between representation (data) and action (methods). Our several modifications to the OneRowNim class illustrate that the data used to represent an object's state can either complicate or simplify the design of the methods needed to solve a problem.

We hope that it is becoming clear to you that in writing object-oriented programs, choosing an appropriate data representation is just as important as choosing the correct algorithm. The concept of an object allows us to encapsulate representation and action into a single entity. It is a very natural way to approach problem solving.

If you look closely enough at any problem, you will find this close relationship between representation and action. For example, compare the task of performing multiplication using Arabic numerals – 65 * 12 = 380 – and the same task using Roman numerals – LXV * XII = DCCLXXX. It's doubtful that our science and technology would be where they are today if our civilization had to rely forever on the Roman way of representing numbers!

public class Test {

public s t a t ic void main ( S t ring argv [ ] ) {

char ch = a ; // Local variables

int k= ( int ) ’b ’ ;

System . out . prin tln ( ch ) ;

System . out . prin tln (k ) ;

ch = ( char )k; // Cast needed here

System . out . prin tln ( ch ) ;

System . out . prin tln ( toUpperCase ( ’ a ’ ));

System . out . prin tln ( toUpperCase ( ch ) ) ;

System . out . prin tln ( digitToInteger ( ’ 7 ’ ));

}

public static char toUpperCase ( char ch ) {

i f ( ( ch >= ’ a ’ ) && ( ch <= ’ z ’ ) )

return ( char ) ( ch  32);

return ch ;

}

public static int digitToInteger ( char ch ) {

if ( ( ch >= ’ 0 ’ ) && ( ch <= ’ 9 ’ ) )

return ch  ’ 0 ’ ;

return 1 ;

}

} // Test

Figure 5.12:  A Java program illustrating character conversions. When run, the program will generate the following outputs, one per line: a, 98, b, A, B, 7.

if our civilization had to rely forever on the Roman way of representing numbers!


JAVA EFFECTIVE DESIGN
Representation and Action. Representation (data) and action (methods) are equally important parts of the problem-solving process.

10. Chapter Summary

Technical Terms

action named constant short-circuit
binary operator operand evaluation
binary digit (bit) operator overloading type conversion
boundary value precedence order unary operator
cast operation promotion Unicode
class constant round off error representation
input-process-output

Summary of Important Points

  • The way we approach a problem can often help or hinder us in our ability to solve it. Choosing an appropriate representation for a problem is often the key to solving it. 
  • In order to evaluate complex expressions, it is necessary to understand the precedence order and associativity of the operators involved. Parentheses can always be used to override an operator's built-in precedence. 
  • Java provides several types of integer data, including the 8-bit byte, 16-bit short, 32-bit int, and 64-bit long types. Unless otherwise specified, integer literals are represented as int data in a Java program. 
  • Java provides two types of floating-point data, the 32-bit float type and the 64-bit double type. Unless otherwise specified, floating-point literals are represented as double data. 
  • In general, if a data type uses n bits in its representation, then it can represent 2n different values. 
  • The fact that Java's primitive types are defined in terms of a specific number of bits is one way that Java promotes platform independence
  • It is necessary to distinguish integer operations from floating-point operations even though the same symbols are used. For example, (7/2) is 3, while (7.0/2) is 3.0.
  • In revising a class that is used by other classes it is important to preserve as much of the class's interface as possible. 
  • In Java, character data are based on the Unicode character set, which provides 216 = 65,536 different character codes. To provide backward compatibility with the ASCII code, the first 128 characters are the ASCII coded characters. 
  • Java operators are evaluated according to the precedence hierarchy shown in Table 5.15. The lower the precedence number, the earlier an operator is evaluated. So the operators at the top of the table are evaluated before operators that occur below them in the table. Operators at the same precedence level are evaluated according to their association, either left to right (L to R) or right to left (R to L).
TABLE 5.15 Java operator precedence and associativity table  
Order Operator Operation Association
0 ( ) Parentheses  
1 ++ -- . Postincrement, postdecrement, dotOperator L to R
2 ++ -- + - ! Preincrement, predecrement R to L
    Unary plus, unary minus, boolean NOT  
3 (type) new Type cast, object instantiation R to L
4 * / % Multiplication, division, modulus L to R
5 + - + Addition, subtraction, string concatenation L to R
6 < > <= >= Relational operators L to R
7 == ! = Equality operators L to R
8 ^ Boolean XOR L to R
9 && Boolean AND L to R
10 || Boolean OR L to R
11 = += -= *= /= %= Assignment operators R to L