Using InstanceOf and Alternatives Within Java
After reviewing a processing approach that utilized the InstanceOf operator, a Zone Leader began to wonder if better alternatives could be employed.
Join the DZone community and get the full member experience.
Join For FreeWhile reviewing a customer code base, there was a situation where JSON data was mapped into a collection of POJOs (plain old Java objects) for processing. Part of the JSON payload included a List<Base>
— where Base is a custom Java parent class that was created to house all of the common attributes maintained by the elements, which could be part of the simple List
.
You may also like: The InstanceOf Code Smell and One Way to Get Around It
To make things simple, let's assume a scenario where the following objects extend the Base
object:
Circle
Rectangle
Triangle
Of the three classes above, all the attributes for each class are distinct and are not shared by the other two. Instead, those attributes are part of the Base
class.
When processing the List<Base>
, there was a need to perform the following logic:
private void processBaseObjects(List<Base> baseObjects) throws InvalidBaseException {
for (Base base : baseObjects) {
if (base instanceof Circle) {
handleCircle(base);
} else if (base instanceof Rectangle) {
handleRectangle(base);
} else if (base instanceof Triangle) {
handlerTriangle(base);
} else {
// Throw some custom exception here
throw new InvalidBaseException();
}
}
}
Two things gained my interest in the approach noted above:
The use of multiple
else if
statementsThe use of the
InstanceOf
operator to make processing decisions
As I took using Google to gain a better understanding of how others have handled such a situation, I was surprised at the high level of discussions/debates at play.
Below are some alternative approaches that can be employed in this scenario.
Apply Polymorphism
If it is possible to change the Circle
, Rectangle
, and Triangle
classes to implement an Interface
; the Interface
class could implemented as shown below:
public interface BaseProcessor {
void handleObject();
}
From there, the Circle
class (as an example) can be updated, as shown below:
public class Circle extends Base implements BaseProcessor {
@Overload
handleObject() {
// start processing logic here ...
}
}
At this point, the original logic becomes simplified, as shown below:
private void processBaseObjects(List<Base> baseObjects) throws InvalidBaseException {
baseObjects.forEach(base -> base.handleObject());
}
The one reservation I have with this approach is that the POJOs is currently a collection of attributes. If the handleObject()
logic requires integration with a given service, the complexity behind the Circle
class (as an example) increases — which might not be desirable.
Of course, if it is not possible to change the Circle
, Rectangle
, and Triangle
classes, this is not an ideal solution.
Use getClass()
Another option is to use the object's getClass()
method to make a test similar to using InstanceOf
. In the original code, this would be updated as shown below:
for (Base base : baseObjects) {
if (base.getClass().equals(Circle.class)) {
handleCircle(base);
} else if (base.getClass().equals(Rectangle.class)) {
handleRectangle(base);
} else if (base.getClass().equals(Triangle.class)) {
handlerTriangle(base);
} else {
// Throw some custom exception here
throw new InvalidBaseException(base.getType());
}
}
While this approach will work, it is important to keep in mind that getClass()
will return the runtime class object — making it impossible to determine if the object falls within a specific inheritance hierarchy.
Implement an Enum
An additional option is to create an enum as shown below:
public enum ShapeActionEnum {
Circle {
@Override
void handleObject() {
// start processing logic here ...
}
},
Rectangle {
@Override
void handleObject() {
// start processing logic here ...
}
},
Triangle {
@Override
void handleObject() {
// start processing logic here ...
}
};
abstract void handleObject();
}
From there, the parent Base
class will include the following property, which would be set during the mapping process:
ShapeActionEnum shapeActionEnum;
As a result, the original processing code would be simplified, as shown below:
private void processBaseObjects(List<Base> baseObjects) throws InvalidBaseException {
baseObjects.forEach(base -> .getShapeActionEnum().handleObject());
}
The challenge I have with this approach is that the processing logic is getting buried inside an enum class — which also introduces similar issues if service interaction is required.
Additional Options
Some additional approaches that could also be applied are noted below:
Visitor Pattern — an example can be found here.
Use a
Map<Class, Runnable>
where theRunnable
contains a lamba function on what action needs to be performed for the mappedClass
.Implement Pattern Matching — JEP Draft can be found here.
Conclusion
I was surprised at the level of information that exists from the concept of using InstanceOf
to determine a logic path.
Given this is a popular zone at DZone, I am interested in what implementations that have also been considered ... outside the examples I provided. Be sure to let me know in the comments section.
Have a really great day!
Further Reading
The InstanceOf Code Smell and One Way to Get Around It
Why the Instance Variable of the Super Class Is Not Overridden in the Sub Class
Opinions expressed by DZone contributors are their own.
Comments