I’m a big fan of Baeldung’s blog. One of his latest post is about casting in Java. Unfortunately, I feel it misses one important point: dynamic casting. Since that’s is (very) relatively new, this post will try to fill that gap.
Do not use casting
The first thing is that it should be relatively easy to avoid casting.
Using polymorphism
Polymorphism is a great way not to cast. Consider the following code:
List animals = new ArrayList();
animals.add(new Cat());
animals.add(new Dog());
for (Animal animal: animals) {
if (animal instanceof Cat) {
((Cat) animal).mew();
} else if (animal instanceof Dog) {
((Dog) animal).bark();
} else if (animal instanceof Snake) {
((Snake) animal).hiss();
}
}
If all object types in the collection inherit from a single type, it’s possible to use polymorphism. Just add one method in this single type and override it in the subtypes.
public abstract class Animal {
public abstract void makeSound();
}
public class Dog {
@Override public void makeSound() {
bark();
}
}
public class Cat {
@Override public void makeSound() {
mew();
}
}
Using generics
Polymorphism cannot be always applied. For example, this is the case when:
- Items are not instances of related type
- The parent type is not within our control sphere
- etc.
In those cases, generics is another way to avoid casting.
Before Java 5, the following code would be the norm:
List dates = new ArrayList();
dates.add(new Date());
Object object = dates.get(0);
Date date = (Date) object; (1)
1 | Casting required.
Though the runtime type is Date , the compiler has no way to know about it. |
With generics, the above code can be rewritten:
List<Date> dates = new ArrayList<>();
dates.add(new Date());
Date date = dates.get(0); (1)
1 | No casting: the compiler has enough information thanks to generics. |
When casting is mandatory
Despite to what some fanatics think, it happens that sometimes casting cannot be ignored.
One such use-case is the Servlet API.
Map storing objects in servlet context/request/session do not use generics.
And even if they did, they would be using Object
.
// In a servlet
ServletContext context = getServletContext();
context.put("date", new Date());
// Somewhere else
ServletContext context = getServletContext();
Object object = context.get("date");
Date date = (Date) object;
Dynamic casting
The only form of casting originally available was static casting.
Which means the casting type needs to be known at compile time.
For example, let’s imagine a method that accepts a Stream<Object>
, filters all element of a certain type and returns those elements in the right type.
This is an example of the usage:
List<?> items = ...
List<Date> dates = filter(Date.class, items);
There’s no way to implement the filter method with static casting. There are actually two issues:
- The
instanceof
operator requires a type, not aClass
instance e.g.item instanceof Date
- The cast syntax as well e.g.
(Date) item
The instanceof
operator can be replaced with a call to Class.isInstance(Object)
(since JDK 1.1).
This is quite well-known, if not widely used.
The API to replace the cast syntax, however, is much more "recent".
There’s a Class.cast(Object)
method since JDK 1.5
It is a simple wrapper around the legacy syntax.
Using both methods, it’s finally possible to implement the filter
method above.
static <T> List<T> filter(Class<T> clazz, List<?> items) {
return items.stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.collect(Collectors.toList());
}
Using the casting API allows dynamic casting. Without it, it’s not possible to implement the above method.
Conclusion
Despite what many people outside the ecosystem think, Java evolves, even if not very fast. However, developers needs to be acquainted with the new capabilities offered by each version.