☕ Java
instanceof Operator
instanceof checks whether an object is an instance of a specific class or interface. It's the safe way to determine an object's type at runtime before casting. Java 16 added pattern matching to instanceof — eliminating the redundant cast that always followed it.
Basic instanceof
instanceof takes an object reference on the left and a type on the right. It returns true if the object is an instance of that type — or a subtype of it.
Java
// Basic syntax: object instanceof Type
Object obj = "Hello";
System.out.println(obj instanceof String); // true
System.out.println(obj instanceof Object); // true — String IS-A Object
System.out.println(obj instanceof Integer); // false
// With class hierarchy:
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
Dog dog = new Dog();
System.out.println(dog instanceof Dog); // true
System.out.println(dog instanceof Animal); // true — Dog IS-A Animal
System.out.println(dog instanceof Object); // true — everything IS-A Object
System.out.println(dog instanceof Cat); // false
// With interfaces:
interface Flyable { }
class Bird extends Animal implements Flyable { }
Bird bird = new Bird();
System.out.println(bird instanceof Flyable); // true — implements Flyable
System.out.println(bird instanceof Animal); // true — extends Animalnull and instanceof
instanceof always returns false when the left operand is null. This is one of the most useful properties — it means instanceof is inherently null-safe.
Java
// null instanceof anything = false (always):
String s = null;
System.out.println(s instanceof String); // false — not true
System.out.println(s instanceof Object); // false
Object o = null;
System.out.println(o instanceof String); // false
// This makes instanceof null-safe:
// Traditional null-then-cast pattern required null check:
if (obj != null && obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
// With instanceof, null check is redundant:
if (obj instanceof String) { // false if obj is null — no NPE
String str = (String) obj;
System.out.println(str.length());
}instanceof Before Casting — The Classic Pattern
The traditional use of instanceof is to guard a downcast. Without instanceof, a wrong cast throws ClassCastException at runtime. With instanceof, you confirm the type before casting safely.
Java
// Without instanceof — risky:
void process(Animal animal) {
Dog dog = (Dog) animal; // throws ClassCastException if animal is a Cat
dog.fetch();
}
// With instanceof — safe:
void process(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // safe — confirmed above
dog.fetch();
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.purr();
}
}
// The classic anti-pattern — instanceof + getClass() comparison:
// instanceof checks the full hierarchy (includes subclasses)
// getClass() checks exact type only
class Square extends Rectangle { }
Rectangle r = new Square();
System.out.println(r instanceof Rectangle); // true — Square IS-A Rectangle
System.out.println(r.getClass() == Rectangle.class); // false — exact type is Square
// Use instanceof for "is compatible with" checks
// Use getClass() equality for "is exactly this type" checks (rare)Pattern Matching instanceof (Java 16+)
Java 16 introduced pattern matching for instanceof. Instead of checking the type and then immediately casting to a new variable, you can do both in one step. The cast variable is only in scope when the condition is true.
Java
// Before Java 16 — check then cast (redundant):
if (obj instanceof String) {
String s = (String) obj; // redundant cast — we already know it's a String
System.out.println(s.toUpperCase());
}
// Java 16+ pattern matching — check and bind in one step:
if (obj instanceof String s) {
System.out.println(s.toUpperCase()); // s is already a String — no cast needed
}
// The binding variable s is only in scope inside the if block:
if (obj instanceof String s) {
System.out.println(s.length()); // s available here
}
// s not available here
// Works with the logical operators:
if (obj instanceof String s && s.length() > 5) {
System.out.println("Long string: " + s);
}
// s available in both the && condition AND the body
// Negation — s is NOT in scope in the negated branch:
if (!(obj instanceof String s)) {
return; // s not available here
}
System.out.println(s.length()); // s IS available here — after the early return
// Pattern matching in complex type-switching:
void describe(Object obj) {
if (obj instanceof Integer i) {
System.out.println("Integer: " + i);
} else if (obj instanceof String s) {
System.out.println("String of length " + s.length());
} else if (obj instanceof int[] arr) {
System.out.println("int array of length " + arr.length);
} else {
System.out.println("Unknown: " + obj);
}
}Pattern Matching in switch (Java 21+)
Java 21 extends pattern matching to switch expressions and statements — enabling concise, exhaustive type-dispatching without any instanceof chains.
Java
// Java 21 — switch with type patterns:
Object obj = getValue();
String result = switch (obj) {
case Integer i -> "Integer: " + i;
case Long l -> "Long: " + l;
case Double d -> "Double: " + d;
case String s -> "String: " + s.toUpperCase();
case int[] arr -> "int array, length " + arr.length;
case null -> "null value";
default -> "Other: " + obj.getClass().getSimpleName();
};
System.out.println(result);
// Guarded patterns — add conditions to cases with 'when':
String classify(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> "negative int";
case Integer i when i == 0 -> "zero";
case Integer i -> "positive int: " + i;
case String s when s.isEmpty() -> "empty string";
case String s -> "string: " + s;
default -> "other";
};
}
// With sealed classes — switch can be exhaustive (no default needed):
sealed interface Shape permits Circle, Rectangle, Triangle { }
record Circle(double radius) implements Shape { }
record Rectangle(double w, double h) implements Shape { }
record Triangle(double base, double height) implements Shape { }
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.w() * r.h();
case Triangle t -> 0.5 * t.base() * t.height();
// no default needed — sealed hierarchy is exhaustive
};
}instanceof vs getClass() vs isInstance()
Java provides three ways to check an object's type at runtime. Each has different semantics and appropriate use cases.
Java
class Animal { }
class Dog extends Animal { }
Dog dog = new Dog();
// 1. instanceof — checks if object IS-A Type (includes subclasses):
System.out.println(dog instanceof Animal); // true
System.out.println(dog instanceof Dog); // true
// 2. getClass() == — checks EXACT runtime type only:
System.out.println(dog.getClass() == Animal.class); // false — Dog != Animal
System.out.println(dog.getClass() == Dog.class); // true — exact match
// 3. Class.isInstance() — runtime equivalent of instanceof (useful in generics):
Class<?> type = String.class;
System.out.println(type.isInstance("Hello")); // true
System.out.println(type.isInstance(42)); // false
// When to use each:
// instanceof — "is this object compatible with this type?" (most common)
// getClass() == — "is this object exactly this type, not a subtype?" (rare)
// Class.isInstance — "check instanceof dynamically at runtime in generic code"
// Example where the difference matters:
void printIfAnimal(Object o) {
if (o instanceof Animal) {
System.out.println("Is an animal (or subtype)"); // Dog qualifies
}
if (o.getClass() == Animal.class) {
System.out.println("Is exactly an Animal"); // Dog does NOT qualify
}
}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.