Orientación a Objetos (Object Orientation) III - Casting de variables de referencia
lunes, mayo 21, 2012 at 6:57PM
gishac in certificacion, certificacion, java, java, ocjp, ocjp, ocp, ocp, ocpjp, orientacion a objetos, scjp

El uso de polimorfismo es una muy buena práctica de desarrollo para reutilizar funcionalidad, hacer el código escalable y entre otras cosas aprovechar las características de la programación orientada a objetos; pero a su vez nos lleva a hacer uso del Casting (cuando la ocasión lo amerita), lo que consiste en convertir de un tipo de objeto a otro.

Suponiendo que tenemos las clases:

public class MathOperator {
	protected double operator1;
	protected double operator2;
	public double getOperator1() {
		return operator1;
	}
	public void setOperator1(double operator1) {
		this.operator1 = operator1;
	}
	public double getOperator2() {
		return operator2;
	}
	public void setOperator2(double operator2) {
		this.operator2 = operator2;
	}
	
	public double calculate()
	{
		return 0;
	}
	
	protected MathOperator(double operator1, double operator2){
		this.operator1 = operator1;
		this.operator2 = operator2;
	}
	
}


public class Sum extends MathOperator {

	protected Sum(double operator1, double operator2) {
		super(operator1, operator2);
	}

	@Override
	public double calculate() {		
		return operator1 + operator2;
	}
	
	public double sumAbsolute(){
		return Math.abs(operator1) + Math.abs(operator2);
	}
}


public class Multiply extends MathOperator {

	protected Multiply(double operator1, double operator2) {
		super(operator1, operator2);
	}

	@Override
	public double calculate() {
		return operator1*operator2;
	}
}

Empezamos viendo que Sum y Multiply son una subclase de MathOperator y que implementan el método calculate y además Sum tiene un método propio sumAbsolute, con ésta estructura de clases es perfectamente válido para el compilador crear una instancia de Sum de las siguientes maneras:

Sum sum = new Sum(2, 3);
MathOperator sum2 = new Sum(2, 3);
Sum sum3 = (Sum) new MathOperator(4,5);

En la primera línea es una instanciación normal
La segunda línea corresponde a un upcasting, el cual el compilador lo acepta sin problemas inclusive de manera implicita (sin el casting) ya que la superclase MathOperator soporta tener una referencia de cualquier clase que la herede; si recordamos el IS-A, en este caso Sum IS-A MathOperator por lo cual es seguro que cualquier método que se ejecute de MathOperator la clase Sum lo implementa.
En la tercera línea tenemos lo contrario, un downcasting, ya que de una superclase estamos tratando de convertirla a un tipo más específico; en este tipo de castings nace una "relación de confianza" entre el desarrollador y el compilador, ya que como vimos en la segunda línea, el objeto sum2 realmente es una instancia de Sum, por eso el compilador acepta el casting como válido.

Si ejecutamos el código el resultado es el siguiente:

Exception in thread "main" java.lang.ClassCastException: javahispano.oop.MathOperator cannot be cast to javahispano.oop.Sum

      at javahispano.oop.Sample.main(Sample.java:11)


Así es, un MathOperator no es un Sum por lo que el casting no es válido, y aquí notamos la diferencia entre una RuntimeException y un error de compilación.

Como mencionamos, existe el voto de confianza que nos da el compilador cuando hacemos éste tipo de casting, ya que si cambiamos un poco el código tenemos el siguiente caso:

public static void main(String[] args) {
	MathOperator sum2 = new Sum(2, 3);
	Sum sum3 = (Sum) sum2;
	doCalculate(sum3);		
}

private static void doCalculate(MathOperator operator){
	System.out.println("Result: " + operator.calculate());
}

El resultado de la ejecución es:

Result: 5.0

Podemos ver que el casting es de igual manera un downgrade, pero en éste caso sum2 aunque está declarado como tipo MathOperator tiene una instancia del tipo Sum, por lo cual el casting es válido; y adicionalmente el método doCalculate no está tipificado para recibir como parámetro un objeto de tipo Sum, sino que es un método general que va a presentar el cálculo de cualquier operación que herede de MathOperator, por lo que el siguiente código es perfectamente válido:

MathOperator sum2 = new Sum(2, 3);
Sum sum3 = (Sum) sum2;
Multiply m1 = new Multiply(4, 2);
doCalculate (sum3);
doCalculate (m1);

El resultado de la ejecución es:

Result: 5.0

Result: 8.0

En éste ejemplo ejecutamos 2 veces el metodo doCalculate con 2 instancias de objeto de diferente tipo, pero que heredan de un tipo común, y como el método espera un tipo MathOperator se realiza el casting de las variables de referencia sum3 y m1 y si bien apenas el metodo tiene una sola línea de código, en la vida real mas allá de ésta didáctica puede ser un proceso con mucha lógica, el cual lo reutilizamos sólo con el hecho de aprovechar las bondades del polimorfismo.

Podemos afinar las conversiones entre tipos si hacemos uso de la palabra reservada instanceof para garantizarnos a nosotros mismos que vamos a hacer un casting válido, por ejemplo:

MathOperator sum2 = new MathOperator(2, 3);
Sum sum3 = (Sum) sum2;
System.out.println(sum3.sumAbsolute());

En éste ejemplo estamos invocando al método sumAbsolute, pero si recordamos ese método sólo está implementado en la clase Sum, y el objeto sum3 realmente es una instancia de MathOperator, claro vamos a tener el mismo error de casting en tiempo de ejecución, pero como vemos el compilador no es capaz de decirnos que estamos tratando de invocar a un método que no existe en ese objeto, aquí hacemos uso de instanceof para garantizar el casting: 

MathOperator sum2 = new MathOperator(2, 3);
if(sum2 instanceof Sum)
{
	Sum sum3 = (Sum) sum2;
	System.out.println(sum3.sumAbsolute());
}
else
{
	System.out.println("Object is not a instance of Sum");
}

 El resultado de la ejecución es:

 Object is not a instance of Sum 

De ésta manera nos aseguramos de que en tiempo de ejecución los castings no lancen una excepción. Cabe recalcar que el compilador si detecta cuando estamos tratando de hacer una conversión que realmente no va a ser posible, por ejemplo si tenemos:

Sum sum = (Sum) new File("file.txt");

Definitivamente vamos a tener un error de compilación ya que de ninguna manera va a ser posible que un objeto de tipo File sea convertido a uno de tipo Sum.

 Tips de Examen:

MathOperator sum = new MathOperator (2, 3);
sum.sumAbsolute();

Esto causa un error de compilación ya que quien tiene el método sumAbsolute es la clase Sum, inclusive si tenemos la siguiente forma:

MathOperator sum = new Sum (2, 3);
sum.sumAbsolute();

Aunque el objeto sum es una instancia de Sum, al hacer el upgrade el compilador no reconoce el método sumAbsolute como parte de la clase MathOperator. Para que la ejecución sea correcta tendría que existir un casting:

MathOperator sum = new Sum (2, 3);
((Sum)sum).sumAbsolute();

En éste caso es válido ya que sum es una instancia de Sum; y en el siguiente caso?:

MathOperator sum = new MathOperator (2, 3);
((Sum)sum).sumAbsolute();

El compilador nos permite pasar, pero al momento de ejecutar el código tendremos el error de ClassCastException.

 

Article originally appeared on javaHispano (http://www.javahispano.org/).
See website for complete article licensing information.