☕ Java
Type Casting
Type casting is converting a value from one type to another. Java supports two kinds: widening (automatic, safe) and narrowing (explicit, potentially lossy). Getting casting wrong causes silent data corruption, ClassCastExceptions, and hard-to-trace bugs. Here's how casting actually works.
What Is Type Casting?
Type casting is the process of converting a value of one data type into another. Java is a strongly typed language — you can't just use a double where an int is expected, or treat a Dog as a Cat. Casting is the mechanism that bridges compatible types when conversion is needed.
Java has two categories of casting:
Widening casting (implicit) — Converting a smaller type to a larger type. Safe because no data is lost. Java does this automatically with no syntax required.
Narrowing casting (explicit) — Converting a larger type to a smaller type. Potentially lossy because the larger value may not fit. You must write the cast explicitly — Java's way of making you acknowledge the risk.
Widening Casting — Automatic and Safe
Widening casting happens automatically when you assign a smaller type to a larger compatible type. No syntax needed — Java inserts the conversion silently because it's guaranteed to be safe.
Java
// The widening chain — each type can widen to any type to its right:
// byte → short → int → long → float → double
byte b = 42;
short s = b; // byte → short (automatic)
int i = s; // short → int (automatic)
long l = i; // int → long (automatic)
float f = l; // long → float (automatic)
double d = f; // float → double (automatic)
// All of these compile with no cast syntax:
int intVal = 100;
long longVal = intVal; // int → long
double doubleVal = intVal; // int → double
float floatVal = intVal; // int → float
// char widens to int and up:
char c = 'A';
int charAsInt = c; // 65 — Unicode code point
long charAsLong = c; // 65L
double charAsDouble = c; // 65.0
// Method parameters — widening happens automatically:
void printDouble(double d) { System.out.println(d); }
printDouble(42); // int 42 widens to double 42.0 automatically
printDouble(100L); // long widens to double automaticallyNarrowing Casting — Explicit and Lossy
Narrowing casting converts a larger type to a smaller type. You must write the target type in parentheses before the value. If the value doesn't fit in the target type, data is silently truncated or corrupted — no exception, no warning.
Java
// Syntax: (targetType) value
double d = 9.99;
int i = (int) d; // 9 — decimal part is truncated, NOT rounded
System.out.println(i); // 9
long l = 123456789012L;
int fromLong = (int) l; // data may be lost if value exceeds int range
System.out.println(fromLong); // 1912276372 — corrupted value
// The full narrowing chain (each requires explicit cast):
// double → float → long → int → short → byte → char
double pi = 3.14159;
float piFloat = (float) pi; // 3.14159 → 3.1415901 (precision loss)
long piLong = (long) pi; // 3.14159 → 3 (decimal truncated)
int piInt = (int) pi; // 3.14159 → 3
short piShort = (short) pi; // 3
byte piByte = (byte) pi; // 3
// Overflow — value wraps around when it doesn't fit:
int big = 300;
byte small = (byte) big; // 300 doesn't fit in byte (-128 to 127)
System.out.println(small); // 44 — wrapped around silently
int negative = -1;
byte negByte = (byte) negative; // -1
char negChar = (char) negative; // 65535 — char is unsigned, -1 wraps
// int to char and back:
int code = 65;
char letter = (char) code; // 'A'
int back = (int) letter; // 65
// Missing cast = compile error:
double x = 3.14;
int y = x; // COMPILE ERROR — possible lossy conversion
int z = (int) x; // correct — explicit acknowledgment of lossCasting with Objects — Reference Type Casting
Casting also applies to object references — casting up and down an inheritance hierarchy. Upcasting (child to parent) is automatic. Downcasting (parent to child) requires an explicit cast and can fail at runtime with a ClassCastException if the object isn't actually an instance of the target type.
Java
class Animal {
void speak() { System.out.println("..."); }
}
class Dog extends Animal {
void speak() { System.out.println("Woof!"); }
void fetch() { System.out.println("Fetching!"); }
}
class Cat extends Animal {
void speak() { System.out.println("Meow!"); }
}
// Upcasting — child to parent (automatic, always safe):
Dog dog = new Dog();
Animal animal = dog; // Dog IS-A Animal — no cast needed
animal.speak(); // "Woof!" — polymorphism, calls Dog's speak()
// animal.fetch(); // COMPILE ERROR — Animal doesn't have fetch()
// Downcasting — parent to child (explicit, risky):
Animal a = new Dog(); // upcast — a holds a Dog object
Dog d = (Dog) a; // downcast — safe because a IS actually a Dog
d.fetch(); // works fine
// ClassCastException — downcasting to wrong type:
Animal a2 = new Cat();
Dog d2 = (Dog) a2; // COMPILE ERROR? No — compiles fine
// throws ClassCastException at RUNTIME
// because a2 holds a Cat, not a Dog
// Always use instanceof before downcasting:
Animal unknown = getAnimal();
if (unknown instanceof Dog) {
Dog safedog = (Dog) unknown; // safe — checked first
safedog.fetch();
}
// Pattern matching instanceof (Java 16+) — cleaner syntax:
if (unknown instanceof Dog safedog) {
safedog.fetch(); // no explicit cast needed — already typed as Dog
}Numeric Casting Pitfalls
These are the specific traps that catch developers when casting between numeric types:
Java
// ── Pitfall 1: double to int truncates, does NOT round ───────────
double d = 9.9999;
int i = (int) d;
System.out.println(i); // 9 — not 10
// To round, use Math.round() first:
long rounded = Math.round(9.9999); // 10
int roundedInt = (int) Math.round(9.9999); // 10
// ── Pitfall 2: long to int silently truncates high bits ──────────
long big = 10_000_000_000L; // 10 billion — exceeds int range
int truncated = (int) big;
System.out.println(truncated); // 1410065408 — garbage value, no warning
// Safe check before casting:
if (big >= Integer.MIN_VALUE && big <= Integer.MAX_VALUE) {
int safe = (int) big;
}
// ── Pitfall 3: float/double precision loss ───────────────────────
double precise = 1234567890.123456789;
float imprecise = (float) precise;
System.out.println(precise); // 1.2345678901234567E9
System.out.println(imprecise); // 1.23456794E9 — precision lost
// ── Pitfall 4: char and int interaction ─────────────────────────
char c = 'A';
int code = c; // widening — 65, no cast needed
char back = (char)(c + 1); // narrowing — 'B', cast required
System.out.println(c + 1); // 66 — int arithmetic, not 'B'
System.out.println("" + (char)(c + 1)); // "B" — cast first, then concatenate
// ── Pitfall 5: integer division before widening ──────────────────
int a = 5, b = 2;
double wrong = a / b; // 2.0 — division happens as int first
double right = (double) a / b; // 2.5 — cast before divisionString Conversions — Not True Casting
Converting between String and other types is not casting in the Java sense — there's no inheritance relationship between String and int. These conversions use dedicated methods.
Java
// Primitive → String:
int i = 42;
String s1 = String.valueOf(i); // "42" — works for any type
String s2 = Integer.toString(i); // "42"
String s3 = "" + i; // "42" — concatenation trick (less preferred)
double d = 3.14;
String ds = String.valueOf(d); // "3.14"
String ds2 = Double.toString(d); // "3.14"
boolean b = true;
String bs = String.valueOf(b); // "true"
// String → Primitive (parsing):
int parsed = Integer.parseInt("42"); // 42
long parsedL = Long.parseLong("123456789"); // 123456789L
double parsedD = Double.parseDouble("3.14"); // 3.14
boolean parsedB = Boolean.parseBoolean("true"); // true
// Parsing fails with NumberFormatException if String isn't a valid number:
int bad = Integer.parseInt("hello"); // throws NumberFormatException
int bad2 = Integer.parseInt("3.14"); // throws NumberFormatException — not an int
// Safe parsing with try-catch:
try {
int value = Integer.parseInt(input);
} catch (NumberFormatException e) {
System.out.println("Invalid number: " + input);
}
// String → char array and back:
String str = "Hello";
char[] chars = str.toCharArray(); // ['H','e','l','l','o']
String back = new String(chars); // "Hello"Related Topics in Java Basics
Variables in Java
A variable is just a named box in memory that holds a value. Java is strict about what goes in each box — you tell it the type upfront. Once you get this, the rest of Java clicks into place.
Data Types in Java
Java needs to know exactly what kind of data it's dealing with before it can store or process it. Integers, decimals, characters, true/false — each has its own type. Knowing which to use (and why) makes your programs efficient and bug-free.
Primitive Data Types
Java has eight primitive data types — the most basic building blocks for storing data. Unlike objects, primitives are stored directly in memory, making them fast and efficient. Understanding each type, its size, range, and when to use it is fundamental to writing correct Java programs.
Non-Primitive Data Types
Non-primitive data types — also called reference types — are everything beyond Java's eight primitives. Strings, arrays, classes, interfaces, enums, and records all fall into this category. They're more powerful than primitives, but they work differently in memory, comparison, and nullability. Understanding the distinction is essential for writing correct Java.