Sobrecarga en Wrapper Classes en Java

Buenas tardes, en esta entrada vamos a añadir algo mas a los conocimientos sobre el overload o Sobrecarga en Java.


  • La sobrecarga hace dificil el Emparejamiento de Métodos

    Aunque ya hemos visto varias reglas para la sobrecarga de métodos en anteriores entradas, en este vamos a añadir algunas herramientas nuevas a nuestro kit de herramientas de Java. En esta sección vamos a echar un ojo a 3 factores que pueden hacer que la sobrecarga sea un poco mas dificil:

    • Widening
    • Autoboxing
    • Var-args

    Cuando una clase tiene métodos sobrecargados, uno de los trabajos del compilador es determinar cuál de los métodos va a usar cuando encuentre una invocación para el método sobrecargado. Vamos a ver un ejemplo que no tiene ninguna de las características de Java 5:

    		class EasyOver {
    			static void go (int x) { System.out.print("int "); }
    			static void go (long x) { System.out.print("long "); }
    			static void go (double x) { System.out.print("double "); }
    			
    			public static void main (String[] args){
    				byte b = 5;
    				short s = 5;
    				long l = 5;
    				float f = 5.0f;
    				
    				go(b);
    				go(s);
    				go(l);
    				go(f);
    			}
    		}
    		

    Esto produce la siguiente salida:

    		int int long double 
    		

    Esto problablemente no sea una sorpresa, la llamada que usa un argumento byte y short son implícitamente ampliados para que puedan emparejarse con la versión del método go() que coge un int. Por supuesto, la llamada con el long usa la versión long de go(), y finalmente, la llamada que usa un float es emparejado con el método que coge un double.

    En cada caso, cuando un método exacto no se encuentra, la JVM usa el método con el argumento mas pequeño que es mas aplio que el parámetro.

    Podemos verificar por nosotros mismos que si solo hubiera una versión de go(), y este coge un double, sería usado para emparecar todas las invocaciones de go().

  • Sobrecarga con Boxing y Var-args

    Ahora vamos a usar neustro último ejemplo, y vamos añadir boxing en la mezcla:

    				class AddBoxing {
    			static void go(Integer x) { System.out.println("Integer"); }
    			static void go(Long x) { System.out.println("long"); }
    			
    			public static void main (String[] args){
    				int i = 5;
    				go(i);	// ¿Qué método go() será invocado?
    			}
    		}
    		

    Como hemos visto anteriormente, si solo hay una versión del método go() y solo puede aceptar un Integer, entonces la capacidad boxing de Java 5 nos permitiría la invocación de go() para lograrlo. Igualmente, si solo existiera una versión que coge un long, el compilador lo usaría para manejar la invocación de go(). La pregunta es, dado que ambos métodos existenm ¿Cuál de los 2 será invocado? En otras palabras ¿Pensará el compilador que ampliar el parámetro primitivo es mas deseable que realizar una operación de autoboxing?. La respuesta es que el compilador escogerá antes la opción de widening antes que el boxing, por lo que la salida será:

    		long
    		

    Los diseñadores de Java 5 decidieron que la regla mas importante debería funcionar de la manera que solía hacerlo, por lo que aunque al capacidad de widening exista, no perderá prioridad respecto a un método nuevo creado que se basa en el boxing. Basandonos en esta regla, vamos a intentar predecir la salida de lo siguiente:

    		class AddVarargs {
    			static void go (int x, int y) { System.out.println("int, int"); }
    			static void go (byte... x) { System.out.println("byte..."); }
    			public static void main (String[] args){
    				byte b = 5;
    				go(b, b);
    			}
    		}
    		

    Como proablemente suponíamos, la salida es:

    		int, int
    		

    Por esto, una vez mas, aunque la invocación requerirá alguna alguna especia de conversión, el compilador elegirá el estilo antiguo antes de elegir el nuevo estilo, manteniendo el código existente mas robusto. Hasta ahora mismo hemos visto que:

    • Widening vence al boxing
    • Widening vence a var-args

    En este punto, nos preguntamos¿El Boxing vence a var-args?:

    		class BoxOrVararg{
    			static void go (Byte x, Byte y){ System.out.println("Byte, Byte"); }
    			static void go(byte... x){ System.out.println("byte...."); }
    			
    			public static void main (String[] args){
    				byte b = 5;
    				go(b,b);	// Cual método go() será invocado?
    			}
    		}
    		

    La salida es:

    		Byte, Byte
    		

    Una buena manera de recordar esta regla es ver que el método var-args es mas “flojo” que el otro método, ya que podría manejar invocaciones con un diferente número de parametros byte. Un método var-args es mas como un método que coge todo, en términos de que invocacion puede manejar.

  • Variables de Referencia Widening

    Hemos visto que es legal ampliar un primitivo. ¿Podremos ampliar una variable de referencia, y si es así, que significaría? Vamos a volver atrás y pensar en una asignación polimorfica:

    		Animal a = new Dog();
    		

    A lo largo de las mismas lineas, la invocación puede ser:

    		class Animal { static void eat() { } }
    
    		class Dog3 extends Animal{
    			public static void main (String[] args){
    				Dog3 d = new Dog3();
    				d.go(d);	// ¿Es esto legal?
    			}
    			void go (Animal a){ }
    		}
    		

    No hay problema. El método go() necesita un Animal, y Dog3 IS-A o ES-UN Animal. (Recordemos, el método go() cree que está cogiendo un objeto Animal, por lo que solo le serán preguntadas cosas que un Animal pueda hacer) Por lo que, en este caso, el compilador ensancha la referencia de Dog3 a un Animal, y la invocación tiene éxito. El punto clave aquí es que una referencia ampliada depende de la herencia, en otras palabras de un test IS-A o ES-UN. Por esto, no es legal ensanchar una clase wrapper a otra, ya que las clases wrapper son pares entre sí. Por ejemplo, NO es válido decir que Short ES-UN o IS-A Integer.

  • Sobrecarga cuando Combinamos Widening y Boxing

    Hemos visto las reglas que se aplican cuando el compilador puede ajustar una invocación a un método realizando conversiones simples. Ahora vamos a ver que pasa cuando una conversión es requerida. En este caso el compilador tendrá que ensanchar y entonces hacer autobox al parametro para que se ajuste a lo que se hizo:

    		class WidenAndBox{
    			static void go(Long x ){ System.out.println("Long"); }
    			
    			public static void main (String[] args){
    				byte b = 5;
    				go(b);
    			}
    		}
    		

    Esto causará un error de compilación.

    Por extraño que parezca. ES posible para el compilador realizar una operación de boxing seguida de una operación de enchancamiento para poder ajustar la invocación a un método:

    		class BoxAndWiden{
    			static void go (Object o){
    				Byte b2 = (Byte) o;
    				System.out.println(b2);
    			}
    			
    			public static void main (String[] args){
    				byte b = 5;
    				go(b);
    			}
    		}
    		

    Esto compilará, y producirá la siguiente salida:

    		5
    		

    Aquí vemos lo que ha pasado cuando el compilador, la JVM, va a la línea en la que se invoca el método go():

    • El byte recibe boxing in un Byte.
    • La referencia a Byte es ensanchada a un Object (Ya que Byte hereda de Object).
    • El método go() tiene una referencia a Objeto que actualmente se refiere a un objeto Byte.
    • El método go() realiza un cast a la referencia de Object a una referencia de Byte.
    • El método go() imprime el valor del Byte.
  • Sobrecarga en Combinación con Var-args

    ¿Qué ocurre cuando intentamos combiar var-args junto a widening o boxing en un escenario en el que se emparejan métodos? Vamos a ver lo siguiente:

    		class Vararg{
    			static void wide_vararg(long... x){
    				System.out.println("long...");
    			}
    			static void box_vararg(Integer... x){
    				System.out.println("Integer...");
    			}
    			public static void main (String[] args){
    				int i = 5;
    				wide_vararg(i,i);
    				box_vararg(i,i);
    			}
    		}
    		

    Esto compila y produce:

    		long...
    		Integer...
    		

    Como podemos ver, podemos combinar var-args junto con widening y boxing. Aquí tenemos un resumen para las reglas de sobrecarga en los métodos que usan widening, boxing y var-args:

    • Widening en primitivos usa los métodos con menos argumentos posibles.
    • Usar boxing o var-args de manera individual son compatibles con la sobrecarga.
    • No podemos ensanchar un tipo wrapper a otro. (Falla IS-A).
    • No podemos ensanchar y luego hacer box. (Un int no puede convertirse en un Long)
    • Podemos hacer box y luego ensanchar. (Un int puede convertirse en un Objeto, via INteger).
    • Podemos combinar var-args jnto con widening y boxing.

    Hay aspectos mas dificiles en la sobrecarga, pero estas otras conciernen con los genéricos, que veremos mucho mas adelante.


Hasta aquí todo lo visto sobre las clases con envoltura junto con la sobrecarga (Overloading) y los métodos que aceptan var-args como argumentos.

Sin mas, cualquier corrección o aporte es bienvenido.

Saludos!!!