Estoy seguro que en algún momento hemos necesitado saber si un objeto es de un determinado tipo o no. Cuando trabajamos con objetos es importante poder controlar esto para evitar “pequeños desastres” durante la ejecución de nuestro código.
Existe una palabra clave especial en Java que se encarga justamente de esto. Hablamos del operador instanceOf
por supuesto pero antes de que des saltos de alegría “quieto parao” y no te lances todavía. En este artículo vamos a hablar de sus pros pero también de sus contras. O dicho de otro modo, por qué instanceOf debería ser tu último recurso.
Sobre el papel usar este operador es tan útil como tentador. Su sintaxis sería algo como el siguiente ejemplo:
obj instanceOf Object
Al lado izquierdo tenemos la instancia y el lado derecho el nombre de la clase y como resultado el operador instanceOf
devuelve un resultado booleano. Con este resultado (true o false, verdadero o falso) podemos determinar si el objeto al que apunta una referencia dada es una instancia de una clase o interfaz concretas. Pero para entenderlo mejor veamos algunos ejemplos:
public class Main {
public static void main (String[] args) {
Perro perro = new Perro();
System.out.print(perro instanceof Animal); // true
}
}
class Animal {}
class Perro extends Animal {}
class Robot {}
Supersencillo. El objeto al que apunta la variable perro es claramente una instancia de la clase Perro, que a su vez hereda características de la clase Animal. Por ello, el resultado es Verdadero (true).
Ahora vayamos con el siguiente ejemplo:
class Animal {}
class Fish extends Animal {
void swim(){
System.out.println(“Swim”);
}
}
class Bird extends Animal {
void fly(){
System.out.println(“Fly”);
}
}
class Kangaroo extends Animal {
void jump(){
System.out.println(“Jump”);
}
}
public final class BadUseOfInstanceOf {
public static void main(String[] args){
makeItMove(new Fish());
makeItMove(new Bird());
makeItMove(new Kangaroo());
}
public static void makeItMove(Animal animal){
if (animal instanceof Fish){
Fish fish = (Fish)animal;
fish.swim();
}
else if (animal instanceof Bird){
Bird bird = (Bird)animal;
bird.fly();
}
else if (animal instanceof Kangaroo){
Kangaroo kangaroo = (Kangaroo)animal;
kangaroo.jump();
}
}
}
Si te has dado cuenta, este es un ejemplo de un mal uso de instanceof
. Me explico. Tenemos que tener en cuenta que estamos haciendo la implementación de este modo porque queremos declarar diferentes tipos con sus comportamientos correspondientes. El matiz es que aquí los objetos se tratan como si fueran del tipo de superclase y ese tipo no es una abstracción útil porque no contiene suficiente información para determinar qué se desea hacer con el objeto y eso puede ser un problema porque instanceof
puede devolver valor verdadero aún cuando no disponemos de una referencia real dado que lo correcto y esperable es que sea al objeto el que apunta la referencia y no de la referencia en sí.
Esto se debe a que la referencia en sí no es nada, es como un hilo que sirve de guía para llegar al objeto. La referencia en sí no es instancia de nada. Esto te tiene que quedar muy claro, porque es posible que, de otro modo, pienses que instanceof
devuelve siempre el valor true si se utiliza del modo antes mostrado en el ejemplo y estaríamos cayendo en una trampa. Si todavía tienes dudas volvamos al primer ejemplo pero cambiándolo un poco:
public class Main {
public static void main (String[] args) {
Perro perro = new Perro();
perro = null;
System.out.print(perro instanceof Animal); // false
}
}
class Animal {}
class Perro extends Animal {}
class Robot {}
Quizá así lo podemos ver más claro. Aquí la referencia perro no apunta a ningún objeto por lo que tenemos un valor null como salida, y null no es instancia de nada (false).
Esto nos deja clara la evidencia de que el resultado no siempre nos ayudará a mantener nuestro código lo más claro y limpio posible. Entonces ¿qué alternativas tenemos a instanceof
. Bueno, si no queremos llenar nuestro código de if/else quizá la mejor aproximación sea que, si queremos que el comportamiento específico dependa del tipo de objeto, entonces ese comportamiento debe encapsularse en el objeto mismo.
Normalmente si cometemos algún error el compilador suele devolver un mensaje de error que nos advierte del problema, pero cuando usamos el de instanceof
con interfaces instanceof siempre compila. Veamos un ejemplo:
interface Vehicle { }
class Car { }
class Ferrari extends Car implements Vehicle {}
Si verificamos la palabra clave instanceof
con los objetos de clase anteriores, veremos los siguientes resultados:
Ferrari ferrari = new Ferrari();
ferrari instanceof Vehicle // true - Ferrari implementa Vehicle
ferrari instanceof Car // true - Ferrari extiende de Car
ferrari instanceof Ferrari // true - Ferrari es Ferrari
ferrari instanceof Object // true - El objeto es el tipo principal de todos los objetos.
Pero si revertimos algo, veremos resultados diferentes:
Car car = new Car();
car instanceof Ferrari // false - El coche es un supertipo de Ferrari, no al revés
Se suele recomendar utilizar el método isInstance()
en lugar de instanceof
para comprobar la clase del objeto. Ambos son muy parecidos, ambos devuelven un valor booleano, pero la gran diferencia es que el método de clase isInstance()
funcionará tal y como esperamos en cualquier escenario, algo que ya queda claro que con instanceof no es siempre fiable.
Veamos un ejemplo
public class Test
{
public static void main(String[] args)
{
Integer i = new Integer(5);
// prints true as i is instance of class
// Integer
System.out.println(i instanceof Integer);
}
}
Output = True
Ahora, si queremos verificar la clase del objeto en tiempo de ejecución, debemos usar el método isInstance()
.
public class Test
{
// This method tells us whether the object is an
// instance of class whose name is passed as a
// string 'c'.
public static boolean fun(Object obj, String c)
throws ClassNotFoundException
{
return Class.forName(c).isInstance(obj);
}
// Driver code that calls fun()
public static void main(String[] args)
throws ClassNotFoundException
{
Integer i = new Integer(5);
// print true as i is instance of class
// Integer
boolean b = fun(i, "java.lang.Integer");
// print false as i is not instance of class
// String
boolean b1 = fun(i, "java.lang.String");
/* print true as i is also instance of class
Number as Integer class extends Number
class*/
boolean b2 = fun(i, "java.lang.Number");
System.out.println(b);
System.out.println(b1);
System.out.println(b2);
}
}
Output:
true
false
true
NOTA: El operador instanceof
arroja un error de tiempo de compilación (“Incompatible conditional operand types”) si verificamos el objeto con otras clases que no son instanciables:
public class Test
{
public static void main(String[] args)
{
Integer i = new Integer(5);
// Below line causes compile time error:-
// Incompatible conditional operand types
// Integer and String
System.out.println(i instanceof String);
}
}
Output :
13: error: incompatible types: Integer cannot be converted to String
System.out.println(i instanceof String);
Y llegamos al final de nuestro artículo de hoy. Pero antes hagamos un resumen de lo aprendido. El operador instanceof puede parecer una buena idea sobretodo cuando comenzamos a programar, pero como hemos visto debe ser usado con cautela y debería ser nuestro último recurso. Su excesivo uso puede indicar uno de los siguientes problemas:
En lugar de instanceof
lo recomendable es utilizar el método de clase Java isInstance()
, que al igual que instanceof
devolverá un valor booleano con la diferencia de que su resultado será mucho más fiable.