skip to content

Explain the implicit narrowing cast performed by compound assignment, and give a case where it silently changes a value.

level: middleimportance: must knowfreq 58%

answer

  1. op= inserts (T) cast, T = LHS type
  2. binary ops promote byte/short/char to int
  3. narrowing needs explicit cast for plain =, free for op=
  4. byte b=127; b+=1 → -128
  5. int n=5; n/=2.0 → 2 (truncates)

basics

~20 s

Compound assignment automatically casts the result back to the variable's type, like b += 1 meaning b = (byte)(b + 1). That hidden cast can quietly chop or wrap a value — e.g. byte b = 127; b += 1; makes b -128 with no error.

solid answer

~50 s

Java's binary numeric operators promote `byte`, `short`, and `char` operands to at least `int`, so an expression like `b + 1` (byte b) is an `int`. The plain form `b = b + 1` therefore fails to compile — you can't store an `int` into a `byte` without an explicit cast. Compound assignment is defined by the JLS as `b = (byte)(b + 1)`: it inserts that narrowing cast for you. This is convenient but dangerous, because narrowing can lose the high bits. `byte b = 127; b += 1;` overflows to `-128`. `int i = 300; byte x = 0; x += i;` truncates to `44`. Floating-to-integer compounds truncate the fraction: `int n = 5; n /= 2.0;` becomes `2`, because the double result `2.5` is cast back to int. None of these emit a warning. In review, treat compound assignment on sub-int types or with floating RHS as a place to check for unintended truncation.

code

java · 11 lines
java
byte b = 127;
b += 1;            // (byte)128 -> -128, silent overflow
System.out.println(b);   // -128

int n = 5;
n /= 2.0;          // 2.5 cast to int -> 2
System.out.println(n);   // 2

int x = 0;
x += 4_000_000_000L; // (int) of a long, silently truncates
System.out.println(x);   // -294967296

go deeper

for a junior

Recognizes that compound assignment can change small-type values unexpectedly.

for a middle

States the (T) cast rule, explains numeric promotion to int, and gives an overflow/truncation example.

for a senior

Explains float→int truncation rules, char/byte wrapping, and review heuristics to catch silent narrowing.

for a principal

Connects to JLS conversion rules, designs accumulator/typing conventions and lint policy to prevent silent precision loss in numeric code.

## Background you need first ### Numeric promotion Java's arithmetic operators don't operate directly on `byte`, `short`, or `char`. Before a binary operation, Java applies **binary numeric promotion**: small integer types are promoted to `int` (and if either operand is `long`/`float`/`double`, both go to that wider type). So: - `byte + byte` → the operands become `int`, result is `int`. - `char + int` → result is `int`. - `int / double` → result is `double`. ### Widening vs narrowing - **Widening** = moving to a type that can hold all values of the source (e.g. `int`→`long`, `int`→`double`). Java does this automatically; no data lost. - **Narrowing** = moving to a *smaller* type that may not hold the value (e.g. `int`→`byte`, `double`→`int`). Java **requires an explicit cast** for plain assignment, because it can lose information. ## Why plain `b = b + 1` fails for a byte ``` byte b = 10; b = b + 1; // b + 1 is int; storing int in byte = narrowing = needs a cast → ERROR ``` The compiler refuses because you'd be silently narrowing. ## What compound assignment does (JLS §15.26.2) `E1 op= E2` is defined as: ``` E1 = (T)((E1) op (E2)) ``` where `T` is the **declared type of E1**. The `(T)` is an **implicit narrowing cast**. So: ``` byte b = 10; b += 1; // = b = (byte)(b + 1) → compiles, b == 11 ``` The convenience: you don't write the cast. The hazard: **the cast happens whether or not the value fits**, with **no compile error and no runtime exception** — just silent truncation/overflow. ## Cases where it silently changes the value **1. Integer overflow of the small type:** ``` byte b = 127; // max byte b += 1; // (byte)128 → -128 (wraps) ``` **2. Truncating a larger int:** ``` byte x = 0; int i = 300; x += i; // (byte)300 → 44 (300 mod 256, interpreted signed) ``` **3. Floating-point RHS truncated to integer LHS:** ``` int n = 5; n /= 2.0; // 5/2.0 = 2.5 (double) → (int)2.5 → 2 int m = 5; m *= 1.9; // 9.5 → (int)9.5 → 9 ``` **4. char arithmetic wrapping:** ``` char c = 'A'; // 65 c += 100000; // wraps within 16-bit char range, garbage char, no error ``` ## How `(T)` casts numbers - **Integer→smaller integer:** keep only the low-order bits (modular truncation), then interpret per the target's signedness. - **Floating→integer:** discard the fractional part (round toward zero); out-of-range values clamp to MIN/MAX, and NaN becomes 0. ## Defensive practice - Don't use compound assignment on `byte`/`short`/`char` if the right side can exceed the range. - Be wary of a floating-point RHS with an integer LHS — you'll truncate. - When you *want* to know about narrowing, write the expanded form so the compiler forces an explicit cast (and your intent is visible).

  • What does `int n = 5; n *= 1.5;` produce and why?
    7. `5 * 1.5` is the double 7.5; the compound assignment casts the result back to int, truncating toward zero to 7. The plain `n = n * 1.5` wouldn't compile (double→int needs a cast), which is exactly the safety the compound form removes.
  • How can you keep the convenience of compound assignment but be alerted to narrowing?
    You can't get a compile error from the compound form itself; to surface narrowing you write the explicit form `b = b + 1` (forcing you to add a cast), use a wider type for the accumulator, or add static-analysis rules. Some linters flag compound assignment that performs an implicit narrowing primitive conversion.

saying these in an interview costs you the question

  • Assuming the implicit cast is safe / lossless
  • Expecting a compile error or exception on overflow
  • Thinking `n /= 2.0` keeps the fractional part
  • Confusing widening (automatic, safe) with narrowing (lossy)

context