Java Full Course: Mastering the Language Operators & Expressions

Operators & Expressions


1. Introduction to Operators and Expressions

In the realm of programming, a program is essentially a set of instructions that manipulate data to produce a desired output. At the heart of this data manipulation lie two fundamental concepts: Operators and Expressions. They form the core vocabulary of any programming language, allowing us to perform computations, make decisions, and control the flow of a program.

More formally:

  • An Operator is a symbol or a keyword that tells the compiler or interpreter to perform a specific mathematical, logical, or relational operation. Common examples include + (addition), - (subtraction), * (multiplication), == (equality check), and && (logical AND).
  • An Expression is a combination of variables, constants, literals, operators, and function calls that evaluates to a single value. For instance, a + b, x > y, and price * quantity - discount are all expressions.

The power of programming languages stems from their rich set of operators and the flexibility with which they can be combined into expressions. From simple arithmetic calculations to complex logical conditions that determine which part of a program executes, operators and expressions are the fundamental building blocks.


2. Arithmetic Operators in Java

Arithmetic operators are used to perform mathematical operations on numeric data types (byte, short, int, long, float, double) and char (which is treated as a numeric type in arithmetic contexts). They form the foundation for calculations in Java programs.

2.1 List of Arithmetic Operators

Java provides the following arithmetic operators:

OperatorNameTypeDescription
+AdditionBinaryAdds two operands
-SubtractionBinarySubtracts right operand from left
*MultiplicationBinaryMultiplies two operands
/DivisionBinaryDivides left operand by right
%ModulusBinaryReturns remainder of division
++IncrementUnaryIncreases value by 1
--DecrementUnaryDecreases value by 1
+Unary plusUnaryIndicates positive value (rarely used)
-Unary minusUnaryNegates the value

2.2 Binary Arithmetic Operators (+, -, *, /, %)

Binary operators work on two operands.

a) Addition (+)

  • Adds numbers.
  • If either operand is a String, it performs string concatenation instead (this is a special case, not arithmetic).
  • Example:
int sum = 10 + 5; // 15 double d = 2.5 + 1.5; // 4.0

b) Subtraction (-)

  • Subtracts the right operand from the left.
  • Example:
int diff = 10 - 3; // 7

c) Multiplication (*)

  • Multiplies two operands.
  • Example:
int product = 4 * 7; // 28

d) Division (/)

  • Integer division: When both operands are integers, the result is an integer (truncated toward zero). Fractional part is discarded.
  • Floating-point division: If at least one operand is float or double, the result is a floating-point value.
  • Example:
int a = 10 / 3; // 3 (integer division) double b = 10 / 3; // 3.0 (because 10 and 3 are ints, integer division happens first, then promoted to double) double c = 10.0 / 3; // 3.3333333333333335 double d = 10 / 3.0; // 3.3333333333333335
Warning

[!WARNING] Division by zero:

  • Integer division by zero throws ArithmeticException.
  • Floating-point division by zero results in Infinity or NaN (Not-a-Number).

e) Modulus (%)

  • Returns the remainder after division.
  • Works with integers and floating-point numbers.
  • Sign of the result follows the sign of the dividend (left operand).
  • Example:
int r1 = 10 % 3; // 1 int r2 = -10 % 3; // -1 int r3 = 10 % -3; // 1 double r4 = 7.5 % 2.5; // 0.0

2.3 Unary Arithmetic Operators

Unary operators operate on a single operand.

a) Unary Plus (+) and Unary Minus (-)

  • Unary plus simply returns the operand's value (rarely used).
  • Unary minus negates the operand.
  • Example:
int x = 5; int y = -x; // y = -5 int z = +x; // z = 5

b) Increment (++) and Decrement (--)

  • Increase or decrease a numeric variable by 1.
  • Two forms: prefix and postfix.
FormEffect
++varIncrement var by 1, then use the new value.
var++Use the current value of var, then increment it by 1.
--varDecrement var by 1, then use the new value.
var--Use the current value of var, then decrement it by 1.
  • Example:
int a = 5; int b = ++a; // a becomes 6, b gets 6 int c = a--; // c gets 6, then a becomes 5
Crucial

[!IMPORTANT] They can be applied only to variables, not to constants or expressions.


2.4 Type Conversion in Arithmetic

Java performs binary numeric promotion when evaluating arithmetic expressions:

  • If either operand is double, the other is converted to double.
  • Else if either operand is float, the other is converted to float.
  • Else if either operand is long, the other is converted to long.
  • Else both operands are converted to int (even if they are byte, short, or char).

This means that arithmetic on byte, short, or char results in an int. To store the result back, an explicit cast may be required.

byte b1 = 10; byte b2 = 20; // byte b3 = b1 + b2; // Compilation error: b1+b2 results in int byte b3 = (byte) (b1 + b2); // OK, with cast

2.5 Overflow and Underflow

Arithmetic with integer types does not throw exceptions on overflow/underflow; it wraps around silently.

int max = Integer.MAX_VALUE; // 2147483647 int overflow = max + 1; // -2147483648 (wraps to minimum)
Note

[!CAUTION] For long, overflow wraps around. For float and double, overflow results in Infinity or -Infinity.


2.6 Compound Assignment Operators

These combine an arithmetic operation with assignment: +=, -=, *=, /=, %=.

int x = 10; x += 5; // equivalent to x = x + 5; → x = 15 x *= 2; // x = x * 2; → x = 30
Tip

[!TIP] Compound assignments perform an implicit cast to the left-hand side type, which can prevent compilation errors in some cases. byte b = 10; b += 5; works perfectly, whereas b = b + 5; would cause an error because b+5 becomes an int.


2.7 Operator Precedence

Arithmetic operators follow standard mathematical precedence:

PriorityCategoryOperators
1 (Highest)Unary++, --, +, -
2Multiplicative*, /, %
3 (Lowest)Additive+, -

Parentheses () can be used to override precedence.

int result = 5 + 2 * 3; // 5 + 6 = 11 int result2 = (5 + 2) * 3; // 7 * 3 = 21

2.8 Important Notes

Character Arithmetic

char values are treated as integers (their Unicode code points).

char ch = 'A'; ch++; // ch becomes 'B' int value = 'C' - 'A'; // 2

String Concatenation

The + operator, when at least one operand is a String, performs concatenation, not arithmetic.

System.out.println(10 + 20 + "Hello"); // "30Hello" System.out.println("Hello" + 10 + 20); // "Hello1020"

Evaluation Order

Expressions are evaluated from left to right respecting precedence and associativity. Associativity for binary arithmetic operators is left-to-right.


3. Relational Operators in Java

Relational operators (also called comparison operators) are used to compare two values. They evaluate to a boolean result—either true or false. These operators are fundamental for decision-making, loops, and control flow in Java programs.

3.1 List of Relational Operators

Java provides six relational operators:

OperatorNameDescription
==Equal toReturns true if both operands are equal
!=Not equal toReturns true if operands are not equal
>Greater thanReturns true if left > right
<Less thanReturns true if left < right
>=Greater than or equal toReturns true if left >= right
<=Less than or equal toReturns true if left <= right

All relational operators are binary operators (they operate on two operands).


3.2 Usage and Examples

a) Equality Operators: == and !=

  • Compare two operands for equality or inequality.
  • Can be used with numeric types, characters, and reference types (objects).
  • For objects, == checks if the references point to the same object, not whether the objects are logically equivalent. For logical equality, use the equals() method.

Numeric and character examples:

int a = 5, b = 10; boolean result1 = (a == b); // false boolean result2 = (a != b); // true char c1 = 'A', c2 = 'B'; boolean result3 = (c1 == c2); // false (compares Unicode values)

Object reference example:

String s1 = new String("Hello"); String s2 = new String("Hello"); boolean refEqual = (s1 == s2); // false (different objects) boolean contentEqual = s1.equals(s2); // true (logical equality)

b) Relational Operators: <, >, <=, >=

  • Used only with numeric types (including char).
  • Compare magnitudes.
int x = 7, y = 3; System.out.println(x > y); // true System.out.println(x < y); // false System.out.println(x >= 7); // true System.out.println(y <= 2); // false double d1 = 4.5, d2 = 4.5; System.out.println(d1 >= d2); // true

3.3 Relational Operators on Different Types

  • Numeric types: All relational operators work seamlessly across numeric types. Java performs binary numeric promotion before comparison (e.g., int compared with double promotes the int to double).
int i = 10; double d = 10.0; System.out.println(i == d); // true
  • Characters: char values are compared based on their Unicode code points.
System.out.println('a' < 'b'); // true (97 < 98)
  • Booleans: == and != can be used with boolean values, but <, >, etc., are not allowed.
boolean flag1 = true, flag2 = false; System.out.println(flag1 == flag2); // false // System.out.println(flag1 > flag2); // Compilation error
  • Reference types: Only == and != are allowed (compare references). Using < or > on objects results in a compilation error unless the objects implement Comparable and you use compareTo().

3.4 Precedence and Associativity

Relational operators have lower precedence than arithmetic operators but higher than logical operators.

  • Arithmetic (+, -, *, /, %) → higher than relational
  • Relational (<, >, <=, >=) → higher than equality (==, !=)
  • Equality (==, !=) → lower than relational
  • All relational operators are left associative (evaluated left to right).
int a = 5, b = 10, c = 15; boolean result = a + b > c; // (5+10) > 15 → 15 > 15 → false

Parentheses can be used to clarify or override precedence:

boolean result = (a < b) == (c > b); // true == true → true

3.5 Common Pitfalls and Best Practices

a) Mixing = and ==

  • = is assignment; == is comparison.
  • Accidentally using = in a condition is a common error.
int x = 5; // if (x = 10) { ... } // Compilation error: int cannot be converted to boolean // Correct: if (x == 10)

For boolean variables, this can be particularly tricky:

boolean flag = false; if (flag = true) { ... } // This is valid but always true; intended was flag == true

b) Floating-Point Comparisons

  • Floating-point numbers (float, double) should not be compared directly with == due to precision issues. Instead, use a tolerance.
double a = 0.1 + 0.2; // 0.30000000000000004 double b = 0.3; if (Math.abs(a - b) < 1e-9) { ... } // correct way

c) Chaining Comparisons

  • Java does not support chained comparisons like a < b < c. This must be written as a < b && b < c.
// if (10 < x < 20) // Invalid if (10 < x && x < 20) // Correct

3.6 Using Relational Operators in Control Flow

Relational operators are most often used in conditions for if statements, while loops, and for loops.

int age = 18; if (age >= 18) { System.out.println("Eligible to vote"); } else { System.out.println("Not eligible"); } int count = 0; while (count < 5) { System.out.println(count); count++; }

3.7 Summary Table

OperatorUse CaseExample (x=5, y=3)Result
==Equalityx == yfalse
!=Inequalityx != ytrue
>Greater thanx > ytrue
<Less thanx < yfalse
>=Greater than or equalx >= 5true
<=Less than or equaly <= 2false

3.8 Key Points to Remember

  • All relational operators return a boolean (true or false).
  • They can be used with all primitive numeric types and char.
  • == and != also work with boolean and with object references.
  • For object comparisons, use equals() for logical equality; == only checks reference identity.
  • Relational operators cannot be used with arrays or non-primitive types except for reference equality.
  • Avoid direct == comparison of floating-point numbers due to precision issues.

4. Logical Operators in Java

Logical operators are used to combine multiple boolean expressions or values. They operate on boolean operands and return a boolean result. These operators are essential for building complex conditions in decision-making and looping constructs.

4.1 List of Logical Operators

Java provides three primary logical operators:

OperatorNameDescription
&&Logical ANDReturns true if both operands are true. Short-circuiting.
||Logical ORReturns true if at least one operand is true. Short-circuiting.
!Logical NOTReturns the opposite of the operand. Unary operator.

Additionally, Java has non-short-circuiting versions (rarely used):

OperatorNameDescription
&Boolean ANDSame as && but always evaluates both operands.
|Boolean ORSame as || but always evaluates both operands.
^Logical XORReturns true if operands are different (exclusive OR).
Note

[!NOTE] The &, |, and ^ operators also serve as bitwise operators when applied to integer types. When applied to boolean, they behave as logical operators.


4.2 Short-Circuit Operators: && and ||

a) Logical AND (&&)

  • Evaluates the left operand first.
  • If the left operand is false, the result is false and the right operand is not evaluated.
  • This short-circuit behavior prevents unnecessary computations and avoids potential errors.

Truth table for &&:

aba && b
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse
int a = 5, b = 0; if (b != 0 && a / b > 1) { // b != 0 is false, so a/b is NOT evaluated System.out.println("Safe division"); } else { System.out.println("Division by zero avoided"); }

b) Logical OR (||)

  • Evaluates the left operand first.
  • If the left operand is true, the result is true and the right operand is not evaluated.

Truth table for ||:

aba || b
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse
String name = null; if (name == null || name.isEmpty()) { // name == null is true, so name.isEmpty() not called System.out.println("Name is empty or null"); }

4.3 Non-Short-Circuit Logical Operators: & and |

  • & (boolean AND): Always evaluates both operands, even if the left is false.
  • | (boolean OR): Always evaluates both operands, even if the left is true.
  • These are used when you need side effects of the right operand to occur regardless of the left operand's value.
int x = 10; if (x > 5 & ++x < 20) { // both sides evaluated; x becomes 11 // ... } System.out.println(x); // 11 // With &&, x would remain 10 because the second part wouldn't be evaluated.
Note

[!CAUTION] Using & and | with boolean operands is uncommon and can lead to confusing code. Prefer short-circuit operators unless you specifically require both evaluations.


4.4 Logical XOR: ^

  • Exclusive OR: returns true if the operands are different; false if they are the same.

Truth table for ^:

aba ^ b
truetruefalse
truefalsetrue
falsetruetrue
falsefalsefalse
boolean rain = true; boolean umbrella = false; boolean getWet = rain ^ umbrella; // true (rain true, umbrella false → different)

XOR is sometimes used in toggle operations or parity checks.


4.5 Logical NOT: !

  • Unary operator that negates a boolean value.

Truth table for !:

a!a
truefalse
falsetrue
boolean isAdult = age >= 18; if (!isAdult) { System.out.println("Minor"); }

4.6 Precedence and Associativity

The logical operators have the following precedence (from highest to lowest):

  1. ! (logical NOT)
  2. & (boolean AND)
  3. ^ (logical XOR)
  4. | (boolean OR)
  5. && (short-circuit AND)
  6. || (short-circuit OR)

Associativity:

  • ! is right associative.
  • &, ^, |, &&, || are left associative.
boolean result = !a && b || c; // equivalent to ((!a) && b) || c

Parentheses can be used to override precedence and improve readability.


4.7 Combining Relational and Logical Operators

Logical operators are typically used to combine relational expressions.

int marks = 85; char grade; if (marks >= 90) { grade = 'A'; } else if (marks >= 80 && marks < 90) { grade = 'B'; } else if (marks >= 70 && marks < 80) { grade = 'C'; } else { grade = 'F'; }

4.8 Short-Circuit Benefits

  • Performance: Avoids unnecessary evaluations.
  • Safety: Guards against runtime exceptions (e.g., null checks, division by zero).
  • Idiomatic usage: The && and || operators are the standard in Java for combining conditions.

4.9 Common Pitfalls

a) Using & or | instead of && or || unintentionally

  • & and | have lower precedence than relational operators but higher than && and ||. Mixing them can lead to unexpected results.
if (a > 5 & b++ < 10) { ... } // b increments even if a <= 5

b) Confusing && with & in bitwise contexts

  • For integer types, & is bitwise AND; for booleans, it's logical AND. This overload can cause confusion.

c) Negating complex conditions incorrectly

  • De Morgan's laws are essential for simplifying negated conditions:
    • !(a && b)!a || !b
    • !(a || b)!a && !b
// Instead of: if (!(x > 0 && x < 10)) { ... } // Use: if (x <= 0 || x >= 10) { ... }

4.10 Summary Table

OperatorNameShort-circuit?Example (a=true, b=false)Result
&&Logical ANDYesa && bfalse
||Logical ORYesa || btrue
!Logical NOTN/A!afalse
&Boolean ANDNoa & bfalse
|Boolean ORNoa | btrue
^Logical XORNoa ^ btrue

4.11 Key Points to Remember

  • Use && and || for most conditional logic; they short-circuit and are safer.
  • Use ! to negate a boolean expression.
  • & and | are primarily for bitwise operations; when used with booleans they are non-short-circuiting.
  • Understand precedence to avoid subtle bugs; when in doubt, use parentheses.
  • Apply De Morgan's laws to simplify negated conditions.

5. Assignment Operators in Java

Assignment operators are used to assign a value to a variable. They combine the assignment operation with an optional arithmetic or bitwise operation. Java provides a simple assignment operator and several compound assignment operators.

5.1 Simple Assignment Operator (=)

  • The most basic assignment operator.
  • Assigns the value on the right-hand side (RHS) to the variable on the left-hand side (LHS).
  • The LHS must be a variable (or array element, field) — not a constant or expression.

Syntax:

variable = expression;

Examples:

int x = 10; // assigns 10 to x String name = "Java"; // assigns reference to name x = x + 5; // assigns 15 to x
  • The assignment operator returns the assigned value, so assignments can be chained.
  • The associativity of = is right to left.

Chaining:

int a, b, c; a = b = c = 100; // c=100, then b=c, then a=b → all get 100

5.2 Compound Assignment Operators

Compound assignment operators combine an operation with assignment, providing a concise way to modify a variable.

OperatorExampleEquivalent To
+=a += ba = a + b
-=a -= ba = a - b
*=a *= ba = a * b
/=a /= ba = a / b
%=a %= ba = a % b
&=a &= ba = a & b (bitwise AND)
|=a |= ba = a | b (bitwise OR)
^=a ^= ba = a ^ b (bitwise XOR)
<<=a <<= ba = a << b (left shift)
>>=a >>= ba = a >> b (signed right shift)
>>>=a >>>= ba = a >>> b (unsigned right shift)

Examples:

int x = 10; x += 5; // x = 15 x -= 3; // x = 12 x *= 2; // x = 24 x /= 4; // x = 6 x %= 5; // x = 1 int y = 5; y &= 3; // 5 & 3 = 1 → y = 1 y |= 2; // 1 | 2 = 3 → y = 3 y ^= 1; // 3 ^ 1 = 2 → y = 2 y <<= 1; // 2 << 1 = 4 → y = 4 y >>= 1; // 4 >> 1 = 2 → y = 2

5.3 Important Characteristics of Compound Assignment

a) Implicit Type Casting

Compound assignment operators perform an implicit narrowing primitive conversion if necessary. This means they automatically cast the result to the type of the left operand, even if that would normally cause a loss of precision.

byte b = 10; b += 5; // allowed; equivalent to b = (byte)(b + 5) // Without compound assignment: // b = b + 5; // compilation error: b + 5 is int
Tip

[!TIP] This implicit cast makes compound assignments safe in situations where the simple assignment would require an explicit cast.

b) They Evaluate the Left Operand Only Once

Compound assignment evaluates the left operand only once. This matters when the left operand has side effects (e.g., array access with index computation).

int[] arr = {0, 1, 2}; int i = 0; arr[i++] += 5; // arr[0] = arr[0] + 5; i increments after System.out.println(i); // prints 1

c) Assignment as an Expression

Like the simple =, compound assignments return the value that was assigned. This allows them to be used within larger expressions.

int a = 5; int b = (a += 3); // a becomes 8, then b = 8

5.4 Precedence and Associativity

  • All assignment operators have the lowest precedence of all operators in Java.
  • They are right associative (evaluated from right to left).
int a, b, c; a = b = c = 10; // works because = is right associative // (c = 10) → (b = c) → (a = b)

Because of low precedence, parentheses are rarely needed when assignments are separate statements. However, when used inside expressions, parentheses may be required to avoid unintended evaluation order.


5.5 Common Pitfalls and Best Practices

a) Confusing = with ==

  • = is assignment, == is comparison.
// if (x = 5) { ... } // Compiles only if x is boolean; otherwise error

b) Overflow and Underflow

Compound assignments do not prevent overflow; they simply perform the operation and assign the potentially truncated result.

byte b = 127; b += 1; // b becomes -128 (overflow)

c) Using Compound Assignment with String

The += operator is overloaded for String — it performs concatenation, not arithmetic addition.

String s = "Hello"; s += " World"; // s = "Hello World"

d) Avoid Unnecessary Compound Assignments

Compound assignments improve conciseness but may reduce readability when used with complex expressions. Use them judiciously.


5.6 Summary Table

OperatorTypeExampleEquivalent ToCasting Behavior
=Simple assignmenta = ba = bNo implicit cast
+=Add & assigna += ba = a + bImplicit cast to a's type
-=Subtract & assigna -= ba = a - bImplicit cast
*=Multiply & assigna *= ba = a * bImplicit cast
/=Divide & assigna /= ba = a / bImplicit cast
%=Modulus & assigna %= ba = a % bImplicit cast
&=Bitwise AND & assigna &= ba = a & bImplicit cast
|=Bitwise OR & assigna |= ba = a | bImplicit cast
^=Bitwise XOR & assigna ^= ba = a ^ bImplicit cast
<<=Left shift & assigna <<= ba = a << bImplicit cast
>>=Right shift & assigna >>= ba = a >> bImplicit cast
>>>=Unsigned right shift & assigna >>>= ba = a >>> bImplicit cast

5.7 Key Points to Remember

  • The simple assignment operator = assigns the value of the right operand to the left operand and returns the assigned value.
  • Compound assignment operators combine an operation with assignment and implicitly cast the result to the left operand's type.
  • Assignment operators have the lowest precedence and are right associative.
  • Be cautious with overflow, and remember that += on String performs concatenation.
  • Use compound assignments for concise code, but ensure readability.

6. Increment and Decrement Operators in Java

Increment (++) and decrement (--) operators are unary operators that add 1 or subtract 1 from a numeric variable. They are concise and commonly used in loops, counters, and pointer-like operations. These operators have two forms: prefix and postfix, which differ in when the increment or decrement takes effect.

6.1 Syntax and Basic Behavior

OperatorNameEffect
++IncrementIncreases the operand by 1
--DecrementDecreases the operand by 1

Both operators can appear before (prefix) or after (postfix) the operand.

  • Prefix: ++var or --var The variable is incremented/decremented first, and the new value is used in the surrounding expression.
  • Postfix: var++ or var-- The original value of the variable is used in the surrounding expression, then the variable is incremented/decremented.

6.2 Prefix vs. Postfix – Detailed Examples

a) Standalone Use

When used alone as a statement, prefix and postfix produce the same final result.

int x = 5; x++; // x becomes 6 ++x; // x becomes 7

b) Inside Expressions

The distinction becomes critical when the operator is used within a larger expression.

int a = 5; int b = ++a; // a increments to 6, then b gets 6 System.out.println("a = " + a + ", b = " + b); // a = 6, b = 6 int c = 5; int d = c++; // d gets the original value 5, then c increments to 6 System.out.println("c = " + c + ", d = " + d); // c = 6, d = 5

c) In Loops

Both forms are common, but postfix is more conventional in for loops:

for (int i = 0; i < 10; i++) { // postfix is typical System.out.println(i); }

However, prefix works equally well here because the loop update is a standalone statement.


6.3 Applicable Types

Increment and decrement operators can be applied to:

  • All integer types (byte, short, int, long)
  • Floating-point types (float, double)
  • char (increments the Unicode code point)
  • Not applicable to boolean or reference types.
double d = 3.5; d++; // d becomes 4.5 char ch = 'A'; ch++; // ch becomes 'B' (Unicode 65 → 66)

6.4 Precedence and Associativity

  • Increment and decrement operators have very high precedence – higher than arithmetic, relational, and logical operators.
  • They are right associative (evaluate from right to left when chained).
int x = 5; int y = ++x * 2; // (++x) first: x becomes 6, then 6 * 2 = 12
Crucial

[!IMPORTANT] Only one increment/decrement can be applied directly to a variable; ++(++i) is not allowed because the operand of ++ must be a variable, not a value.


6.5 Side Effects and Order of Evaluation

Because increment/decrement modify variables, they can lead to subtle bugs when used in complex expressions. The evaluation order in Java is deterministic: operands are evaluated left to right, but the actual effect of prefix/postfix depends on when the increment occurs.

Example with multiple operators:

int i = 2; int j = i++ + ++i; // i++ → uses 2, i becomes 3; ++i → increments to 4, uses 4; total = 6 System.out.println("i = " + i + ", j = " + j); // i = 4, j = 6

Another:

int a = 1; a = a++; // What happens? // a++ returns original value (1), then a increments to 2, // then the expression result (1) is assigned to a → a = 1. System.out.println(a); // 1
Warning

[!WARNING] Such expressions are often confusing and should be avoided in production code.


6.6 Common Use Cases

a) Loop Counters

for (int i = 0; i < 10; i++) { // loop body }

b) Array/Index Traversal

int[] arr = {10, 20, 30}; int index = 0; while (index < arr.length) { System.out.println(arr[index++]); // prints arr[0], arr[1], arr[2] }

c) Compact Assignment

int count = 5; count++; // instead of count = count + 1;

d) In Conditions

int x = 0; while (x++ < 5) { System.out.println(x); // prints 1,2,3,4,5 }

6.7 Pitfalls and Best Practices

a) Avoid Using Increment/Decrement Inside Complex Expressions

Expressions like result = i++ + ++i are error-prone and hard to read. Prefer splitting into separate statements.

Better:

int result = i + j; i++; j++;

b) Not Applicable to Constants or Literals

// 5++; // Compilation error: invalid argument to increment

c) Mixed with Assignment – Can Be Confusing

int i = 5; i = i++; // i remains 5 (postfix returns original, then i increments, then assignment overwrites)

d) In Method Arguments

When passed to a method, the increment happens before or after the argument is evaluated depending on prefix/postfix.

int x = 10; System.out.println(x++); // prints 10, then x becomes 11 System.out.println(++x); // x increments to 12, then prints 12

6.8 Type Promotion and Casting

  • For byte, short, char, increment/decrement work without explicit cast because they act as if the variable is temporarily promoted to int, but the result is stored back with an implicit narrowing conversion (if within range).
byte b = 127; b++; // b becomes -128 (overflow, but allowed)
  • For floating-point, increment adds exactly 1.0 (or 1.0f for float).

6.9 Summary Table

FormExampleEffect on VariableValue Used in Expression
Prefix increment++xIncrement firstNew (incremented) value
Postfix incrementx++Increment afterOriginal value
Prefix decrement--xDecrement firstNew (decremented) value
Postfix decrementx--Decrement afterOriginal value

6.10 Key Points to Remember

  • Increment (++) and decrement (--) add or subtract 1 from a numeric variable.
  • Prefix updates the variable before using its value; postfix uses the value first, then updates.
  • They can be applied to numeric primitives and char, but not to boolean or objects.
  • Use them sparingly in complex expressions to maintain readability.
  • In loops, postfix is conventional but not mandatory; both work for standalone updates.
  • Watch out for overflow/underflow, especially with byte and short.

7. Conditional Operator (Ternary Operator) in Java

The conditional operator (? :) is Java's only ternary operator—it takes three operands. It provides a concise way to evaluate a boolean condition and return one of two values based on the result. Often used as a shorthand for simple if-else statements.

7.1 Syntax

condition ? expressionIfTrue : expressionIfFalse
  • condition: A boolean expression.
  • expressionIfTrue: The value returned if condition evaluates to true.
  • expressionIfFalse: The value returned if condition evaluates to false.

The entire expression evaluates to the value of either expressionIfTrue or expressionIfFalse, depending on the condition.


7.2 Basic Example

int age = 18; String status = (age >= 18) ? "Adult" : "Minor"; System.out.println(status); // Adult

This is equivalent to:

String status; if (age >= 18) { status = "Adult"; } else { status = "Minor"; }

7.3 Using the Result

The ternary operator returns a value, so it can be used in assignments, method arguments, return statements, and expressions.

In return statement:

public int getMax(int a, int b) { return (a > b) ? a : b; }

As a method argument:

System.out.println("Result: " + ((score > 60) ? "Pass" : "Fail"));

In assignments with different types (subject to type compatibility):

int x = 10; double y = (x > 5) ? 3.14 : 2.71; // OK, int to double promotion

7.4 Type Rules

The type of the entire ternary expression is determined by the types of expressionIfTrue and expressionIfFalse. Java follows binary numeric promotion rules for numeric types, and for non-numeric types, the result type is the common supertype.

  • If both expressions have the same type, that is the result type.
  • If one is a primitive and the other is a wrapper, unboxing occurs and the primitive type is used.
  • If the types are numeric, the result is the type that would result from numeric promotion.
  • If the types are references, the result type is the most specific common supertype (or Object if none).
int i = 5; double d = (i > 0) ? 10 : 3.14; // result is double (10 → 10.0) String s = (i > 0) ? "Positive" : "Non-positive"; // both String Number n = (i > 0) ? 42 : 3.14; // result is Number (common supertype)
Crucial

[!IMPORTANT] Mixing incompatible types can cause compile-time errors. String s = (true) ? "Hello" : 123; → Error: incompatible types


7.5 Nesting Ternary Operators

Ternary operators can be nested to create multi-way conditions. However, excessive nesting harms readability.

Nested example (three-way branch):

int marks = 85; String grade = (marks >= 90) ? "A" : (marks >= 80) ? "B" : (marks >= 70) ? "C" : "F"; System.out.println(grade); // B
Tip

[!TIP] Deeply nested ternary operators are often discouraged. Consider using if-else if or a switch for complex conditions.


7.6 Precedence and Associativity

  • The conditional operator has very low precedence—only assignment operators have lower precedence.
  • It is right associative, meaning nesting is evaluated from right to left.
int a = 5, b = 10, c = 15; int result = a > b ? a : b > c ? b : c; // groups as: a > b ? a : (b > c ? b : c) // Equivalent to: int max = (a > b) ? a : ((b > c) ? b : c);

Because of right associativity, the above returns the maximum of three numbers.

Use parentheses to make the grouping explicit:

int result = (a > b) ? a : ((b > c) ? b : c);

7.7 Common Use Cases

  • Conditional assignment of values.
  • Returning values from methods based on a simple condition.
  • String concatenation with a conditional result.
  • Initializing a variable with a value that depends on a condition.
// Safe division check double result = (denominator != 0) ? numerator / denominator : 0; // Conditional null handling String displayName = (name != null) ? name : "Anonymous"; // In logging System.out.println((debug) ? "Debug: " + detail : "Info: " + message);

7.8 Pitfalls and Best Practices

a) Avoid Side Effects

Since both expressionIfTrue and expressionIfFalse are evaluated conditionally, side effects should be used with caution.

b) Type Unification Issues

When mixing numeric and object types, unexpected boxing/unboxing may occur.

Integer a = null; int b = (true) ? a : 5; // NullPointerException because a is unboxed to int
Warning

[!WARNING] Always ensure that if one branch uses a wrapper, it is not null when the result type is primitive.

c) Overuse Reduces Readability

Simple ternary is fine; nested ternaries become difficult to read. Prefer explicit if-else or switch for complex logic.

d) Use with Parentheses in Complex Expressions

To avoid precedence surprises, wrap the entire ternary expression in parentheses when used in larger expressions.

System.out.println("Value: " + (x > 0 ? x : -x));

e) Cannot Be Used as a Standalone Statement

The ternary operator must be part of an expression; it cannot be used alone as a statement.

// (x > 0) ? x++ : x--; // Compilation error: not a statement // Use if in such cases.

7.9 Summary Table

AspectDescription
Operator? :
Number of operands3 (ternary)
Syntaxcondition ? exprIfTrue : exprIfFalse
Result typeCommon type of the two expressions after numeric/promotion/unboxing rules
PrecedenceVery low, only higher than assignment operators
AssociativityRight to left
Typical usageConditional assignment, simple if-else replacement
NestingPossible but reduces readability
Common pitfallUnboxing null causing NullPointerException; side effects; readability

7.10 Key Points to Remember

  • The conditional operator is a concise alternative to if-else when you need to choose between two values.
  • It returns a value, so it can be used in assignments, returns, and method arguments.
  • The types of the two branches must be compatible; Java determines a common result type.
  • Avoid nesting more than one level—use if-else for clarity.
  • Be cautious with side effects and null values to avoid runtime errors.

8. Bitwise Operators in Java

Bitwise operators perform operations on individual bits of integer types (byte, short, int, long, and char). They treat the operands as sequences of bits (binary numbers) and are commonly used in low-level programming, cryptography, graphics, device drivers, and performance-critical applications.

8.1 List of Bitwise Operators

Java provides seven bitwise operators:

OperatorNameTypeDescription
&Bitwise ANDBinarySets bit to 1 if both bits are 1
|Bitwise ORBinarySets bit to 1 if at least one bit is 1
^Bitwise XOR (exclusive OR)BinarySets bit to 1 if bits are different
~Bitwise NOT (complement)UnaryFlips all bits (1→0, 0→1)
<<Left shiftBinaryShifts bits left, fills with zero
>>Signed right shiftBinaryShifts bits right, preserves sign bit
>>>Unsigned right shiftBinaryShifts bits right, fills with zero
Note

[!NOTE] The &, |, and ^ operators also work as logical operators when applied to boolean operands. When used with integer types, they perform bitwise operations.


8.2 Bitwise Logical Operators (&, |, ^, ~)

These operators work on each corresponding pair of bits (or the single operand for ~).

a) Bitwise AND (&)

  • Result bit is 1 only if both operand bits are 1.

Truth table:

Bit ABit BA & B
000
010
100
111
int a = 12; // binary: 1100 int b = 10; // binary: 1010 int c = a & b; // 1000 (8) System.out.println(c); // 8

b) Bitwise OR (|)

  • Result bit is 1 if at least one operand bit is 1.

Truth table:

Bit ABit BA | B
000
011
101
111
int a = 12; // 1100 int b = 10; // 1010 int c = a | b; // 1110 (14) System.out.println(c); // 14

c) Bitwise XOR (^)

  • Result bit is 1 if the operand bits are different; 0 if they are the same.

Truth table:

Bit ABit BA ^ B
000
011
101
110
int a = 12; // 1100 int b = 10; // 1010 int c = a ^ b; // 0110 (6) System.out.println(c); // 6

d) Bitwise NOT (~)

  • Flips every bit (unary operator). Equivalent to -x - 1 because of two's complement representation.
int x = 12; // binary: 0000 1100 (32-bit representation) int y = ~x; // binary: 1111 0011 (which is -13 in decimal) System.out.println(y); // -13

8.3 Shift Operators (<<, >>, >>>)

Shift operators move bits to the left or right. They operate on the binary representation of integers.

a) Left Shift (<<)

  • Shifts bits to the left by the specified number of positions.
  • Vacant low-order bits are filled with 0.
  • Equivalent to multiplying by 2^n (but may overflow).
int a = 5; // 0000 0101 int b = a << 1; // 0000 1010 = 10 System.out.println(b); // 10

b) Signed Right Shift (>>)

  • Shifts bits to the right by the specified number of positions.
  • Vacant high-order bits are filled with the sign bit (0 for positive, 1 for negative). This preserves the sign.
  • Equivalent to dividing by 2^n (floor division for negative numbers).
int positive = 10; // 0000 1010 int neg = -10; // 1111 0110 (two's complement) System.out.println(positive >> 1); // 5 (0000 0101) System.out.println(neg >> 1); // -5 (1111 1011)

c) Unsigned Right Shift (>>>)

  • Shifts bits to the right by the specified number of positions.
  • Vacant high-order bits are filled with 0, regardless of sign.
  • Also called "logical right shift".
int positive = 10; // 0000 1010 int neg = -10; // 1111 0110 System.out.println(positive >>> 1); // 5 (0000 0101) System.out.println(neg >>> 1); // 2147483643 (0111 1111 ... 1011)

8.4 Shift Distance Masking

For int operands, only the lower 5 bits of the shift distance are used (i.e., distance & 31). For long operands, only the lower 6 bits are used (distance & 63). This means shifting by 32 bits on an int does nothing.

int x = 1; System.out.println(x << 32); // 1 (same as shifting by 0) System.out.println(x << 33); // 2 (same as shifting by 1)

8.5 Type Promotion in Bitwise Operations

  • When using bitwise operators on byte, short, or char, the operands are promoted to int before the operation.
  • The result is always int (or long if any operand is long).
byte b1 = 10; // 0000 1010 byte b2 = 6; // 0000 0110 // byte b3 = b1 & b2; // Compilation error: result is int byte b3 = (byte) (b1 & b2); // OK, cast back

For shift operators, the promoted type is used, and the shift distance is masked accordingly.


8.6 Precedence and Associativity

  • Bitwise operators have precedence between relational and logical operators.
  • From highest to lowest among bitwise:
    1. ~ (unary)
    2. <<, >>, >>>
    3. &
    4. ^
    5. |
int result = 5 & 3 | 2; // (5 & 3) | 2 → 1 | 2 = 3

Associativity is left to right for binary bitwise operators.


8.7 Common Use Cases

  • Flag management: Use individual bits as boolean flags.
final int READ = 1; // 001 final int WRITE = 2; // 010 final int EXECUTE = 4; // 100 int permissions = READ | WRITE; // 011 if ((permissions & READ) != 0) { /* has read permission */ }
  • Fast multiplication/division by powers of two (using shift).
int x = 10; int multiplied = x << 2; // x * 4 = 40 int divided = x >> 1; // x / 2 = 5
  • Data encoding/decoding (e.g., packing two 16-bit integers into one 32-bit integer).
int high = 0x1234; int low = 0x5678; int packed = (high << 16) | low; int highExtracted = packed >>> 16; int lowExtracted = packed & 0xFFFF;
  • Cryptography and hash functions often rely on bitwise operations.

  • Color manipulation (e.g., in graphics).

int rgba = (alpha << 24) | (red << 16) | (green << 8) | blue;

8.8 Pitfalls and Best Practices

a) Mixing with Logical Operators

Using & or | when you meant && or || can cause unexpected behavior because they are not short-circuiting.

// if (x != 0 & 10 / x > 1) { ... } // May throw ArithmeticException if x = 0

b) Shift Overflows

Shifting too far can lose data. Remember the distance masking.

c) Unsigned Right Shift on Negative int Produces Large Positive Number

This is often desired when working with raw bits but can be surprising.

d) Precedence Confusion

Always use parentheses to make bitwise expressions clear, especially when mixing with other operators.

// if (flags & MASK == 0) // Wrong: == has higher precedence than & if ((flags & MASK) == 0) // Correct

e) Portability

Bitwise operations on int and long are well-defined in Java (two's complement, fixed-width). This makes Java bitwise code portable across platforms.


8.9 Summary Table

OperatorNameExample (int a=12, b=10)Result (binary)Decimal
&Bitwise ANDa & b1100 & 1010 = 10008
|Bitwise ORa | b1100 | 1010 = 111014
^Bitwise XORa ^ b1100 ^ 1010 = 01106
~Bitwise NOT~a~...1100 = ...0011-13
<<Left shifta << 11100 << 1 = 1100024
>>Signed right shifta >> 11100 >> 1 = 01106
>>>Unsigned right shifta >>> 11100 >>> 1 = 01106

8.10 Key Points to Remember

  • Bitwise operators work only on integral types (byte, short, int, long, char).
  • They manipulate individual bits, making them efficient for low-level tasks.
  • &, |, ^ have both bitwise and logical versions; the context (integer vs boolean) determines behavior.
  • Shift operators <<, >>, >>> are useful for fast multiplication/division and bit packing.
  • The shift distance is masked (5 bits for int, 6 bits for long).
  • Use parentheses to avoid precedence mistakes.
  • Bitwise operations are fundamental in systems programming, graphics, networking, and performance-sensitive code.

9. The instanceof Operator in Java

The instanceof operator is a binary operator used to test whether an object is an instance of a specific class, subclass, or interface. It returns a boolean value and is commonly used for type checking before downcasting, ensuring type safety at runtime.

9.1 Syntax

objectReference instanceof Type
  • objectReference: A reference to an object (cannot be a primitive type).
  • Type: A class, abstract class, interface, or array type.
  • Result: true if the object is an instance of the specified type; otherwise false.

9.2 Basic Behavior

a) Simple Class Example

class Animal {} class Dog extends Animal {} Animal animal = new Dog(); if (animal instanceof Dog) { System.out.println("animal is a Dog"); } // Output: animal is a Dog

b) Null Handling

  • If the object reference is null, instanceof always returns false (no exception is thrown).
String str = null; System.out.println(str instanceof String); // false

c) Interface Checking

interface Flyable {} class Bird implements Flyable {} Bird bird = new Bird(); System.out.println(bird instanceof Flyable); // true

d) Array Types

  • Arrays are objects in Java. instanceof can test array types.
int[] numbers = new int[5]; System.out.println(numbers instanceof int[]); // true System.out.println(numbers instanceof Object); // true (arrays are Objects)

9.3 Compile-Time vs. Runtime Behavior

  • Compile-time: The compiler checks whether the reference type and the target type are in the same inheritance hierarchy. If it can determine that the test will always be false, a compilation error occurs.
String s = "hello"; // if (s instanceof Integer) { } // Compile error: incompatible types
  • Runtime: The actual class of the object is checked. Polymorphism is respected; an object of a subclass is an instance of its parent class.
Animal a = new Dog(); System.out.println(a instanceof Animal); // true (Dog is an Animal) System.out.println(a instanceof Dog); // true

9.4 Using instanceof for Safe Downcasting

A common use case is to check the type before performing a downcast to avoid ClassCastException.

public void handleAnimal(Animal animal) { if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.bark(); } else if (animal instanceof Cat) { Cat cat = (Cat) animal; cat.meow(); } }

9.5 Pattern Matching for instanceof (Java 16+)

Starting with Java 16, instanceof can be combined with a pattern variable, making the code more concise and eliminating explicit casting.

Syntax:

if (object instanceof Type variable) { // variable is automatically cast to Type }

Example:

if (animal instanceof Dog dog) { dog.bark(); // dog is already of type Dog }

The pattern variable is in scope only in the true block. This improves readability and reduces errors.


9.6 Using instanceof with Generics

Due to type erasure, the generic type information is not available at runtime. Therefore, you cannot use instanceof directly with a generic type parameter.

// Cannot do this: List<String> list = new ArrayList<>(); // if (list instanceof List<String>) { } // Compile error // However, you can use wildcards or raw types: if (list instanceof List<?>) { } // OK if (list instanceof List) { } // OK (raw type, but with warning)

For checking element types, you would need to examine the elements or use a different approach.


9.7 Precedence

  • instanceof has higher precedence than == and != (equality operators), but lower precedence than relational operators (<, >, <=, >=).
  • It is non-associative (cannot be chained directly).
  • To avoid confusion, always use parentheses when mixing instanceof with other operators.
if (obj instanceof String && ((String) obj).length() > 5) { ... }

9.8 Common Pitfalls and Best Practices

a) Using instanceof with null

Always remember: null instanceof Anything is false. No need for an explicit null check before instanceof, but be cautious if you use the object after the check.

b) Compiler-Detected Impossibility

If the compiler knows the reference type can never be an instance of the target type, it issues a compilation error. This helps catch logical errors early.

c) Overuse of instanceof

Excessive use of instanceof can indicate poor object-oriented design. Often, it is better to use polymorphism (method overriding) instead of explicit type checks. Reserve instanceof for situations where the type is not known until runtime or when working with external classes.

d) Pattern Matching and Scope

With pattern matching, the pattern variable is only in scope where it is definitely matched. Avoid using it outside the block.

e) Performance

instanceof is a relatively fast operation (a single type comparison), but frequent use in tight loops may have an impact.


9.9 Summary Table

AspectDescription
Operatorinstanceof
TypeBinary operator
OperandsLeft: object reference; Right: class, interface, or array type
Resultboolean (true if the object is an instance of the type)
Behavior with nullReturns false
Compile-timeChecks compatibility; error if types are unrelated
Pattern matchingSupported from Java 16: if (obj instanceof Type var) { ... }
GenericsCannot use concrete generic types due to erasure; use wildcards or raw types
PrecedenceBetween relational and equality operators
Common useSafe downcasting, type checks before casting

9.10 Key Points to Remember

  • instanceof tests whether an object is an instance of a specific type (class, subclass, or interface).
  • It returns false for null references.
  • The compiler ensures the test is sensible; unrelated types cause compilation errors.
  • Use instanceof before downcasting to avoid ClassCastException.
  • Pattern matching (Java 16+) simplifies the idiom by introducing a scoped pattern variable.
  • Avoid overusing instanceof; prefer polymorphic design when possible.
  • For generic types, use wildcards or raw types because type parameters are erased at runtime.

10. Expressions in Java

An expression is the fundamental building block of any Java program. It is a construct that evaluates to a single value and may produce side effects. Understanding expressions—their types, evaluation order, and composition—is essential for writing correct and efficient code.

10.1 What Is an Expression?

An expression is a combination of:

  • Literals (e.g., 42, "Hello", true)
  • Variables (e.g., count, name)
  • Operators (e.g., +, >, &&, ++)
  • Method invocations (e.g., Math.sqrt(x))
  • Parentheses to control grouping

When evaluated, an expression yields a result of some data type and may cause a side effect (e.g., changing a variable's value).

5 // literal expression x // variable expression x + y // arithmetic expression x > 0 && y < 10 // logical expression obj instanceof String // type check expression count++ // increment expression (with side effect) Math.pow(2, 3) // method invocation expression

10.2 Types of Expressions

Expressions can be categorized by the kind of result they produce or the operators involved.

a) Arithmetic Expressions

Produce a numeric result (int, long, float, double).

a + b * c (x + y) / z

b) Relational Expressions

Produce a boolean result by comparing two values.

age >= 18 price != 0

c) Logical Expressions

Combine boolean values using logical operators.

isValid && isActive !isError || retryCount < 3

d) Assignment Expressions

Use the assignment operator (=) or compound assignment (+=, etc.). They evaluate to the assigned value.

x = 10 // expression value is 10 count += 5 // expression value is the new count

e) Unary Expressions

Use unary operators (+, -, ++, --, !, ~).

-x !flag i++

f) Conditional (Ternary) Expression

The ? : operator yields one of two values based on a condition.

(score >= 60) ? "Pass" : "Fail"

g) instanceof Expression

Tests object type and returns boolean.

obj instanceof String

h) Method Invocation Expression

Calls a method and evaluates to the method's return value.

System.out.println("Hi") // expression of type void Math.max(a, b) // expression of type int

10.3 Evaluation Order

The order in which parts of an expression are evaluated is determined by three rules:

  1. Operator precedence – which operations are performed first.
  2. Operator associativity – for operators of the same precedence, the direction of evaluation (left-to-right or right-to-left).
  3. Operand evaluation order – in Java, operands are always evaluated left to right before the operator is applied.

a) Precedence (highest to lowest)

CategoryOperatorsAssociativity
Postfixexpr++, expr--Left-to-right
Unary++expr, --expr, +, -, !, ~Right-to-left
Multiplicative*, /, %Left-to-right
Additive+, -Left-to-right
Shift<<, >>, >>>Left-to-right
Relational<, >, <=, >=, instanceofLeft-to-right
Equality==, !=Left-to-right
Bitwise AND&Left-to-right
Bitwise XOR^Left-to-right
Bitwise OR|Left-to-right
Logical AND&&Left-to-right
Logical OR||Left-to-right
Conditional? :Right-to-left
Assignment=, +=, -=, *=, /=, %=, etc.Right-to-left

b) Associativity

  • Most binary operators are left associative (evaluate left operand, then right, then combine).
  • Assignment and unary operators are right associative.

c) Operand Evaluation Order

For any binary operator, Java guarantees that the left operand is fully evaluated before the right operand (except for short-circuiting operators, which may skip the right operand). This is important when operands have side effects.

int a = 2; int b = 3; int result = a++ + ++b; // left operand (a++) evaluated first → 2, a becomes 3 // right operand (++b) evaluated next → 4, b becomes 4 // result = 2 + 4 = 6

10.4 Expression Types

Every expression has a compile-time type, determined by the types of its subexpressions and the operators applied. The type dictates what operations can be performed on the expression's result.

  • Numeric expressionsbyte, short, int, long, float, double after binary numeric promotion.
  • Boolean expressions – result of relational, logical, and instanceof operators.
  • Reference expressions – result of new, method calls returning objects, etc.
  • Void expressions – method invocations that return void; cannot be used where a value is expected.

Type of the conditional operator is the common type of the second and third operands after promotion.


10.5 Constant Expressions

A constant expression is an expression that can be evaluated at compile time. Constant expressions are used in annotations, case labels in switch, and for initializing final variables.

A constant expression consists only of:

  • Literals
  • final variables of primitive type or String initialized with constant expressions
  • Arithmetic, relational, logical, bitwise, and shift operators applied to constants
  • The conditional operator with constant operands
  • Parentheses
final int MAX = 100; int x = MAX * 2; // 200 is a constant expression switch (value) { case 1 + 2: // 3 is a constant expression break; }

The compiler performs constant folding to pre-compute the result.


10.6 Expression Statements

Not all expressions can stand alone as statements. In Java, only certain expressions become valid statements when followed by a semicolon:

  • Assignment expressions
  • Increment/decrement expressions (++, --)
  • Method invocations
  • Object creation expressions (new)
x = 5; // assignment statement count++; // increment statement System.out.println("Hi"); // method invocation statement new ArrayList<>(); // object creation statement
Note

[!NOTE] Other expressions (e.g., a + b, x > y) are not valid statements because they produce a value that is not used.


10.7 Side Effects

Expressions can have side effects—they change the program's state. Common side-effect-causing operators include:

  • Assignment operators (=, +=, etc.)
  • Increment/decrement (++, --)
  • Method invocations that modify state
int i = 5; int j = i++ + ++i; // i changes twice during evaluation

Understanding side effects and evaluation order is crucial to avoid subtle bugs.


10.8 Common Expression Patterns

a) Chained Assignment

int a, b, c; a = b = c = 10; // c = 10; b = c; a = b;

b) Combined Comparison and Assignment

int max = (a > b) ? a : b;

c) Short-Circuiting in Conditions

if (name != null && name.length() > 0) { ... }

d) Bitwise Manipulation

int flags = (read ? READ_FLAG : 0) | (write ? WRITE_FLAG : 0);

10.9 Important Considerations

  • Floating-point precision: Arithmetic with float/double may produce rounding errors. Avoid direct equality comparisons.
  • Integer overflow: Java does not warn about overflow; it wraps around silently.
  • Null references: Using null in expressions that require a primitive (e.g., unboxing) throws NullPointerException.
  • Short-circuiting: && and || skip evaluation of the right operand when the left operand decides the result. Use this to guard against potential errors.
  • Type promotion: Binary numeric promotion can change the result type, sometimes leading to unexpected behavior.
  • Generics and erasure: The type of an expression involving generics may be erased at runtime, affecting instanceof and casts.

10.10 Examples of Complex Expressions

// Conditional with side effects int x = 5; int y = (x > 0) ? x++ : x--; // x becomes 6, y becomes 5 // Bitwise and logical mix boolean valid = (flags & MASK) != 0 && process(flags); // Method chaining String result = str != null ? str.trim() : "default"; // Shift and arithmetic int packed = (high << 16) | (low & 0xFFFF);

10.11 Summary

  • An expression evaluates to a value and may have side effects.
  • Java expressions are built from literals, variables, operators, and method calls.
  • The type of an expression is known at compile time and determines how the value can be used.
  • Evaluation order follows precedence, associativity, and left-to-right operand evaluation (with short-circuit exceptions).
  • Only certain expressions can be used as statements.
  • Constant expressions are computed at compile time.
  • Understanding expressions is fundamental to writing correct, efficient, and maintainable code.

Hi! Need help with studies? 👋
AI