Runtime Type Identification (RTTI) in Java
Question
Describe runtime type identification in Java. Give an example using instanceof.
Answer
Runtime Type Identification (RTTI) in Java allows you to determine the actual type of an object at runtime. The instanceof
operator is the primary mechanism for RTTI, enabling you to check if an object is an instance of a specific class or interface.
Basic Usage
-
Simple Type Checking
public class RTTIBasics { public static void basicTypeChecking() { Object obj = "Hello"; // Check if object is instance of String if (obj instanceof String) { String str = (String) obj; System.out.println(str.toUpperCase()); } // Check if object is instance of Number Number num = 42; if (num instanceof Integer) { Integer intValue = (Integer) num; System.out.println(intValue.doubleValue()); } } }
-
Interface Checking
public class InterfaceChecking { public static void interfaceTypeChecking() { Object obj = new ArrayList<>(); // Check if object implements List interface if (obj instanceof List) { List<?> list = (List<?>) obj; System.out.println("Is a List"); } // Check if object implements Serializable if (obj instanceof Serializable) { System.out.println("Is Serializable"); } } }
Pattern Matching (Java 14+)
-
Basic Pattern Matching
public class PatternMatching { public static void patternMatching() { Object obj = "Hello"; // Pattern matching with instanceof if (obj instanceof String str) { System.out.println(str.toUpperCase()); } // Pattern matching with multiple conditions if (obj instanceof String str && str.length() > 5) { System.out.println("Long string: " + str); } } }
-
Complex Pattern Matching
public class ComplexPatternMatching { public static void complexPatternMatching() { Object obj = new ArrayList<String>(); // Pattern matching with generics if (obj instanceof ArrayList<?> list) { System.out.println("Is an ArrayList"); } // Pattern matching with multiple types if (obj instanceof List<?> list && !list.isEmpty()) { System.out.println("Non-empty List"); } } }
Common Use Cases
-
Type-Safe Processing
public class TypeSafeProcessor { public static void processObject(Object obj) { if (obj instanceof Number num) { processNumber(num); } else if (obj instanceof String str) { processString(str); } else if (obj instanceof List<?> list) { processList(list); } } private static void processNumber(Number num) { System.out.println("Processing number: " + num); } private static void processString(String str) { System.out.println("Processing string: " + str); } private static void processList(List<?> list) { System.out.println("Processing list of size: " + list.size()); } }
-
Collection Processing
public class CollectionProcessor { public static void processCollection(Collection<?> collection) { if (collection instanceof List<?> list) { processList(list); } else if (collection instanceof Set<?> set) { processSet(set); } else if (collection instanceof Queue<?> queue) { processQueue(queue); } } private static void processList(List<?> list) { // Process list-specific operations System.out.println("Processing List"); } private static void processSet(Set<?> set) { // Process set-specific operations System.out.println("Processing Set"); } private static void processQueue(Queue<?> queue) { // Process queue-specific operations System.out.println("Processing Queue"); } }
Best Practices
-
Avoiding Excessive Type Checking
public class TypeCheckingBestPractices { // Bad - Excessive type checking public void badTypeChecking(Object obj) { if (obj instanceof String) { // String specific code } else if (obj instanceof Integer) { // Integer specific code } else if (obj instanceof Double) { // Double specific code } // ... many more type checks } // Good - Using polymorphism public void goodTypeChecking(Number num) { // Use polymorphism instead of type checking System.out.println(num.doubleValue()); } }
-
Using Pattern Matching
public class PatternMatchingBestPractices { // Bad - Old style type checking public void oldStyleChecking(Object obj) { if (obj instanceof String) { String str = (String) obj; processString(str); } } // Good - Using pattern matching public void patternMatchingChecking(Object obj) { if (obj instanceof String str) { processString(str); } } }
Common Pitfalls
-
Null Handling
public class NullHandling { // Bad - Missing null check public void badNullHandling(Object obj) { if (obj instanceof String) { String str = (String) obj; System.out.println(str.length()); // NPE if obj is null } } // Good - Proper null handling public void goodNullHandling(Object obj) { if (obj != null && obj instanceof String str) { System.out.println(str.length()); } } }
-
Incorrect Type Hierarchy
public class TypeHierarchy { // Bad - Checking for subclass before superclass public void badTypeHierarchy(Object obj) { if (obj instanceof Dog) { // Dog specific code } else if (obj instanceof Animal) { // Will never be reached // Animal specific code } } // Good - Checking superclass first public void goodTypeHierarchy(Object obj) { if (obj instanceof Animal) { // Animal specific code } else if (obj instanceof Dog) { // Dog specific code } } }
Advanced Usage
-
Generic Type Checking
public class GenericTypeChecking { public static <T> void checkGenericType(Object obj, Class<T> type) { if (type.isInstance(obj)) { T value = type.cast(obj); processGenericValue(value); } } private static <T> void processGenericValue(T value) { System.out.println("Processing value of type: " + value.getClass()); } }
-
Multiple Interface Checking
public class MultipleInterfaceChecking { public static void checkInterfaces(Object obj) { if (obj instanceof Serializable && obj instanceof Cloneable) { System.out.println("Object is both Serializable and Cloneable"); } // Using pattern matching with multiple interfaces if (obj instanceof Serializable ser && obj instanceof Cloneable) { System.out.println("Object is both Serializable and Cloneable"); } } }
Performance Considerations
-
JVM Optimization
public class PerformanceOptimization { // Good - Using pattern matching (optimized by JVM) public void optimizedChecking(Object obj) { if (obj instanceof String str) { System.out.println(str.length()); } } // Bad - Multiple type checks public void unoptimizedChecking(Object obj) { if (obj instanceof String) { String str = (String) obj; if (str.length() > 0) { System.out.println(str); } } } }
-
Caching Type Information
public class TypeInformationCaching { private final Map<Class<?>, Boolean> typeCache = new HashMap<>(); public boolean isInstanceOf(Object obj, Class<?> type) { return typeCache.computeIfAbsent(type, t -> t.isInstance(obj) ); } }
Design Patterns and RTTI
-
Visitor Pattern
public class VisitorPattern { public interface Visitor { void visit(String str); void visit(Integer num); void visit(List<?> list); } public static class TypeVisitor implements Visitor { @Override public void visit(String str) { System.out.println("Visiting String: " + str); } @Override public void visit(Integer num) { System.out.println("Visiting Integer: " + num); } @Override public void visit(List<?> list) { System.out.println("Visiting List of size: " + list.size()); } } public static void visitObject(Object obj, Visitor visitor) { if (obj instanceof String str) { visitor.visit(str); } else if (obj instanceof Integer num) { visitor.visit(num); } else if (obj instanceof List<?> list) { visitor.visit(list); } } }
-
Factory Pattern
public class FactoryPattern { public static Object createObject(String type) { if (type instanceof String str) { switch (str.toLowerCase()) { case "string": return new String(); case "integer": return new Integer(0); case "list": return new ArrayList<>(); default: throw new IllegalArgumentException("Unknown type"); } } throw new IllegalArgumentException("Type must be a String"); } }
Modern Java Features
-
Pattern Matching in Switch (Java 17+)
public class ModernPatternMatching { public static String getType(Object obj) { return switch (obj) { case String str -> "String: " + str; case Integer num -> "Integer: " + num; case List<?> list -> "List of size: " + list.size(); case null -> "null"; default -> "Unknown type"; }; } }
-
Record Pattern Matching (Java 19+)
public class RecordPatternMatching { public record Point(int x, int y) {} public static String getQuadrant(Object obj) { return switch (obj) { case Point(int x, int y) when x > 0 && y > 0 -> "First Quadrant"; case Point(int x, int y) when x < 0 && y > 0 -> "Second Quadrant"; case Point(int x, int y) when x < 0 && y < 0 -> "Third Quadrant"; case Point(int x, int y) when x > 0 && y < 0 -> "Fourth Quadrant"; default -> "Not a Point"; }; } }