Java OCAJP7: Widening e Unboxing dei tipi nell’overload dei metodi

Nell’esame di certificazione OCAJP ci sono molte domande che testano la conoscenza dei meccanismi di widening e boxing/unboxing dei tipi. Il boxing consiste nella capacità di assegnare implicitamente un tipo primitivo ad una reference della relativa classe wrapper mentre, analogamente, l’unboxing consiste nella capacità di estrarre implicitamente un tipo primitivo (ad esempio int) dal relativo tipo wrapper che lo contiene (Integer).

La funzionalità di boxing/unboxing è stata introdotta con la versione 5.0 della Java SE.

Di seguito un semplice esempio del loro utilizzo:

public class JavaCertification  {

	public static void main(String[] args) {
		Integer a = 10;              // boxing
		Integer b = new Integer(20);
		int c = b;                   // unboxing
		System.out.print(b);         // unboxing
	}
}

Uno dei casi più frequenti di applicazione è quello relativo ai parametri passati ad un metodo del quale ci sono più overload.
Vediamo quindi un po’ di esempi per chiarire questi meccanismi e le precedenze con cui vengono applicati quando il compilatore deve risolvere il corretto overload del metodo da invocare.

Iniziamo con il caso semplice, in cui tutti i tipi sono tipi primitivi. In questo caso viene invocato il metodo che presenta il match esatto del tipo del parametro in input con il tipo passato. Essendo int il tipo di default per i numeri interi, viene invocato il relativo metodo, che produce quindi l’output indicato.

public class JavaCertification  {  
	 	
	public void print(short y) { 
		System.out.println("short: " + y);
	}

 	// c'è il match esatto quindi viene invocato questo
 	public void print(int y) { 
 		System.out.println("int: " + y);
 	}

 	public void print(long y) { 
 		System.out.println("long: " + y);
 	}
		 	 	
	public static void main(String[] args) {  
		JavaCertification tmp = new JavaCertification();
    		tmp.print(10);
    	} 
		    
}

OUTPUT:

int: 10

Nell’esempio seguente, invece, non è presente un overload del metodo in cui il tipo primitivo del parametro di input matcha esattamente con quello passato. In questo caso viene effettuato il cosiddetto widening del tipo, cioè viene “esteso” per cercare di matchare il tipo primitivo più “grande” nella gerachia.

public class JavaCertification  {  
	 	
	public void print(short y) { 
		System.out.println("short: " + y);
 	}

	// non c'è il match esatto con un metodo di overloading
 	// viene fatto il widening del tipo int a long
 	public void print(long y) { 
 		System.out.println("long: " + y);
 	}
		 	
	public static void main(String[] args) {  
		JavaCertification tmp = new JavaCertification();
    		tmp.print(10);
   	 } 
		    
}

OUTPUT:

long: 10

Nell’esempio successivo, il tipo passato come parametro è un double ma nessuno degli overload del metodo accetta un double oppure un tipo primitivo a cui un double può essere esteso. In questo caso otteniamo un errore di compilazione, che in preparazione all’esame OCAJP7 occorre essere in grado di riconoscere.

public class JavaCertification  {  
	 	
	public void print(short y) { 
		System.out.println("short: " + y);
	}

	public void print(int y) { 
		System.out.println("long: " + y);
	}

 	public void print(long y) { 
 		System.out.println("long: " + y);
 	}
		 	
	public static void main(String[] args) {  
		JavaCertification tmp = new JavaCertification();
    		tmp.print(10.0); // double
	} 		    
}

ERRORE DI COMPILAZIONE:

The method print(short) in the type JavaCertification is not applicable 
for the arguments (double)

Il prossimo esempio prevede l’invocazione di un metodo, passando il parametro di un tipo per cui, tra i vari overload del metodo disponibili, ce ne sono diversi che accettano in input un tipo primitivo a cui il tipo passato può essere esteso tramite l’operazione di widening. Nella fattispecie, sia int che long sono tipi “successivi” a short nella catena di widening. In questo caso, viene matchato il primo disponibile, quindi int:

public class JavaCertification  {  	 	

 	// non c'è il match esatto con un metodo che prende un parametro di tipo short
	// risalendo nella catena dei tipo (upcast) il primo utilizzabile è int
 	public void print(int y) { 
 		System.out.println("int: " + y);
 	}

 	public void print(long y) { 
 		System.out.println("long: " + y);
 	}
		 	
	public static void main(String[] args) {  
		JavaCertification tmp = new JavaCertification();
    		short x = 8; 
		tmp.print(x); 
	} 		    
}

OUTPUT:

int: 8

Vediamo cosa succede nel caso in cui, per il tipo utilizzato come parametro nell’invocazione del metodo, siano presenti sia il metodo di overload con accetta lo stesso tipo primitivo che il metodo che accetta il corrispondente oggetto wrapper del tipo primitivo (esempio: int e Integer)

public class JavaCertification  {  
	 	
	// match esatto con il metodo che prende in input il tipo primitivo
	public void print(int y) { 
		System.out.println("int: " + y);
	}

 	public void print(Integer y) { 
 		System.out.println("Integer: " + y);
 	}
		 	
	public static void main(String[] args) {  
		JavaCertification tmp = new JavaCertification();
		tmp.print(10);  // tipo primitivo
	} 
		    
}

OUTPUT:

int: 10

Qui il risultato è piuttosto ovvio ed il metodo che viene invocato è quello che prevede il parametro del tipo primitivo esatto con cui viene invocato.

Il discorso si fa interessante quando non è presente un overload del metodo che accetta il tipo primitivo esatto ma sono presenti un metodo che prende come parametro il tipo primitivo “superiore” e uno che accetta un parametro del tipo della classe wrapper del tipo primitivo. (long ed Integer con chiamata passando int)

public class JavaCertification  {  
	 	
 	// viene fatto l'upcast al tipo primitivo più grande
 	public void print(long y) { 
 		System.out.println("long: " + y);
 	}

 	// NON viene fatto l'autoboxing alla classe wrapper relativa al tipo primitivo
 	public void print(Integer y) { 
 		System.out.println("Integer: " + y);
 	}
		 	
	public static void main(String[] args) {  
		JavaCertification tmp = new JavaCertification();
		tmp.print(10);  // tipo primitivo
	} 
		    
}

OUTPUT:

long: 10

Qui la cosa da notare e da ricordarsi assolutamente in ottica di esame OCPJP è che per i tipi primitivi il widening ha precedenza sull’unboxing. Passando come parametro nella chiamata un valore di tipo primitivo int, viene cioè invocato il metodo che accetta come argomento un tipo primitivo long e non il metodo che accetta un oggetto della classe wrapper Integer.

Nel prossimo esempio vediamo il caso inverso in cui il metodo viene invocato passando come parametro una reference di tipo Integer e il metodo prevede 2 overload, uno che accetta un parametro primitivo int ed uno che accetta una reference di tipo Long.

public class JavaCertification  {  
	 	
	// viene fatto l'autoboxing da Integer a int
	public void print(int y) { 
		System.out.println("int: " + y);
	}

 	// NON viene fatto l'upcast al wrapper del tipo più grande
 	public void print(Long y) { 
 		System.out.println("Long: " + y);
 	}
		 	
	public static void main(String[] args) {  
		JavaCertification tmp = new JavaCertification();
		tmp.print(new Integer(10)); // oggetto tipo wrapper
        } 		    
}

OUTPUT:

int: 10

In questo caso non esiste il widening dei tipi wrapper da Integer a Long, in quanto Integer e Long non condividono nessuna relazione gerarchica (derivano entrambi da Number, ma non sono legati tra loro da una relazione di ereditarietà) per cui viene effettuato l’unboxing dell’oggetto della classe wrapper nel relativo tipo primitivo.

Un’ulteriore cosa importante da notare è che il compilatore, nella ricerca dell’overload del metodo da matchare esegue una sola tra le operazioni di widening e unboxing, ma non le può eseguire entrambe sul tipo del parametro passato al metodo.
Vediamo un anche questo esempio:

public class JavaCertification  {

           public void print(Long y) {
                   System.out.println("Long: " + y);
           }

           public static void main(String[] args) {
                   JavaCertification tmp = new JavaCertification();
                   tmp.print(10);  // tipo primitivo
           }

}

ERRORE DI COMPILAZIONE:

The method print(Long) in the type JavaCertification is not applicable for the arguments (int)

In questo caso il metodo viene invocato con un valore int, ma l’unica sua definizione accetta come argomento un Long. La trasformazione da int a Long prevederebbe il widening a long e poi il boxing a Long ma come detto, il compilatore non esegue entrambe le operazioni insieme. Otteniamo quindi un errore in fase di compilazione che ci dice che non viene trovato nessun metodo che prevede un parametro del tipo che gli stiamo passando.

This entry was posted in $1$s. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *