Chapter 3: Operators and Expressions
Operators are the symbols and keywords that allow you to perform calculations, make comparisons, and manipulate data in Java. They are the building blocks of expressions, which in turn are the heart of almost every statement you write. Understanding how operators work, how they interact, and how expressions are evaluated is essential to mastering the flow of logic in any Java program.
In this chapter you will explore Java’s full set of operators: arithmetic and assignment, comparison and logical, bitwise and shift, and the rules that determine how they combine through precedence and associativity. You will also learn how Java automatically promotes types during mixed calculations and how to control conversions explicitly through casting.
Operators work on values and variables, while expressions combine them into units of meaning that the compiler can evaluate. For example, the simple line int total = 3 + 4; contains both assignment and arithmetic operators, forming an expression that computes a value and stores it in a variable. Java evaluates such expressions from left to right according to strict precedence rules, ensuring consistent and predictable behavior across all platforms.
As you progress through this chapter, you will see how operators shape both the arithmetic and logical structure of programs. By the end, you will not only know what each operator does, but also how to combine them safely, read them fluently, and recognize subtle effects such as type promotion and evaluation order that influence every Java expression you write.
= and == can change the logic of a program completely. Understanding the order and meaning of operators prevents these errors before they happen.
Arithmetic and assignment operators
Arithmetic and assignment operators are the core tools for performing mathematical operations and storing results in variables. They handle everything from simple addition and subtraction to compound updates that combine computation and assignment in one concise expression.
Basic arithmetic operators
Java supports the standard arithmetic operators found in most programming languages. These work on numeric types such as int, double, float, and long.
| Operator | Meaning | Example |
+ | Addition | 5 + 2 → 7 |
- | Subtraction | 5 - 2 → 3 |
* | Multiplication | 5 * 2 → 10 |
/ | Division | 5 / 2 → 2 (integer division) |
% | Remainder (modulus) | 5 % 2 → 1 |
Division between integers always produces an integer result. If either operand is a floating-point value, the result will be a floating-point number:
int a = 5 / 2; // 2
double b = 5 / 2.0; // 2.5
Unary operators can also change the sign or increment values directly:
| Operator | Description | Example |
+ | Unary plus (no effect) | +x |
- | Unary minus (negates value) | -x |
++ | Increment by 1 | x++ or ++x |
-- | Decrement by 1 | x-- or --x |
The prefix and postfix forms of ++ and -- differ in when the value changes. Prefix updates the variable before its value is used; postfix updates it after.
int x = 5;
System.out.println(++x); // 6 (prefix: increments, then prints)
System.out.println(x++); // 6 (postfix: prints, then increments)
System.out.println(x); // 7
Assignment operators
Assignment operators store values in variables. The simplest is the = operator, which copies the result of an expression into a variable. Compound assignment operators combine an operation with assignment, simplifying repeated updates.
| Operator | Equivalent to | Example |
= | — | x = 5 |
+= | x = x + value | x += 5 |
-= | x = x - value | x -= 5 |
*= | x = x * value | x *= 5 |
/= | x = x / value | x /= 5 |
%= | x = x % value | x %= 5 |
Compound assignments are especially useful for counters, accumulators, or iterative calculations.
int total = 10;
total += 5; // total = 15
total *= 2; // total = 30
double to an int using += will silently cast the result back to int, which may lose precision.
Order of evaluation
Arithmetic expressions are evaluated according to standard mathematical precedence. Multiplication, division, and modulus come before addition and subtraction. Parentheses can be used to override this order.
int result = 2 + 3 * 4; // 14
int fixed = (2 + 3) * 4; // 20
Assignments, by contrast, are evaluated from right to left, meaning the value on the right-hand side is always computed before it is stored in the variable on the left.
int x = 5;
int y = x = 10; // both x and y become 10
Comparison and logical operators
Comparison and logical operators allow Java programs to make decisions. They evaluate relationships between values and combine true or false results into meaningful conditions for control structures such as if, while, and for. Together, they form the foundation of conditional logic.
Comparison operators
Comparison (or relational) operators compare two values and produce a boolean result, either true or false. These operators can be used with numeric, character, and some object types such as String (when compared with specific methods like equals()).
| Operator | Meaning | Example |
== | Equal to | 5 == 5 → true |
!= | Not equal to | 5 != 3 → true |
> | Greater than | 5 > 3 → true |
< | Less than | 3 < 5 → true |
>= | Greater than or equal to | 5 >= 5 → true |
<= | Less than or equal to | 3 <= 4 → true |
These operators work reliably for primitive types. When comparing object references (for example, two String values), == checks whether both references point to the same object in memory, not whether their contents match. To compare object content, use the equals() method instead.
String a = "Java";
String b = new String("Java");
System.out.println(a == b); // false (different objects)
System.out.println(a.equals(b)); // true (same text)
equals() or equalsIgnoreCase() when comparing strings or other objects for value equality. The == operator only checks if both variables refer to the same memory location.
Logical operators
Logical operators combine or invert boolean values. They are used to form complex conditions in control flow statements. Each operator returns true or false depending on the logical relationship between operands.
| Operator | Name | Description | Example |
&& | Logical AND | True only if both operands are true | (x > 0 && y > 0) |
|| | Logical OR | True if either operand is true | (x > 0 || y > 0) |
! | Logical NOT | Inverts a boolean value | !ready |
Java uses short-circuit evaluation for && and ||. This means that evaluation stops as soon as the outcome is known. For example, in (x > 0 && y / x > 2), if x is not greater than zero, the second expression is never executed, preventing a potential division-by-zero error.
int x = 0;
int y = 5;
if (x != 0 && y / x > 1) {
System.out.println("Safe to divide");
}
// No error because the second condition is skipped
&& and || rather than their non-short-circuit counterparts (& and |) unless you specifically need both sides of the expression to evaluate for side effects.
Combining comparison and logic
Conditions often combine comparison and logical operators to create meaningful decisions. Parentheses can group expressions and control evaluation order, improving clarity.
int age = 25;
boolean hasLicense = true;
if (age >= 18 && hasLicense) {
System.out.println("You may drive.");
}
Logical operators can also be nested or chained to handle more complex decision trees:
boolean eligible = (age >= 18 && age <= 70) || hasLicense;
This expression evaluates to true if the person is between 18 and 70 years old, or already holds a valid license.
Ternary conditional operator
The ternary operator (?:) is a compact form of if that selects one of two values based on a condition. It is useful for short expressions where full control structures would add clutter.
int age = 20;
String access = (age >= 18) ? "granted" : "denied";
System.out.println("Access " + access);
The expression before the question mark is the condition. If it evaluates to true, the expression immediately after the ? is chosen; otherwise, the one after the : is used.
if statement for anything more complex.
Bitwise and shift operators
Bitwise and shift operators work at the level of individual bits within integer types such as byte, short, int, and long. They are used for low-level programming tasks, including data encoding, cryptography, graphics, and performance-sensitive computations. Although they may seem specialized, understanding them deepens your knowledge of how Java represents numbers internally.
Bitwise operators
Bitwise operators treat their operands as binary values and perform logical operations on each corresponding bit. The result is another integer whose bits reflect the chosen operation.
| Operator | Name | Description | Example |
& | Bitwise AND | 1 if both bits are 1 | 5 & 3 → 1 |
| | Bitwise OR | 1 if either bit is 1 | 5 | 3 → 7 |
^ | Bitwise XOR | 1 if bits differ | 5 ^ 3 → 6 |
~ | Bitwise NOT | Inverts all bits | ~5 → -6 |
To understand how these work, it helps to visualize binary representations:
int a = 5; // 0101
int b = 3; // 0011
System.out.println(a & b); // 0001 → 1
System.out.println(a | b); // 0111 → 7
System.out.println(a ^ b); // 0110 → 6
System.out.println(~a); // 1010 → -6 (two’s complement)
These operations are purely bitwise and do not perform logical short-circuiting as && and || do. Each bit position is compared independently.
Bit masking and flags
A common use of bitwise operators is creating and testing bit masks. A mask is an integer where specific bits represent on/off states. You can combine masks with |, test with &, and toggle with ^.
final int READ = 1; // 0001
final int WRITE = 2; // 0010
final int EXEC = 4; // 0100
int permissions = READ | WRITE; // 0011
boolean canWrite = (permissions & WRITE) != 0;
System.out.println(canWrite); // true
permissions ^= WRITE; // toggle WRITE
System.out.println((permissions & WRITE) != 0); // false
By combining bit masks, you can store multiple binary states efficiently within a single variable. This approach is common in systems and game programming, where performance and memory layout are critical.
Shift operators
Shift operators move the bits of a number left or right. Shifting left effectively multiplies by powers of two, while shifting right divides by powers of two. Java provides three shift operators:
| Operator | Name | Description | Example |
<< | Left shift | Shifts bits left, filling with zeros | 5 << 1 → 10 |
>> | Arithmetic right shift | Shifts bits right, preserving sign bit | -8 >> 2 → -2 |
>>> | Logical right shift | Shifts bits right, filling with zeros (no sign) | -8 >>> 2 → large positive value |
The left shift (<<) multiplies by two for each shift position, while the arithmetic right shift (>>) divides by two and keeps the sign for negative numbers. The logical right shift (>>>) fills empty bits with zeros, treating the number as unsigned.
int n = 5; // 00000101
System.out.println(n << 1); // 10 (00001010)
System.out.println(n >> 1); // 2 (00000010)
int m = -8; // 11111000
System.out.println(m >> 2); // -2 (11111110)
System.out.println(m >>> 2); // 1073741822 (sign bit cleared)
Practical uses of bitwise operations
Bitwise and shift operations appear throughout performance-oriented and embedded Java applications. Some common examples include:
- Flags and configuration values stored compactly in a single integer.
- Hash functions that mix and combine bits for uniform distribution.
- Graphics operations where pixel colors or channel values are packed into integers.
- Compression or encoding schemes that manipulate bits directly.
// Combine RGB color values into a single 32-bit integer
int r = 120, g = 200, b = 255;
int color = (r << 16) | (g << 8) | b;
System.out.printf("0x%06X%n", color); // 0x78C8FF
Operator precedence and associativity
When an expression contains multiple operators, Java follows a strict set of rules to determine the order in which operations are evaluated. These rules are known as operator precedence. Operators with higher precedence are evaluated before those with lower precedence. When operators have the same precedence, associativity determines the direction in which they are applied (left-to-right or right-to-left).
Understanding precedence and associativity ensures that expressions behave as intended. Without parentheses to clarify order, even a small assumption can lead to unexpected results.
int result = 2 + 3 * 4; // 14, not 20 (multiplication first)
int fixed = (2 + 3) * 4; // 20 (parentheses change the order)
Highest to lowest precedence
| Operator | Description |
() | Parentheses for grouping and method calls |
[], . | Array indexing and member access |
++, -- | Postfix increment and decrement |
++, --, +, -, ~, ! | Prefix increment/decrement, unary plus/minus, bitwise NOT, logical NOT |
(), new, cast | Object creation and type casting |
*, /, % | Multiplication, division, modulus |
+, - | Addition and subtraction, or string concatenation |
<<, >>, >>> | Bitwise shift operators |
<, <=, >, >=, instanceof | Relational comparisons and type checking |
==, != | Equality and inequality comparisons |
& | Bitwise AND |
^ | Bitwise XOR |
| | Bitwise OR |
&& | Logical AND |
|| | Logical OR |
? : | Ternary conditional operator |
=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>>= | Assignment and compound assignment (right-associative) |
, | Comma operator (evaluates expressions in sequence) |
Associativity rules
Most operators in Java are evaluated from left to right. However, a few (notably assignment and the ternary conditional operator) are right-associative. The following table summarizes the general pattern:
| Associativity | Operators |
| Left to right | * / % + - << >> >>> < <= > >= == != & ^ | && || |
| Right to left | ++ -- + - ~ ! ? : = += -= *= /= %= &= |= ^= <<= >>= >>>= |
Right-associative operators are evaluated from the innermost right-hand expression first. For example:
int a = 2;
int b = 3;
int c = 4;
int result = a = b = c; // assigns 4 to b, then to a
System.out.println(a); // 4
Parentheses and clarity
Even though Java’s precedence rules are consistent, experienced developers rely on parentheses to make code readable. This not only avoids mistakes but also signals intention clearly to others maintaining the code.
boolean test = (x > 5 && y < 10) || (z == 0);
Such grouping makes the logic obvious at a glance. The compiler would interpret the same expression correctly even without parentheses, but human readers might not.
Type promotion and casting
When expressions in Java involve values of different types, the language automatically converts them to a common type before performing the operation. This process is called type promotion. It ensures that the result is accurate and consistent, even when mixing integers and floating-point numbers. When automatic promotion is not what you want, you can use casting to explicitly convert one type to another.
Automatic type promotion
Java promotes smaller numeric types to larger ones to prevent data loss during operations. For example, if you add an int to a double, the int is automatically promoted to double before the addition occurs. The general promotion order (from smallest to largest) is:
byte → short → int → long → float → double
Character values are also promoted to integers when used in arithmetic expressions, since char is stored as a 16-bit Unicode number.
int result = 'A' + 1; // 'A' is 65 → result is 66
When multiple different types appear in a single expression, Java promotes them to the widest type involved:
int i = 5;
double d = 2.5;
double total = i + d; // int promoted to double
double to int) because that could lose precision or overflow.
Integer division and promotion
When dividing integers, Java performs integer division even if the result is not whole. To obtain a floating-point result, at least one operand must be a floating-point type.
int a = 5, b = 2;
System.out.println(a / b); // 2 (integer division)
System.out.println(a / 2.0); // 2.5 (automatic promotion to double)
This rule is important to remember when writing formulas, as the wrong type can silently change the outcome of your calculations.
float and double, since rounding differences can appear when comparing results later.
Explicit casting
Casting is the process of manually converting one type into another. It is used when you want to control how data is interpreted or when you must narrow a value to a smaller type. Casting is written by placing the target type in parentheses before the value or expression.
double value = 9.8;
int rounded = (int) value; // converts 9.8 → 9
Because narrowing conversions can lose data, Java requires an explicit cast to make sure you are aware of the risk.
long big = 12345678900L;
int small = (int) big; // value truncated to fit into int
System.out.println(small); // unpredictable numeric result
Casting between object types
In addition to primitive conversions, Java allows reference casting between related object types. This occurs within inheritance hierarchies, where a subclass object can be treated as its superclass (upcasting) or vice versa (downcasting).
Object obj = "Java"; // upcast: String → Object
String text = (String) obj; // downcast: Object → String
Upcasting is always safe because every subclass instance is also an instance of its superclass. Downcasting, however, must be done carefully. If the object is not actually an instance of the target type, a ClassCastException will occur at runtime.
Object num = Integer.valueOf(42);
String s = (String) num; // runtime error
instanceof operator to check the type safely. This prevents runtime exceptions and improves code reliability.
Type inference with var
Since Java 10, the var keyword allows the compiler to infer a variable’s type from its initializer. It does not change the rules of promotion or casting, but it makes declarations more concise while maintaining type safety.
var count = 10; // inferred as int
var name = "Java"; // inferred as String
var ratio = 5.0 / 2; // inferred as double
The inferred type is still static and fixed at compile time. You cannot later assign a different type to the same variable.
var can make code shorter, but it should be used only when the type is obvious from context. Clarity should always take priority over brevity.
Together, type promotion, casting, and inference form the backbone of Java’s strong and predictable type system. They let the compiler detect errors early while giving developers the control needed for precision when it matters.