Type Casting in Java
The idea in one line
Casting tells the compiler to treat a value as a different type.
- For primitives, casting converts the value (possible data loss).
- For references (objects), casting does not change the objectβonly the view type (unsafe casts can throw
ClassCastException
).
1) Primitive casting
Widening (safe, implicit)
Small β big: byte β short β int β long β float β double
, and char β int
.
No precision loss for integer widening (but long β float
may lose precision due to floatβs format).
int i = 42;
long L = i; // OK: widening
double d = L; // OK: widening
char c = 'A';
int code = c; // 65
Narrowing (unsafe, explicit)
Big β small must be cast; may overflow or truncate.
double price = 10.99;
int iprice = (int) price; // 10 (fraction truncated)
int big = 130;
byte b = (byte) big; // -126 (overflow wraps around)
Numeric promotion in expressions
Smaller types promote to int
in arithmetic.
byte x = 10, y = 20;
// x + y is int, not byte
byte z = (byte) (x + y); // cast required
2) Reference casting (objects)
Upcasting (safe, implicit)
Subclass β superclass (or interface) is automatic.
Dog dog = new Dog();
Animal a = dog; // upcast: always safe
Runnable r = dog; // to an implemented interface: safe
Downcasting (explicit, may fail)
Superclass β subclass needs a cast and is only safe if the object really is that subclass.
Animal a = new Dog(); // actual object is Dog
Dog d = (Dog) a; // OK at runtime
Animal a2 = new Cat();
Dog d2 = (Dog) a2; // ClassCastException at runtime
Always check first:
if (a2 instanceof Dog d3) { // pattern matching (Java 16+)
d3.bark();
}
Tip: prefer polymorphism (virtual methods) to avoid downcasts.
3) Generics, arrays, and wrappers
-
Generics are invariant:
List<String>
is not aList<Object>
.List<String> ls = new ArrayList<>(); // List<Object> lo = ls; // compile-time error List<? extends Object> lo = ls; // OK (read-only intent)
Avoid unchecked casts; use wildcards.
-
Arrays are covariant:
String[]
is a subtype ofObject[]
, but you might get ArrayStoreException at runtime if you store the wrong type. -
Wrappers & Number: you canβt cast unrelated wrappers:
Integer I = 5; // Double D = (Double) (Object) I; // runtime ClassCastException Number n = I; double dv = n.doubleValue(); // use Numberβs methods
4) Overload selection & casts
Casts can change which overloaded method is chosen (most specific applicable method wins).
void f(long x) { }
void f(Integer x) { }
f(5); // chooses f(long) (primitive int β long)
f((Integer)5); // chooses f(Integer)
5) Runnable βall-in-oneβ demo (Java 17)
Save as Main.java
:
import java.util.*;
public class Main {
public static void main(String[] args) {
// ---- Primitive casting ----
double price = 10.99;
int iprice = (int) price; // 10
System.out.println("Primitive narrowing: (int)10.99 -> " + iprice);
int big = 130;
byte b = (byte) big; // -126 due to overflow
System.out.println("Overflow on narrowing: (byte)130 -> " + b);
char c = 'A';
int code = c; // 65
System.out.println("Widening char->int: 'A' -> " + code);
byte x = 10, y = 20;
byte z = (byte) (x + y); // expression promotes to int
System.out.println("Promotion in expr: (byte)(10+20) -> " + z);
// ---- Reference casting ----
Animal a = new Dog(); // upcast (implicit)
a.speak(); // "woof"
if (a instanceof Dog d) { // safe downcast via pattern match
d.bark();
}
Animal a2 = new Cat();
try {
Dog wrong = (Dog) a2; // will throw ClassCastException
wrong.bark();
} catch (ClassCastException ex) {
System.out.println("Caught bad downcast: " + ex);
}
// ---- Interfaces ----
Runnable r = new Dog(); // upcast to interface
r.run();
// ---- Arrays covariance pitfall ----
try {
Object[] arr = new String[2]; // covariant assignment
arr[0] = 123; // ArrayStoreException at runtime
} catch (ArrayStoreException ex) {
System.out.println("ArrayStoreException: " + ex.getMessage());
}
// ---- Generics invariance ----
List<String> ls = List.of("a", "b");
List<? extends Object> lo = ls; // read-only view with wildcard
System.out.println("Generics invariance: size=" + lo.size());
// ---- Wrappers & Number ----
Number n = Integer.valueOf(42);
double dv = n.doubleValue(); // use Number API, not casts
System.out.println("Number.doubleValue(): " + dv);
// ---- Overload selection ----
OverloadDemo od = new OverloadDemo();
od.f(5); // chooses f(long)
od.f((Integer) 5); // chooses f(Integer)
}
}
abstract class Animal { void speak() { System.out.println("..."); } }
class Dog extends Animal implements Runnable {
@Override void speak() { System.out.println("Dog: woof"); }
void bark() { System.out.println("Dog barks"); }
@Override public void run() { System.out.println("Dog runs"); }
}
class Cat extends Animal { @Override void speak() { System.out.println("Cat: meow"); } }
class OverloadDemo {
void f(long x) { System.out.println("f(long): " + x); }
void f(Integer x) { System.out.println("f(Integer): " + x); }
}
Compile & run:
javac --release 17 Main.java
java Main
Quick best practices
- Prefer polymorphism over downcasting.
- For primitives, avoid narrowing unless necessary; watch for truncation/overflow.
- With generics, use wildcards instead of unchecked casts.
- Use
instanceof
(pattern matching in Java 16+) before downcasting. - For numeric wrappers, use
Number
methods (intValue()
,doubleValue()
, β¦), not cross-wrapper casts.