java-foundations Lesson 6 20 min read

Expressions and Operators

Precedence, type promotion, casting, and the concatenation trap

Reading: Reges & Stepp: Ch. 2 §2.1–2.2

After this lesson, you will be able to:

  • Evaluate arithmetic expressions using Java’s precedence rules
  • Predict the result of mixed-type expressions using type promotion
  • Use explicit casts to convert between int and double
  • Explain why 0.1 + 0.2 != 0.3 and use epsilon comparison
  • Apply compound assignment (+=) and increment (++) operators
  • Avoid the string concatenation trap with parentheses

Why Does 10 / 4 Give 2?

Try this in Python:

print(10 / 4)   # 2.5

Now try it in Java:

System.out.println(10 / 4);   // 2

Where did the .5 go? Java sees two int operands, performs integer division, and truncates the decimal. This one behavior causes more bugs in student code than almost anything else in the course. Today you learn exactly how Java evaluates every expression — what order, what types, and what traps to avoid.

From CSCD 110: In Python, / always gives a float and // gives integer division. In Java, / does integer division when both operands are int. There is no // operator. To get decimal division, at least one operand must be double.


Operator Precedence: PEMDAS with a Twist

Java evaluates arithmetic using the same rules you learned in math class:

Level Operators Associativity Example
1 (highest) () Inner to outer (3 + 4) * 2 → 14
2 *, /, % Left to right 10 / 2 * 3 → 15
3 (lowest) +, - Left to right 10 - 3 + 2 → 9

Rule 1: Higher-precedence operators go first. Rule 2: Same-level operators go left to right. Rule 3: Parentheses override everything.

Let’s trace 2 + 3 * 4 - 1:

  1. * has higher precedence: 3 * 412. Expression becomes 2 + 12 - 1.
  2. + and - are same level, left to right: 2 + 1214.
  3. 14 - 113.

Key Insight: When in doubt, add parentheses. a + ((b * c) / d) - e is longer than a + b * c / d - e but instantly readable. Professional code favors clarity over cleverness.

Check Your Understanding

What is the value of 10 % 3 + 2 * 4?


Mixed Types: Type Promotion

When int meets double in an expression, Java automatically promotes the int to double before the operation. The result is always double:

System.out.println(3 + 2.5);    // 5.5 (int promoted to double)
System.out.println(10 / 4);     // 2   (both int → integer division)
System.out.println(10 / 4.0);   // 2.5 (int promoted → real division)
System.out.println(10.0 / 4);   // 2.5 (int promoted → real division)

Promotion follows one direction: byteshortintlongfloatdouble. You never lose data going right.

The critical detail is that promotion happens per operation, not per expression:

// Step-by-step: 10 / 4 + 3.0
// Step 1: 10 / 4 → both int → integer division → 2
// Step 2: 2 + 3.0 → int promoted to 2.0 → 5.0
System.out.println(10 / 4 + 3.0);  // 5.0 (NOT 5.5!)

Common Pitfall: Integer division happens before promotion kicks in. In 10 / 4 + 3.0, the division 10 / 4 uses two int operands and produces 2. The truncation damage is already done by the time 3.0 enters the picture.


Explicit Casting

Sometimes you need to force a type conversion yourself. An explicit cast uses the target type in parentheses:

double gpa = 3.87;
int truncated = (int) gpa;      // 3 (decimal part chopped off)

int total = 255;
int count = 3;
double average = (double) total / count;  // 85.0 (correct!)

There are two directions:

Widening (intdouble) is safe and automatic — no data lost.

Narrowing (doubleint) requires an explicit cast because it truncates — the decimal part is dropped, not rounded:

(int) 3.99    // 3 — truncated, NOT rounded
(int) -2.7    // -2 — toward zero

If you want rounding, use Math.round():

long rounded = Math.round(3.99);          // 4L
int roundedInt = (int) Math.round(3.99);  // 4

The Trick: The cast operator has higher precedence than arithmetic. (double) x / y casts x first, then divides (giving a decimal result). (double) (x / y) divides first (integer division), then casts — still wrong. Cast before the operation.

Check Your Understanding

Given int x = 7, y = 2;, what is the value of (double) (x / y)?


The String Concatenation Trap

The + operator does double duty: addition with numbers, concatenation with strings. Since + evaluates left to right, the position of a String determines what is arithmetic and what is concatenation:

System.out.println("Sum: " + 2 + 3);   // "Sum: 23"
System.out.println(2 + 3 + " is sum"); // "5 is sum"
System.out.println("Sum: " + (2 + 3)); // "Sum: 5"

Trace the first line: "Sum: " + 2 → String + int → "Sum: 2". Then "Sum: 2" + 3 → String + int → "Sum: 23".

Trace the second line: 2 + 3 → int + int → 5. Then 5 + " is sum" → int + String → "5 is sum".

The Trick: To force arithmetic before concatenation, wrap the math in parentheses:

// BAD: concatenation instead of addition
System.out.println("Total: " + price + tax);     // "Total: 1020"

// GOOD: arithmetic first, then concatenation
System.out.println("Total: " + (price + tax));    // "Total: 30"
System.out.println("Result: " + 2 + 3);

Compound Assignment and Increment

The most common things you do with variables are add to them, subtract from them, and count. Java provides shorthand:

Shorthand Equivalent Meaning
x += 3 x = x + 3 Add 3 to x
x -= 5 x = x - 5 Subtract 5 from x
x *= 2 x = x * 2 Multiply x by 2
x /= 4 x = x / 4 Divide x by 4
x %= 3 x = x % 3 Set x to x mod 3
x++ x = x + 1 Increment by 1
x-- x = x - 1 Decrement by 1
int total = 0;
total += 10;    // total is now 10
total += 25;    // total is now 35
total -= 5;     // total is now 30

int count = 0;
count++;        // count is now 1
count++;        // count is now 2

There are two forms of increment: post (x++) returns the old value then increments, and pre (++x) increments first then returns the new value. As standalone statements, they are identical. The difference only matters inside larger expressions:

int x = 5;
int y = x++;   // y is 5, x is 6 (post: use old, then increment)

int a = 5;
int b = ++a;   // b is 6, a is 6 (pre: increment, then use new)

Best practice: use x++ and x-- as standalone statements. Avoid embedding them in complex expressions.

Common Pitfall: Never write x = x++; — it does not increment x. Post-increment returns the old value (5), then increments x to 6, then the assignment overwrites x back to 5. Just write x++;.


Floating-Point Precision

One more surprise:

System.out.println(0.1 + 0.2);          // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3);   // false!

This is not a Java bug. Computers store numbers in binary, and 0.1 cannot be represented exactly in binary — just as 1/3 cannot be represented exactly in decimal (0.333…). This affects every programming language.

Never compare double values with ==. Use epsilon comparison:

double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 1e-9;

if (Math.abs(a - b) < epsilon) {
    System.out.println("Approximately equal");  // prints!
}

Three types, three comparison strategies:

Type Correct Comparison
int a == b
double Math.abs(a - b) < epsilon
String a.equals(b)

The Math Class

Java provides mathematical functions through the Math class (no import needed):

Method Returns Example Result
Math.abs(x) same type Math.abs(-7) 7
Math.pow(b, e) double Math.pow(2, 10) 1024.0
Math.sqrt(x) double Math.sqrt(25) 5.0
Math.round(x) long Math.round(3.6) 4L
Math.max(a, b) same type Math.max(5, 9) 9
Math.min(a, b) same type Math.min(5, 9) 5

Since Math.pow() and Math.sqrt() return double, cast back to int when needed:

int squared = (int) Math.pow(5, 2);    // 25
int rounded = (int) Math.round(4.6);   // 5

The Trick: To round a double to n decimal places, multiply by 10^n, round, then divide by 10^n:

double price = 19.876;
double rounded = Math.round(price * 100.0) / 100.0;  // 19.88

Putting It Together: Pay Stub Calculator

Here is a complete program combining precedence, type promotion, casting, concatenation, and printf:

import java.util.Scanner;

public class PayStub {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("Enter your name: ");
        String name = scanner.nextLine();

        System.out.print("Enter hours worked: ");
        double hours = scanner.nextDouble();

        System.out.print("Enter hourly rate: ");
        double rate = scanner.nextDouble();

        double grossPay = hours * rate;
        double taxRate = 0.15;
        double tax = grossPay * taxRate;
        double netPay = grossPay - tax;

        System.out.println("=== Pay Stub for " + name + " ===");
        System.out.println("Hours: " + hours + " @ $" + rate + "/hr");
        System.out.printf("Gross Pay:  $%.2f%n", grossPay);
        System.out.printf("Tax (15%%):  $%.2f%n", tax);
        System.out.printf("Net Pay:    $%.2f%n", netPay);
    }
}

Sample output:

Enter your name: Alice
Enter hours worked: 40
Enter hourly rate: 18.50
=== Pay Stub for Alice ===
Hours: 40.0 @ $18.5/hr
Gross Pay:  $740.00
Tax (15%):  $111.00
Net Pay:    $629.00
Check Your Understanding

What does System.out.println("" + 10 + 20); print?


Formula Translation Practice

Translating math formulas into Java is where all these rules come together. Watch for integer division traps:

// Area of a triangle: A = (1/2) * b * h
double area = 0.5 * base * height;       // 0.5 avoids integer division

// Celsius to Fahrenheit: F = (9/5) * C + 32
double fahrenheit = 9.0 / 5.0 * celsius + 32;  // 9/5 would give 1!

// Average of three int scores
double average = (double) (s1 + s2 + s3) / 3;  // cast sum before dividing

// Distance formula: d = sqrt((x2-x1)^2 + (y2-y1)^2)
double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
Check Your Understanding

Which Java expression correctly computes the average of three int variables a, b, and c as a decimal value?


Summary

Every computation in Java is an expression governed by precedence rules. Multiplication, division, and modulus go before addition and subtraction. Same-level operators evaluate left to right. Parentheses override everything.

When int meets double, the int is promoted — but promotion happens per operation, not per expression. 10 / 4 + 3.0 gives 5.0, not 5.5, because integer division already truncated.

Explicit casts let you convert between types: (double) sum / count for correct averages, (int) Math.round(x) for rounding. Casting truncates; Math.round() rounds.

Never compare double values with ==. Use epsilon comparison: Math.abs(a - b) < epsilon.

Compound assignment (+=, -=, *=) and increment (++, --) are shorthand for the most common operations. These become essential when you learn loops.

Next lesson: We take a closer look at String — Java’s most-used reference type. You will learn charAt(), substring(), equals() vs ==, and why strings behave differently from int and double.