domingo, 16 de octubre de 2011

Bloques de Inicialización y Bloques estáticos

Este es de esos temas de los que no sabía nada al respecto pero resulto muy sencillo resulta que existen una par de opciones no muy usadas en java y pueden no llegar a ser muy útiles, pero sí muy interesantes.

Bloques de inicialización

public class Bloques {
       {
             //bloque de inicializacion
       }
       public static void main(String[] args) {

       }
}
Un bloque de inicialización es simplemente un par de llaves({}) colocado dentro de la clase (pero no dentro de un método esos son simples bloques), las instrucciones colocadas allí son ejecutadas inmediatamente después de la llamada a super() de nuestro constructor.
public class Bloques {
       public Bloques(){
             System.out.println("Mi Constructor");
       }
       {
             System.out.println("bloque de inicializacion");
       }
       public static void main(String[] args) {
             new Bloques();
       }

}

Esto tendrá como salida
bloque de inicializacion
Mi Constructor

Bloques estáticos

A diferencia de los bloques de inicialización, los bloques estáticos se ejecutan cuando una clase es cargada, no cuando se crea una instancia, El momento en el que una clase es cargada puede variar por ejemplo cuando hablamos de una o varias clases en un archivo esto quiere decir de inmediato antes incluso de ejecutar la primer instrucción de main.
 public class Bloques {
      
       public Bloques(){
             System.out.println("Mi Constructor");
       }
       static{
             System.out.println("bloque estatico 1");
       }
       {
             System.out.println("bloque de inicializacion");
       }
       static{
             System.out.println("bloque estatico 2");
       }
       public static void main(String[] args) {
             System.out.println("main");
             new Bloques();
       }

}

Produce la salida
bloque estatico 1
bloque estatico 2
main
bloque de inicializacion
Mi Constructor

Cuando la clase está en diferente archivo, la clase se carga cuando es mencionada por primera vez, ya sea creado una instancia o llamando a un atributo o método estático de esta.
Por ejemplo:
public class Bloques {
       static int lalala= 10;
       public Bloques(){
             System.out.println("Mi Constructor");
       }
       static{
             System.out.println("bloque estatico 1");
       }
       {
             System.out.println("bloque de inicializacion");
       }
       static{
             System.out.println("bloque estatico 2");
       }
}

Y en otro archivo:
public static void main(String[] args) {
       System.out.println("main");
       Bloques.lalala++;
}
Se imprimirá en pantalla:
main
bloque estatico 1
bloque estatico 2

Notas Finales
  • Los bloques estáticos se ejecutan una sola vez, los de inicialización cada que se crea un objeto nuevo.
  • Pueden existir múltiples bloques (tanto estáticos como de inicialización) en ese caso se ejecutaran en el orden en que aparecen.
  • Los bloques estáticos no deben hacer mención de métodos o atributos de la clase no estaticos.

sábado, 15 de octubre de 2011

El enigma de los métodos sobrecargados y el error de ambigüedad



En el post anterior hice una explicación (no muy buena, lo sé) acerca de los métodos sobrecargados y como elige el compilador que método ejecutar (cuando varios métodos tienen lista de parámetros compatible con los argumentos de la llamada), En el libro de Katy Sierra se hace énfasis en que la mejor manera de aprender lo suficiente para pasar un examen de certificación de un lenguaje de programación es programando mucho, al terminar cada tema me siento frente a mi computadora y juego un poco con los tópicos del tema que leí, agotando mis dudas con el que lo sabe todo: el señor compilador, verifique la información del post anterior con él, pero me salto una duda ¿Qué pasa en un escenario como el siguiente?
public class Sobrecarga {

       void metodo(int n, byte b){
             System.out.println("int byte");
       }
       void metodo(short s, long l){
             System.out.println("short long");
       }
       public static void main(String[] args){
             Sobrecarga sc = new Sobrecarga();
             byte b = 1;
             sc.metodo(b, b);
       }
      
}
¿Qué se imprime en pantalla al ejecutar esto?
NADA, error de compilación, ojo el error no está en la declaración de los métodos, sino con los argumentos de llamada (byte,byte), hablándolo con un compañero de clase llegamos a una conclusión (basada solo en estar jugando con las declaraciones de los parámetros), la cual es: "el compilador se decide primero por el (short long)  en vez de (int byte) pues el primer parámetro es más pequeño  (short < int) pero con el segundo se topa una sorpresa (long >> byte), lo que no le agrada mucho". El punto es que al compilador le debe quedar claro el ”ganador” sin necesidad de algo monstruoso como un backtracking o algo similar.
Esto del error por ambigüedad no es exclusivo de los primitivos, también se puede dar en objetos aunque allí la situación está un poco más clara. Y se solo cuando el argumento de llamada es null.

public class  VarArgs{
       public void metodo(Object o){
             System.out.println("Object");
       }
       public void metodo(String s){
             System.out.println("String");
       }
      
       public void metodo(int... n){
             System.out.println("int...n");
       }
       public  static void main(String [] args){
             VarArgs va = new VarArgs();
             va.metodo(null);
       }
}
null es válido para las tres versiones sobrecargadas del método, aquí aparece un error de compilación.
Para responder ¿Por qué? lo hare con otro ejemplo.
class A{ }
class B extends A{ }

public class  VarArgs{
       public void metodo(Object o){
             System.out.println("Object");
       }
       public void metodo(A a){
             System.out.println("A");
       }
      
       public void metodo(B b){
             System.out.println("B");
       }
       public  static void main(String [] args){
             VarArgs va = new VarArgs();
             va.metodo(null);
       }
}
Aquí no existe ningún error de compilación y se imprime en pantalla
B
Cuando el compilador tiene una llamada a un método (con parámetros con referencias a objetos) en la cual varios métodos pueden responder a ella se ejecutara el que este más debajo de la jerarquía de herencia de clases. Aquí está muy clara
Object
A
B
 Pero en el ejemplo anterior no lo estaba
Object
String        ¿?  int[]
Ese era el motivo precisamente, y por esto solo se da en el caso de null pues es un valor valido para cualquier referencia a objeto.

Métodos sobrecargados

Este tema puede parecer simple pero no lo es tanto, imaginen un escenario como este
class Sobrecarga{
       public void metodo(byte b){ 
             System.out.println("byte");
       }
       public void metodo(int i){ 
             System.out.println("int");
       }
       public  void metodo(long l){
             System.out.println("long");
       }
       public  void metodo(double d){
             System.out.println("double");
       }
}

¿Que se imprimiría en pantalla cuando llamemos a “metodo” con un parámetro de un short?

       Sobrecarga sc  =  new Sobrecarga();
       short  s = 18;
       sc.metodo(s);

La respuesta es
int

¿Pero porque?
Bien, lo primero que trata de hacer el compilador para decidir cuál método ejecutara es buscar la que tenga el parámetro más pequeño tal que el tipo del argumento quepa en él. En este caso la versión del método con el parámetro más pequeño donde quepa un short es la de int,

 ¿Por qué no byte, acaso 18 no cabe en un byte?
Si pero esto se hace en tiempo de compilación eso significa que  si la variable tiene 18, -1 o chorro cientos no importa lo que importa es el tipo de la variable.




Clases de envoltorio


Existe una clase de envoltorio para cada tipo primitivo de java, todas tienen el mismo nombre de su respectivo primitivo pero empiezan con mayúscula (al ser clases), excepto claro por Integer de int y Character de char.

Existen dos grandes y maravillosas características introducidas a estas en la versión 1.5 de java, llamadas unboxing (digamos desenvolver) y autoboxing (¿envolver en automático?).

             int primitivo = 6;
             Integer  envoltura = primitivo; /*autoboxing  (¡se está envolviendo solita!, pasa de ser primitivo a ser un objeto de su envoltorio pues)*/
             primitivo = envoltura; //outboxing (se desenvuelve)

¿Porque vemos esto para métodos sobrecargados?

Porque podemos tener algo como esto:


public class Sobrecarga{
       public void metodo(Byte b){ 
             System.out.println("Byte");
       }
       public  static void main(String[] args){
             Sobrecarga sc = new Sobrecarga();
             byte  prim = 9;
             sc.metodo(prim);
       }
}

Esto imprime en pantalla
Byte

¿Por qué?
Pues porque la variable prim de tipo byte hizo auto-boxing para convertirse en un Byte.

Ahora sí ¿qué prefiere el compilador,  hacer auto-boxing o usar un tipo de mayor tamaño?
public class Sobrecarga{
       public void metodo(Byte b){ 
             System.out.println("Byte");
       }
       public void metodo(long l){ 
             System.out.println("long");
       }
       public  static void main(String[] args){
             Sobrecarga sc = new Sobrecarga();
             byte  prim = 9;
             sc.metodo(prim);
       }
}
Lo anterior imprimirá long
De eso concluyo, advertencia no me consta. “El compilador prefiere desperdiciar bits (recordar que un casting añade bits) a desperdiciar tiempo de ejecución (auto-boxing)". Lo siguiente apoyara mi teoría.


Var-args (Argumentos variables o algo así)

No sé qué tanto tienen existiendo pero no los conocía, los var-args son en realidad arrays (y por lo tanto objetos), pero con flexibilidad a la hora de pasarlos como argumentos.
 Al tener un escenario como este:

public class  VarArgs{
       public void metodo(int... n){
             System.out.println("int...n");
       }
       public  static void main(String [] args){
             VarArgs va = new VarArgs();
             va.metodo();//1
             va.metodo(5);//2
             va.metodo(1,2,5);//3
             va.metodo(null);//4
             va.metodo(new int[]{4,5,6});//5
       }
}

El código anterior no produce ningún error de compilación, los var-args pueden recibir una lista de 0 o más enteros (líneas comentadas con 1,2 y 3), pero como en realidad es un array puede recibir cualquier valor posible para un array (4 y 5). Piensen un momento “¿si yo fuera  compilador me daría flojera dar soporte a esto?”, pues pasa algo así pues volviendo al tema de los métodos sobrecargados el compilador solo escogerá el método con var-args si y solo si no tiene otra opción.
Ejemplo
public class  VarArgs{
       public void metodo(Object o){
             System.out.println("Object");
       }
       public void metodo(int... n){
             System.out.println("int...n");
       }
       public  static void main(String [] args){
             VarArgs va = new VarArgs();
             int i =9;
             va.metodo(i);
       }
}

El código anterior imprimirá
Object
¿Porque?
El compilador prefiere hacer auto-box (pasar de int a Integer) y después pasar la clase de Integer a Object, en vez de poner al solitario int en un array.