Topic 1.6
Type Conversion and Casting
In one line
Java converts a value to a bigger type automatically (widening) because nothing can be lost, but makes you write a cast like (int) to convert to a smaller type (narrowing), because digits or bits may be thrown away. Knowing exactly what a cast keeps and drops, and when Java promotes types inside expressions, explains most 'lossy conversion' errors and many silent bugs.
Think of it like this
Pouring water. From a small glass into a big jug, it always fits, so nobody needs to ask. From a full jug into a small glass, some spills, so you must say 'yes, I know, pour anyway'. Widening in Java is the glass into the jug: automatic. Narrowing is the jug into the glass: you must write a cast to say you accept the spill.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Conversion
- Changing a value from one type to another, such as from
inttodouble. - Widening
- Converting to a type that can hold every value of the original, like
inttolong. Java does it automatically. - Narrowing
- Converting to a smaller type that might not hold the value, like
doubletoint. Java requires a cast. - Cast
- The type name in brackets before a value, like
(int) 9.99. It tells the compiler you accept any loss. - Truncate
- Cut off the decimal part without rounding. 9.99 truncates to 9.
- Numeric promotion
- Java's rule that converts small types to
int(or to the bigger operand's type) before arithmetic. - Lossy
- A conversion that might lose information: digits, bits or precision.
- Parse
- Read text and turn it into a value, like turning the String
"42"into theint42.
Step by step
01The widening ladder
Each step up the ladder can hold every value of the step below, so Java climbs it for you without being asked. long total = 5; widens the int 5. double avg = total; widens again.
char joins at int: it's unsigned, so it can't widen to short (which is signed and can't hold 40,000) but fits in int. byte and short can't widen to char either, because they can be negative.
02Narrowing needs a cast, and here is what it drops
Decimal → integer: the fraction is dropped, rounding toward zero. (int) 7.9 is 7, (int) -7.9 is -7. If the value is beyond the int range it is clamped: (int) 1e20 is 2,147,483,647. (int) Double.NaN is 0.
Integer → smaller integer: only the low bits are kept. (byte) 200: 200 is 11001000, and as a byte the top bit is the sign, so it reads as -56. (byte) 256 is 0, because 256's low 8 bits are all zero.
Decimal → byte/short/char happens in two steps: first to int (truncate and clamp), then to the small type (keep low bits). So (byte) 300.7 is (byte) 300, which is 44.
03Promotion inside expressions
Java's bytecode only has arithmetic for int, long, float and double. So whenever you compute with byte, short or char, they're first widened to int, and the result is an int.
Mixed types climb to the bigger one: int + long is long, long + float is float, int + double is double. This is why 5 / 2 is 2 (both int) while 5 / 2.0 is 2.5 (the int is promoted to double).
The type depends on the operands, not on where you store the result. double avg = sum / count; with two ints does an integer division first and only then widens the truncated answer.
04Casting at the right moment
A cast applies to the value immediately after it. (double) total / count casts total first, so the division is done in double. (double) (total / count) divides in int first (truncating) and casts the already-truncated result.
The same goes for big products: (long) a * b widens a before multiplying, while (long) (a * b) multiplies in int (possibly overflowing, Topic 1.3) and widens too late.
05Compound assignment hides a cast
The JLS defines x op= y as x = (T) (x op y) where T is the type of x. That built-in cast is why byte b = 10; b += 5; compiles, and why char c = 'a'; c += 1; gives 'b'.
It's convenient but can hide loss: int n = 10; n += 3.7; computes 13.7 in double, then casts to int: 13, with no warning.
06Converting between text and numbers
A cast can't turn text into a number: (int) "42" doesn't compile. Use the parse methods: Integer.parseInt("42"), Long.parseLong, Double.parseDouble, Boolean.parseBoolean. They throw NumberFormatException if the text isn't a valid number.
Integer.parseInt("ff", 16) reads other bases. In the other direction, String.valueOf(42), Integer.toString(42) or "" + 42 all give "42", and Integer.toString(255, 2) gives binary.
07Rounding is not casting
Math.round(x) rounds to the nearest whole number, with halves going up (toward positive infinity): Math.round(2.5) is 3 and Math.round(-2.5) is -2. It returns a long for a double argument, so int r = Math.round(2.5); needs a cast.
Math.floor rounds down and Math.ceil rounds up, both returning double. For money rounding with a chosen rule, use BigDecimal.setScale (Topic 1.4).
Try it yourself
- 1
Predict the bytes
Before running, predict
(byte) 127,(byte) 128,(byte) 255and(byte) -129. Then add them to the second example. (Answers: 127, -128, -1, 127.) - 2
Move the cast
In the third example, change
(double) total / counttototal / (double) countand then tototal / count * 1.0. The first gives 3.5; the second gives 3.0, becausetotal / countis evaluated first inint. - 3
Parse something that isn't a number
In the last example, change
"42"to" 42"(with a space) and run.parseIntdoesn't trim spaces, so you get aNumberFormatException. Fix it with" 42".strip()(Java 11) ortrim().
Code & diagrams
Expected output
int -> long -> double: 100.0
(int) 9.99 = 9
(int) -9.99 = -9
Math.round(9.5) = 10
Math.round(-9.5) = -9
Math.floor(-9.99) = -10.0
Math.ceil(9.01) = 10.0Expected output
(byte) 200 = -56
(byte) 256 = 0
(short) 70000 = 4464
(int) 3000000000L = -1294967296
(char) 66 = B
(int) 1e20 = 2147483647
(int) -1e20 = -2147483648
(int) NaN = 0
(byte) 300.7 = 44Expected output
sum=30 c=30 a=15
7 / 2 = 3
(double) 7 / 2 = 3.5
(double) (7 / 2) = 3.0
10 += 3.7 -> 13
int to float: 16777217 -> 16777216Expected output
43
7.5
9000000
43!
255
11111111Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Add to a byte without a cast
Write byte level = 10; level = level + 1;.
Break #2
Store a double in an int
Write double price = 3.7; int rupees = price;.
Break #3
Parse text that isn't a number
Call Integer.parseInt("12a").
Myth vs fact
Myth
Casting a double to int rounds it.
Fact
It truncates toward zero: (int) 2.99 is 2 and (int) -2.99 is -2. Use Math.round to round.
Myth
Widening never loses anything.
Fact
Integer to integer widening is exact, but int → float, long → float and long → double can round away the last digits.
Myth
b += 1 and b = b + 1 are identical.
Fact
Compound assignment includes a cast to the variable's type, so b += 1 compiles for a byte while b = b + 1 doesn't.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Primitive conversions compile to single bytecodes:
i2l,i2d,l2i,d2i,i2b,i2c,i2sand so on.d2iimplements the JLS rules exactly: NaN → 0, out of range →MIN_VALUE/MAX_VALUE, otherwise truncate toward zero. There's nod2b;(byte) someDoubleisd2itheni2b. - ▸
Assignment of a constant expression of type
inttobyte,shortorcharis allowed without a cast if the value fits (JLS §5.2). That rule doesn't apply to method arguments:void f(byte b)called asf(5)fails to compile. - ▸
Math.round(double)is defined asfloor(x + 0.5)semantics without the double-rounding bug older JDKs had: since Java 7,Math.round(0.49999999999999994)correctly returns 0. - ▸
Boxing conversions are separate from primitive ones: an
intcan box toIntegerbut not toLong, soLong x = 5;fails whilelong x = 5;works. Topic 8.1 and Phase 9 return to boxing.
Remember this
- 1
Widening conversions go up the chain
byte → short → int → long → float → double(andchar → int). Java does them automatically wherever a bigger type is expected: in assignments, method arguments and arithmetic. - 2
Narrowing goes the other way and needs an explicit cast:
int n = (int) 9.99;. Decimal to integer truncates toward zero (9.99 becomes 9, -9.99 becomes -9). Integer to a smaller integer keeps only the low bits, so(byte) 200is -56. Adoubletoo big for anintsaturates atInteger.MAX_VALUE, andNaNbecomes 0. - 3
Numeric promotion: before
+,-,*,/,%and comparisons, Java converts both operands to a common type:doubleif either isdouble, elsefloatif either isfloat, elselongif either islong, otherwiseint. Sobyte + byteis anint, which is whybyte b = b1 + b2;needs a cast. - 4
Compound assignment (
+=,-=,*=…) includes a hidden cast back to the variable's type.b += 1compiles whereb = b + 1doesn't, andint n = 10; n += 3.7;silently makesn13. - 5
Some widenings still lose precision silently:
int → float,long → floatandlong → doublekeep the magnitude but can round the last digits.float f = 16_777_217;stores 16,777,216. - 6
Casting doesn't round. Use
Math.round(returnslongfor adouble),Math.floororMath.ceilwhen you want rounding. Text is a separate world: convert withInteger.parseInt("42"),Double.parseDouble, and back withString.valueOf(n).booleancan't be cast to or from anything.
Explain it without notes
What is the difference between widening and narrowing, and why does only one need a cast?
Why does byte c = a + b; fail when a and b are both byte?
Explain the result of (byte) 200 step by step.
Why does double avg = sum / count; give a whole number when sum and count are ints, and how do you fix it?
Practice
Compute the average of the ints 7, 8 and 10 as a double and print it rounded to the nearest whole number as an int.
Print what (byte), (short) and (char) casts do to the int 65601.
Convert the String "1250" to a number, add 10% using integer arithmetic only, and print the result as a String with " rupees" appended.
Trade-offs
- ↔
Explicit casts make every possible loss visible, at the cost of noisier code; Java chose safety over brevity, unlike C, which narrows silently.
- ↔
Compound assignment is concise and handles small types neatly, but its hidden cast can mask real precision loss (
int += double). - ↔
Using
inteverywhere avoids most casts but costs memory in huge arrays;byte/shortsave memory but need casts on every arithmetic store.
Done when you can
I can draw the widening ladder and say which conversions need a cast.
I can predict what a narrowing cast keeps for decimals and for integers.
I know the numeric promotion rules and why
byte + byteis anint.I place casts so division and multiplication happen in the right type.
I convert between Strings and numbers with
parseInt/valueOfand know aboutNumberFormatException.I use
Math.round,floororceilwhen I mean rounding, not a cast.