Operators and Assignments

Precedence and Associativity Rules for Operators

   Precedence and associativity rules are necessary for deterministic evaluation of expressions.
The operators are summarized in the following-

Postfix operators                         [] .(parameters) expression++ expression--
Unary prefix operators                ++expression --expresion +expression -expression
Unary prefix creation and cost    new(type)
Multiplicative                              * / %
Additive                                       + -
Shift                                             << >> >>>
Relational                                     <   <=   >  >= instanceof
Equality                                         ==  !=
Bitwise/logical AND                     &
Bitwise/logical XOR                     ^
Bitwise/logical OR                         |
Conditional  AND                         &&
Conditional  OR                             ||
Conditional                                    ?:
Assignment                                   + += -= *= /= %/ << = >>= >>>= &=  ^= |=

The operators are shown with decreasing precedence from the top of the table.
Operators within the same row have the same precedence.
Parentheses, ( ), can be used to override precedence and associativity.
The unary operators, which require one operand, include the postfix increment (++) and decrement (--) operators from the first row, all the prefix operators (+, -, ++, --, ~, !) in the second  row, and the prefix operators (object creation operator new, cast operator (type)) in the third row.
The conditional operator (? :) is ternary, that is, requires three operands.
All operators not listed above as unary or ternary, are binary, that is, require two operands.
All binary operators, except for the relational and assignment operators, associate from left to right. The relational operators are nonassociative.
Except for unary       postfix increment and decrement operators, all unary operators, all assignment
operators, and the ternary conditional operator associate from right to left.  Precedence rules are used to determine    which    operator   should be   applied first   if there   are two  operators with different precedence, and   these follow   each   other in the    expression. In such a case, the operator with the highest precedence is applied first. 2 + 3 * 4 is evaluated as 2 + (3 * 4) (with the result 14) since * has higher precedence than +.
      Associativity rules are used to determine which operator should be applied first if there are two
operators with the same precedence, and these follow each other in the expression.
  Left associativity implies grouping from left to right: 1 + 2 - 3 is interpreted as ((1 + 2) - 3), since
the binary operators + and - both have same precedence and left associativity.
  Right associativity implies grouping from right to left: - - 4 is interpreted as (- (- 4)) (with the
result 4), since the unary operator - has right associativity.
     The precedence and associativity rules together determine the evaluation order of the operators.

  

Evaluation Order of Operands

In order to understand the result returned by an operator, it is important to understand the evaluation order of its operands. Java states that the operands of operators are evaluated from left to right.
      Java guarantees that all operands of an operator are fully evaluated before the operator is applied.
The only exceptions are the short-circuit conditional operators &&, ||, and ?:.  In the case of a binary operator, if the left-hand operand causes an exception the right-hand   operand is not evaluated. The evaluation of the left-hand operand can have side effects that can influence the value of the right-hand operand. For example, in the following code:
int b = 10;
System.out.println((b=3) + b);
the value printed will be 6 and not 13. The evaluation proceeds as follows: (b=3) + b
3 + b b is assigned the value 3
3 + 3
6
The evaluation order also respects any parentheses, and the precedence and associativity rules of
operators.

  Conversions

In this section we discuss the different kinds of type conversions and list the contexts in which these can occur. Some type conversions must be explicitly stated in the program, while others are      done implicitly. Some type      conversions can    be checked at compile time to guarantee their validity at runtime, while others will require an extra check at runtime.

 

Unary Cast Operator: (type)

Java, being a strongly typed language, checks for type compatibility (i.e., checks if a type can substitute for another type in a given context) at compile time. However, some checks are only
possible at runtime (for example, which type of object a reference actually denotes during
execution). In cases where an operator would have incompatible operands (for example, assigning
a double to an int), Java demands that a cast be used to explicitly indicate the type conversion. The
cast construct has the following syntax:
(<type>) <expression>
The cast (<type>) is applied to the value of the <expression>. At runtime, a cast results in a new
value of <type>, which best represents the value of the <expression> in the old type. We use the
term casting to mean applying the cast operator for explicit type conversion.
Casting can be applied to primitive values as well as references. Casting between primitive data
types and reference types is not permitted. Boolean values cannot be cast to other data values, and
vice versa. The reference literal null can be cast to any reference type.

 

 

Narrowing and Widening Conversions

For the primitive data types, the value of a narrower data type can be converted to a value of a
broader data type without loss of information. This is called a widening primitive conversion. The conversions shown are transitive. For example, an int can be directly converted to a
double without first having to convert it to a long and a float.

byte-------->short--,
                                \
                                  ---------->int------->long-------->double
                                /
                    char-- '

     Converting from a broader data type to a narrower data type is called a narrowing primitive
conversion, which can result in loss of magnitude information.   

  Numeric Promotions

Numeric operators only allow operands of certain types. Numeric promotion is implicitly applied
on the operands to convert them to permissible types. Distinction is made between unary and
binary numeric promotion.

 

Unary Numeric Promotion

    If the single operand of the operator has a type narrower than int, it is converted to int by an implicit  widening primitive conversion; otherwise, it is not converted.
     In other words, unary numeric promotion converts operands of byte, short and char to int by applying an implicit widening conversion, but operands of other numeric types are not affected.
Unary numeric promotion is applied in the following contexts:
operand of the unary arithmetic operators
operand of the unary integer bitwise complement operator
during array creation; for example, new int[20], where the dimension expression (in this case
20) must evaluate to an int value.
indexing array elements; for example, table['a'], where the index expression (in this case 'a')
must evaluate to an int value
individual operands of the shift operators <<, >> and >>>

 

Binary Numeric Promotion

Binary numeric promotion implicitly applies appropriate widening primitive conversions so that a
pair of operands have the broadest numeric type of the two, which is always at least int. Given T to
be the broadest numeric type of the two operands, the operands are promoted as follows under
binary numeric promotion. If T is broader than int, both operands are converted to T; otherwise,
both operands are converted to int.
This means that byte, short, and char are always converted to int at least.
Binary numeric promotion is applied in the following contexts:
operands of the arithmetic operators *, /, %, +, and –
operands of the relational operators <, <=, >, and >=
operands of the numerical equality operators == and !=
operands of the integer bitwise operators &, ^, and |

 

Type Conversion Contexts

Type conversions can occur in the following contexts:
assignments involving primitive data types and reference types
method invocation involving parameters of primitive data types and reference types
arithmetic expression evaluation involving numeric types
string concatenation involving objects of class String and other data types.

 

Numeric Type Conversions on Assignment

If the destination and source have the same type in an assignment, then, obviously, the source and
the destination are type compatible, and the source value need not be converted. Otherwise, if a
widening primitive conversion is permissible, then the widening conversion is applied implicitly,
that is, the source type is promoted to the destination type in an assignment context.
// Implicit Widening Primitive Conversions
int smallOne = 1234;
long bigOne = 2000; // Implicit widening: int to long.
double largeOne = bigOne; // Implicit widening: long to double.
double hugeOne = (double) bigOne; // Cast redundant but allowed.
Integer values widened to floating-point values can result in loss of precision. Precision relates to
the number of significant bits in the value, and must not be confused with magnitude, which
relates how big a value can be represented. In the next example, the precision of the least
significant bits of the long value may be lost when converting to a float value.
long bigInteger = 98765432112345678L;
float realNo = bigInteger; // Widening but loss of precision: 9.8765436E16



No comments:

Post a Comment