Programming
Type Casting
Object-Oriented Programming
Java
Data Structures

How do you cast a List of supertypes to a List of subtypes?

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

In software development, specifically within object-oriented programming, casting is a common operation wherein a variable of one type is converted into another. While straightforward in most cases, casting a list of supertype elements to a list of subtype elements presents unique challenges and considerations. This type of casting often arises in scenarios involving polymorphism, where a method might return a list of elements of a generic supertype, but specific operations require handling them as a specific subtype.

Understanding the Basics: Supertypes and Subtypes

To begin with, it's crucial to understand the definitions of supertypes and subtypes within the context of a programming language like Java. A subtype is a class that extends another class (the supertype) or implements an interface. For instance, consider a class Animal that has several subclasses such as Cat and Dog.

Approach to Casting Lists

Given a List<Animal>, you might find yourself needing to treat it as a List<Cat> or a List<Dog> to leverage unique methods provided by these subclasses. Directly casting the list, such as (List<Dog>) listAnimals, is unsafe and generally incorrect. This is because Java’s generics are invariant, meaning that even though Cat is a subtype of Animal, a List<Cat> is not a subtype of List<Animal>.

Safe Approaches to Converting Lists

Using a Filtered and Casted Loop

One safe way to achieve the conversion is by iterating over the list, checking each element's type, and then adding the appropriately casted objects to a new list of the subtype.

java
1List<Animal> listAnimals = // obtained from somewhere
2List<Cat> listCats = new ArrayList<>();
3
4for (Animal animal : listAnimals) {
5    if (animal instanceof Cat) {
6        listCats.add((Cat) animal);
7    }
8}

This method ensures type safety by using instanceof to check the type before casting and adding to the new list. This avoids ClassCastException.

Using Java Streams (Java 8+)

A more modern and concise approach is to use Java Streams:

java
1List<Cat> listCats = listAnimals.stream()
2    .filter(Cat.class::isInstance)
3    .map(Cat.class::cast)
4    .collect(Collectors.toList());

This method also maintains type safety and leverages the powerful Stream API for concise processing of collections.

Downcasting vs Upcasting

Upcasting refers to casting from a subtype to a supertype, which is always safe and can be done implicitly. Downcasting, however, involves casting from a supertype to a subtype. Downcasting can be dangerous and should be handled with care, generally requiring explicit checks as shown above.

Summary Table

Below is a summary of key points discussed:

MethodDescriptionSafety
Direct Cast(List<Dog>) listAnimalsUnsafe and likely to cause ClassCastException
Filtered LoopUsing a loop with type checks and explicit castingSafe, but verbose
Streams APILeveraging Java's Stream API with methods like filter and mapConcise and safe

Potential Pitfalls and Best Practices

Directly casting without checking types can lead to runtime exceptions, notably ClassCastException. It is best practice to ensure type safety either through explicit checks (using instanceof) or by utilizing Java's generic type system, particularly bounded wildcards that can promise compiler-level type safety to some extent.

Conclusion

Casting a list of supertypes to a list of subtypes, while not directly supported by Java’s type system, can be achieved safely through careful implementation of type checks and controlled list population. Understanding the limitations and risks inherent in unchecked casting is crucial to avoid common pitfalls in type management within Java and similar statically typed languages.


Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms