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.