Skip to content
Part IA Michaelmas Term

Type Conversion

Implicit Conversion: Widening

A widening conversion moves from a smaller (narrower) type to a larger (wider) type. No information is lost, so the compiler performs these conversions automatically — no explicit cast is needed.

The widening chain for numeric types:

byte → short → int → long → float → double

Additionally: char widens to int, and any integer type widens to float or double.

int i = 42;
long l = i;       // OK — int fits in long
double d = i;     // OK — int fits in double precisely (up to 2^53)
float f = 3.14f;
double d2 = f;    // OK — float fits in double

The compiler silently inserts the widening conversion. The assignment long l = i compiles as though you wrote long l = (long) i.

Watch for Float Widening

long to float is a widening conversion — float has a larger range than long — but float has only ~7 significant digits of precision. Widening a large long to float preserves the magnitude but may lose low-order bits:

long big = 123456789123456789L;
float f = big;   // compiles, but precision loss — f != big

Explicit Conversion: Narrowing

A narrowing conversion moves from a larger type to a smaller type. Information may be lost, so the compiler requires an explicit cast — the programmer must acknowledge the risk.

double d = 3.14159;
int i = (int) d;       // truncates: i == 3

long l = 300L;
byte b = (byte) l;     // overflow: 300 mod 256 = 44

float f = 5.7f;
int j = (int) f;       // truncates: j == 5 (not rounded)

Narrowing an integer truncates the high-order bits. Narrowing a floating-point value truncates the fractional part — it does not round to nearest (use Math.round() for that).

Truncation vs Rounding

double positive = 3.7;
double negative = -3.7;

int a = (int) positive;   // 3 (truncation toward zero)
int b = (int) negative;   // -3 (truncation toward zero)
long c = Math.round(positive); // 4 (rounds to nearest)
long d = Math.round(negative); // -4

Integer Division Trap

When both operands are integers, Java performs integer division — the result is an integer, and any fractional part is discarded. This is a classic source of bugs:

double result = 5 / 2;     // result is 2.0, not 2.5

Why? 5 and 2 are both int literals. Integer division yields 2. Only then is that 2 widened to 2.0 for assignment to double.

To get floating-point division, at least one operand must be a floating-point type:

double r1 = 5.0 / 2;       // 2.5
double r2 = 5 / 2.0;       // 2.5
double r3 = (double) 5 / 2; // 2.5

String Concatenation with +

The + operator is overloaded for strings. When one operand is a String, the other operand is converted to a String via toString(), and the two are concatenated:

String s = "Value: " + 42;          // "Value: 42"
String t = "Result: " + 3.14;        // "Result: 3.14"
String u = "Object: " + new Point(); // "Object: Point@1a2b3c" (or custom toString)

Concatenation is left-associative, which can produce surprising results:

String s = "Sum: " + 5 + 3;   // "Sum: 53" — ("Sum: 5") then + 3
String t = "Sum: " + (5 + 3); // "Sum: 8" — parentheses force addition first

In the first line, "Sum: " + 5 produces "Sum: 5" (a string), then + 3 concatenates again. In the second line, (5 + 3) evaluates to 8 first, then the string concatenation runs.

Wrapper Class Conversions

Each primitive type has a corresponding wrapper class that provides utility methods for conversion between strings and primitives.

String to Primitive

int i = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
boolean b = Boolean.parseBoolean("true");
long l = Long.parseLong("123456789");

parseInt and friends throw a NumberFormatException if the string is not a valid representation.

Primitive to String

String s1 = Integer.toString(42);        // "42"
String s2 = Double.toString(3.14159);    // "3.14159"
String s3 = String.valueOf(42);          // "42" — works for any type
String s4 = "" + 42;                     // "42" — lazy concatenation trick

Autoboxing and Unboxing

Since Java 5, primitives are automatically boxed into their wrapper types and unboxed back:

Integer boxed = 42;          // autoboxing — Integer.valueOf(42)
int unboxed = boxed;         // auto-unboxing — boxed.intValue()

List<Integer> list = new ArrayList<>();
list.add(5);                 // autoboxing int to Integer
int value = list.get(0);     // auto-unboxing Integer to int

Autoboxing creates hidden overhead — each boxing allocates an Integer object on the heap. In performance-sensitive loops, prefer primitives.

Conversion Hierarchy Summary

          Widening (implicit, safe)
          ←←←←←←←←←←←←←←←←←←←
byte  →  short  →  int  →  long  →  float  →  double

                               char

          Narrowing (explicit cast, may lose data)
          →→→→→→→→→→→→→→→→→→→