Home | Blog | Java | Jokes | Poems | Musings | Site Map | Kudos | Downloads | Useful Sites | Interesting | System Setup | Contact

Home Page

AKGBackup - The backup program

            

 

Section 5 : Operators and Assignments


Determine the result of applying any operator, including assignment operators, instanceof, and casts to operands of any type, class, scope, or accessibility, or any combination of these.


Evaluation Order  

All operands are evaluated left to right, even if the order of execution of the operations is something different. This is most noticeable in the case of assignments. For assignments, associativity is right to left.

Unary Operators

Increment/Decrement operators : Both x++ and ++x cause the same result in x. However, the apparent value of the expression itself is different.

Arithmetic operators 

Division and Multiplication

If you multiply first and then divide there is a risk of overflow. Dividing first and then multiplying almost definitely loses precision. It is better to multiply first, because it at least might work perfectly if the representation is long enough. 

Modulo ( % )

If you use negative operands the result’s sign is entirely determined by the sign of the left hand operand. Simply drop any negative sign from either operand and calculate the result. Then if the original left hand operand was negative, negate the result. The sign of the right operand is irrelevant.

When the modulo operator is applied to floating point types the result might well have a fractional part.

This operator can throw Arithmetic Exception if applied to integral types and the second operand is zero. 

NaN

Float.NaN and Double.NaN  are considered to be non-ordinal for comparisons i.e. for any value of x, including NaN itself any comparison with NaN will return false.

In fact, Float.NaN != Float.NaN will return true.

The most appropriate way to test for a NaN result from a calculation is to use static methods like Float.isNaN(float) or Double.isNaN(double).

The shift operators

These can be applied to arguments of integral types only. In fact, they should generally be applied to only operands of either int or long types.

The left shift <<  

This moves all of the bits in the specified value to the left by the number of bit positions specified by num. For each shift left, the high order bit is lost and a zero is brought in on the right. This means that when a left shift is applied to an int operand, bits are lost once they are shifted past bit position 31. If the operand is ‘long’ then bits are lost after bit position 63.

Java’s automatic type promotions produce unexpected results when you are shifting byte or short values. These values are promoted to int when an expression is evaluated and the result is also int. This means that the outcome of a left shift on a byte or short value will be an int, and the bits are not lost until they are past position 31. Furthermore, a negative byte or short will be sign-extended when it is promoted to int. Thus, the high order bits will be filled with 1s. For these reasons, to perform a left shift on a byte or short implies that you must discard the high order bytes of the int result. The easiest way to do this is to simply cast the result back to a byte or short.

Each left shift has the effect of doubling the original value. But, if you shift a 1 bit into a high order position (31 or 63) the value will become negative. 

The right shift >> 

Each time you shift a value to the right, it divides that value by two and discards the remainder. You can take advantage of this for high performance integer division by 2. Of course, you must be aware that you are not shifting any bits off the right end.

When you are shifting right, the top bits exposed by the right shift are filled in with the previous contents of the top bit. This is called sign-extension and serves to preserve the sign of negative numbers.

There is a feature of the arithmetic right shift that differs from simple division by two. If you divide –1 by 2, the result will be zero. However, it is interesting to note that if you shift –1 right, the result is always –1. the result of right shift operator applied to –1 is –1.

There is a feature of the arithmetic right shift that differs from simple division by two. If you divide –1 by 2, the result will be zero. However, the result of right shift operator applied to –1 is –1.

Arithmetic promotion of operands

Arithmetic promotion of operands takes place before any binary operator is applied so that all numeric operands are at least int type. This has an important consequence for the unsigned right shift operator when applied to values that are narrower than int. Take a byte for example. First it is promoted to int, which is done treating the byte as a signed quantity. Next, the shift occurs, and zero bits are propagated into the top bits of the result – but these bits are not part of the original byte. When the result is cast down to a byte again, the high order bits of that byte appear to have been created by a signed shift ( >> ), rather than an unsigned one.

This is why logical right shift operator generally should not be used with operands smaller than an int. It is unlikely to produce a result one expects.

The unsigned right shift (>>>) :

The >> operator automatically fills up the high order bit with it previous contents each time a shift occurs. This preserves the sign of the value. However, sometimes it is undesirable. For example, if you are shifting something that does not represent a numeric value, you may not want sign extension to take place. This situation is common when you are working with pixel based values and graphics. In such cases, you want to substitute the high order bit with zero, no matter what the initial value was. In such cases use unsigned shift.

The >>> operator is only meaningful for 32 and 64 bit values. Remember, smaller values are automatically promoted to int in expressions. This means that sign extension occurs and that shift will take place in a 32 bit rather than 8 or 16 bit value. That is one might expect an unsigned right shift on a byte value to zero fill beginning at bit 7. But this is not the case since it is a 32 bit value that is actually shifted.

The comparison/Relational operators

These operators return a boolean result. There are three types of comparison. Ordinal comparisons test the relative value of numeric operands. Object type comparisons determine if the runtime type of an object is of a particular type or a subclass of that particular type. Equality comparisons test if two values are the same and may be applied to values of non-numeric type.

Ordinal comparisons with <,<=,>,>=

These are applicable to all numeric types and to char and produce a boolean result. Arithmetic promotions are applied when these operators are used. Although it would be an error to assign, say, a float value to a char, it is perfectly in order to compare the two. In this case char is promoted to float and then compared.

instanceof operator

This is used to test the class of an object at runtime. The left hand operand can be any object reference expression, usually a variable or an array element, while the right hand operand must be a class, interface or array type. Java.lang.Class object or its string name cannot be used as right hand operand.

This operator can also be used to test whether a reference refers to an array. Since arrays are themselves objects in Java, this is natural enough, but the test actually checks two things : First it will check if the object is an array and then it will check if the element type of that array is some subclass of the element type of the right hand operand.

If the left hand operand is a null value, this operator simply returns false. It does not cause an exception.

Equality comparison operators ( == and != )

For variables of object type, these operators compare the references themselves rather than the contents of the objects. To achieve a content or semantic comparison, one should use equals() method. To function properly, the equals() method must have been defined for the class of the objects being compared. If it is not, then equals() too behaves like == operator.

For x.equals(y) the test y instanceof x must be true. If this is not the case, then equals() must return false.

If you define equals() in your own classes, you should be careful to observe two rules: 1. First the argument to the equals() method is an object. You must avoid the temptation to make the argument specific to your own class you are defining. If you do this, you have overloaded this method, not overridden it. This means that functionality in the other parts of the Java API that depends on the equals() method will fail. Most significantly, perhaps, lookup methods in containers, such as containsKey() and get() in the HashMap, will fail. 2. The second rule you must observe is that if you define an equals() method, you should also define a hashCode() method. This method should return the same value for objects that compare equal using the equals() method. Again, this behavior is needed to support the containers, and other classes.

The bitwise operators : &, ^, and |

These operators manipulate the bits within an integer. All the integer types are represented by binary numbers of varying bit widths. Java uses an encoding known as two’s complement, which means that negative numbers are represented by inverting all of the bits in a value, then adding 1 to the result. For example, 

42 = 00101010

-42 = (11010101) +1 = 11010110 

This addition of 1 is applied even when changing negative to positive.

The reason Java ( and most other languages ) uses two’s complement is easy to see when you consider the issue of zero crossing. Assuming a byte value, zero is 0000 0000. in one’s complement, simply inverting all the bits gives 1111 1111 which creates negative zero. The trouble is that negative zero is invalid in integer math. This problem is solved by using two’s complement to represent negative values. When using two’s complement, 1 is added to the complement, producing 1 0000 0000. This produces a 1 bit too far to the left to fit back into the byte value, resulting in the desired behavior, where negative zero is same as 0, and 1111 1111 is the encoding for –1.

Because Java uses two’s complement to store negative numbers - and because all integers are signed values in Java – applying bitwise operators can easily produce unexpected results. For example, turning on the high order bit will cause the resulting value to be interpreted as a negative number, whether this is what you intended or not. To avoid unpleasant surprises, just remember that the high order bit determines the sign of an integer no matter how that high order bit gets set. 

Collections of bits are sometimes used to save storage space where several boolean values are needed or to represent the states of a collection of binary inputs from physical devices.

These operators are applicable to integral types. Collections of bits are sometimes used to save storage space where several boolean values are needed or to represent the states of a collection of binary inputs from physical devices.

The bitwise operators calculate each bit of their results by comparing the corresponding bits of the two operands on the basis of these three rules :

  • For AND operators, 1&1 produces 1. Else zero.

  • For XOR, 1^0 produces 1 as does 0^1. Else zero.

  • For |,  0|0 produces 0. Else 1.

It is also permitted to apply these operators to boolean operands.

The short circuit logical operators : && and ||

These are applicable to only on boolean types, and not integral types. Although these shortcuts do not affect the result of the operation, side effects might well be changed. If the evaluation of the right hand operand involves a side effect, then omitting the evaluation will change the overall meaning of the expression. This behavior distinguishes these operators from the bitwise operators applied to boolean types.

The ternary operator    ?:

This operator provides a way to code simple if-else conditions into a single expression. The (boolean) expression left of the ? is evaluated. If true, the result of the whole expression is the value of the sub-expression to the left of the colon, otherwise it is the value of the sub-expression to the right of the colon. The sub-expressions on either side of the colon must be of the same type.

 


section5-1-1 | section5-1-2 | section5-2 | section5-3 | section5-4

Sections : 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

 

 

 

 

Home | Blog | Java | Jokes | Poems | Musings | Site Map | Kudos | Downloads | Useful Sites | Interesting | System Setup | Contact  

Loading

 Number of Pages viewed on this site since January' 2003 : Hit Counter eXTReMe Tracker

For any queries, comments or suggestions, write to me .

This site never compromises your privacy, please read this site's privacy policy.