Explain Codes LogoExplain Codes Logo

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

java
type-safety
generics
casting
Alex KataevbyAlex Kataev·Dec 26, 2024
TLDR

To convert a List<SuperType> to List<SubType>, employ a filtered copy since generics in Java prohibit direct casting:

List<SuperType> superList = ...; List<SubType> subList = superList.stream() .filter(SubType.class::isInstance) // Get only triangles from mixed bag of shapes .map(SubType.class::cast) // Convert shape to triangle .collect(Collectors.toList()); // Magic! List of triangles

Here, Stream, using filter and map operations, ensures type safety and thus prevents ClassCastException at runtime.

Exceptions to avoid direct casting

There may be scenarios where direct casting might prove beneficial, these include:

  • Absolute certainty that the list contains only SubType instances.
  • Performance-driven contexts where cost of individually casting each element is prohibitive.

In such cases, you can utilize @SuppressWarnings("unchecked") to suppress warnings, acknowledging that you've done your due diligence in verifying the list's content:

@SuppressWarnings("unchecked") List<SubType> subList = (List<SubType>)(List<?>) superList;

Nitty-gritty of generics and type safety

Java's type system is there to enforce type safety at compile time. Due to the invariant nature of generic types in Java, a List<SuperType> is not a List<SubType>. Here's why casting at the list level is problematic:

  • Type Erasure: At runtime, instances of List<SuperType> and List<SubType> become simply List. The JVM doesn't keep track of the generics.
  • Risk of Runtime Exceptions: Without checking each element, an insertion of a SuperType object that isn't a SubType will trigger a ClassCastException at runtime.

Alternative design options

Instead of casting, take a peek at some designs that eliminate the need for casting:

  • Use polymorphism: define methods in SuperType that SubType can elegantly override.
  • Adopt the Visitor Pattern: to separate operations from the object structure.
  • Paint with generics and wildcards (List<? extends SuperType>): to write more flexible and readable code.

Dealing with unchecked warnings

When the demon of casting cannot be exorcised due to legacy code or unyielding constraints, @SuppressWarnings("unchecked") comes to the rescue:

  • Break the glass for suppression only when you have an iron grip over the list contents.
  • Always document why the suppression is safe and how you've locked type safety into the cellar.

Casting limitations and workarounds

Casting a List<SuperType> to a List<SubType> is like trying to fit a square peg in a round hole due to type erasure and invariance:

  • Generics snarl at the sight of array creation, e.g., new List<SubType>[10], rules of the game make generic array creation illegal.
  • Parameterized types give the instanceof operator a cold shoulder, e.g., if (list instanceof List<SubType>). Workarounds exist, but caveats apply.