Type Casting & Wrapper Classes
Widening (implicit) casting automatically converts a smaller type into a bigger one — like int to double, with no loss.
Narrowing (explicit) casting requires manually converting a bigger type into a smaller one — like double to int — and some data (the decimal part) can be lost.
Wrapper classes (Integer, Double, Character, Boolean) let you use primitive types as objects. Autoboxing automatically turns a primitive into its wrapper; unboxing does the reverse.
double d = 100.99;
int i = (int) d; // narrowing, i = 100
int x = 5;
Integer boxed = x; // autoboxing
int y = boxed; // unboxing- Widening: automatic, small->big
- Narrowing: manual, big->small, possible data loss
- Autoboxing/Unboxing: primitive <-> wrapper
Widening is automatic and follows this order: byte → short → int → long → float → double (char can also widen directly into int). Data stays safe at every step, no loss occurs, so Java does it automatically without asking for an explicit cast — the compiler knows a smaller value will easily fit into a bigger space.
Interesting edge case: widening from int to float is technically considered "safe" (no compile error), but can lose a lot of precision for large numbers — because a float can only store ~7 significant decimal digits, while an int can go up to 10 digits. This is one place where the general rule "widening = no data loss" breaks down a bit.
byte b = 10;
long l = b; // automatic
double d = l; // automatic
int bigInt = 123456789;
float f = bigInt; // widening, but PRECISION loss ho sakta hai!Narrowing requires a manual cast because data can be lost — Java makes you write (targetType) explicitly, so you're taking this risk "on purpose", not by accident.
Casting double to int completely "truncates" (chops off) the decimal part, it doesn't round — 9.99 becomes 9, not 10. If you need rounding, use Math.round() before the cast.
If a value is outside the target type's range, "overflow" happens — the number wraps around and gives a strange (often negative) value. This happens silently, no exception is thrown — which is why it's a very dangerous bug source if you're not careful.
double d = 9.99;
int i = (int) d; // 9 (decimal cut gaya, round nahi hua)
int rounded = (int) Math.round(d); // 10 (agar rounding chahiye)
int big = 130;
byte b = (byte) big; // -126 (overflow! byte ka max 127 hai)Every primitive has a wrapper class: int→Integer, double→Double, char→Character, boolean→Boolean, long→Long, etc. These let you use them as objects (like in Collections, which only store objects, not primitives) — List<int> isn't possible, but List<Integer> is.
Autoboxing automatically converts a primitive into its wrapper (int → Integer), Unboxing does the reverse (Integer → int) — this has been automatic since Java 5; before that you had to manually call Integer.valueOf()/intValue().
Integer.parseInt("123") turns a String into an int primitive. Integer.valueOf("123") turns a String into an Integer object. Both are common, the only difference is the return type.
Interesting trivia: Java caches Integer objects from -128 to 127 (to save memory, since small numbers are very common). So for Integer a = 100; Integer b = 100;, a == b gives true (same cached object), but for 200 it gives false (two separate objects get created)!
Integer a = 100, b = 100;
System.out.println(a == b); // true (cached)
Integer x = 200, y = 200;
System.out.println(x == y); // false! (not cached)
System.out.println(x.equals(y)); // true (सही tarika)