Il Poliformismo in Java – Parte 3

      Nessun commento su Il Poliformismo in Java – Parte 3

java-logo

Poliformismo Ad-Hoc: Overloading 

Lo stesso nome di metodo può essere “sovraccaricato” per manifestare diversi comportamenti, all’interno di una classe o catena d’eredità, i metodi di cui si fa l’overlaoding devono essere distinguibili per numero e/o tipi di parametri passati in ingresso, inoltre NON è possibile che due metodi differiscano solamente per il tipo restituito.

Esempio

<br />public class MyClass { public void myMethod()<br /><br />{ ... } // YES!!!<br /> public void myMethod(Object obj)<br /><br />{ ... } // YES!!!<br /> public void myMethod(String s)<br /><br />{ ... } // YES!!!<br /> public void myMethod(Object obj, int v)<br /><br />{ ... } // YES!!!<br /> public int myMethod(Object obj, int v)<br /><br />{ ... } // NO!!! }<br />

Una cosa molto interessante è che anche  i costruttori possono essere “sovraccaricati”, per richiamare un costruttore dentro un altro costruttore si usa il costrutto this( ) con i relativi parametri, questo meccanismo può ovviare alla mancanza dei parametri di default

<br />public class MyClass {<br /> public void MyClass(int a, int b)<br /><br />{... }<br /> public void MyClass () {<br /><br />this(0,0) ;<br /><br />}<br /><br />}<br />

Poliformisomo Ad-Hoc: Coercion 

La coercion consiste nella conversione, esplicita o implicita, del tipo di un oggetto perchè sia possibile il suo utilizzo in contesti definiti in termini di un altro tipo, è  un’operazione che potrebbe essere in alcuni casi poco sicura (per errori di conversione). Java prevede un meccanismo molto restrittivo di coercion implicita per i tipi primitivi, con possibilità di casting esplicito.

Per i tipi primitivi è prevista la conversione implicita verso l’alto (upcasting), un metodo scritto in termini di double può essere utilizzato con tutti i tipi primitivi. Questo non significa che sempre si conservi tutta l’informazione!

Schermata 2014-03-22 alle 18.22.42

Es

<br />public static void moo(float d){<br />System.out.println(d);<br />}<br />public static void main(String [] args){<br />    moo(1);//stampa: 1.0<br />    moo('c');//stampa: 99.0<br />    moo(1234567891234578910L); //stampa: 1.23456794E18, diverso!!<br />    moo(1.2)//NON COMPILA!!<br />}<br />

Inoltre è possibile effettuare il cast esplicito verso il basso tra tipi primitivi, e per gli oggetti lungo la catena d’eredità o da/a interfacce, il downcasting può causare errori in runtime se l’oggetto non è compatibile con la variabile a cui viene assegnato
ES

<br />int i = (int) 5.2;//troncamento<br />Object obj = "chutney";<br /> String str = (String) obj;//OK<br />Object[] objArray = new String[6];<br />String[] strArray = (String[])objArray;//OK<br /> strArray = (String[])new Object[5];//NO!!<br />
  • Per quanto riguarda gli operatori predefiniti si ha un misto di coercion e overloading
  • Gli operatori sono definiti per tutti i tipi primitivi
  • In presenza di tipi eterogenei, viene applicato applicato l’operatore del tipo “superiore” effettuando l’upcasting sul tipo “inferiore”
  • L’operatore +, ridefinito anche per le stringhe, esegue la conversione a stringa se uno dei due operandi è una stringa
  • In presenza di oggetti, la conversione viene fatta richiamando toString sull’oggetto
  • Abbiamo detto che a una variabile di un tipo possono essere assegnati oggetti di un sottotipo (sottoclasse o stessa interfaccia)
  • Il tipo della variabile è dunque potenzialmente diverso dal tipo dell’oggetto contenuto!
  • Da qui nasce l’esigenza di meccanismi di risoluzione del tipo (binding)
  • Se il tipo viene determinato in fase di compilazione (binding statico), il tipo sarà necessariamente quello della variabile
  • Solo in tempo di esecuzione è infatti possibile determinare il tipo dell’oggetto contenuto

Binding Statico e Dinamico 

 

Il binding statico consiste nella determinazione del tipo di un oggetto in fase di compilazione, basandosi sul tipo della variabile che referenzia l’oggetto, col  binding dinamico invece il tipo dell’oggetto è determinato durante l’esecuzione, la presenza del binding dinamico consente di sfruttare il meccanismo di overriding . Implementato con una ricerca nella gerarchia di classi, il binding dinamico si paga con un overhead alla chiamata dei metodi.

In Java è implementato il binding dinamico esclusivamente nella chiamata a metodi d’istanza, per slot, metodi statici e slot statici si ha invece binding di tipo statico.La sovrascrittura di un metodo d’istanza darà dunque luogo a overriding dello stesso, la sovrascrittura di uno slot o di un metodo statico dà invece luogo a shadowing dello slot/metodo definito nella superclasse.

Eredità: Overriding 

Si ha overriding quando una classe derivata sovrascrive un metodo d’istanza già definito lungo la catena d’eredità (stesso nome, stessi parametri), quando un metodo overridden viene richiamato (anche in altri metodi definiti nella superclasse), viene eseguito il codice relativo al tipo attuale dell’oggetto o il più vicino nella catena di eredità verso l’alto.

 

class B {
 public void m(){
       System.out.println("B");
     }
}

class D extends B{ public void m(){

System.out.println("D");

}

}

class D1 extends D{
 public static void main(String[] args){

B b = new D1();
      b.m();//cosa stampa?
    }
}

 

Quando si sovrascrive uno slot in Java si ha un effetto un po’ diverso da quello che si ha in caso di overriding infatti lo slot della superclasse continua ad esistere e a far parte dello stato dell’oggetto. È possibile accedere allo slot della superclasse attraverso il riferimento super, tutto il codice della superclasse che si riferisce a uno slot continuerà a riferirsi  a quello originale.

• Per sfruttare l’overriding si può evitare di riferirsi agli slot direttamente, ma utilizzando degli opportuni metodi (setter e getter)

 

class B {

char a = 'B';

}

class D extends B{

char a = 'D'

}

class D1 extends D{
 public static void main(String[] args){

B b = new D1();
      System.out.println(b.a);//cosa stampa?
    }
}

 

Metodo Dispatching 

Java, come la maggioranza dei linguaggi a oggetti, implementa il dispatching singolo: i metodi appartengono agli oggetti su cui vengono chiamati, è scelto il metodo più “vicino” appartenente alla classe, risalendo la catena d’eredità in base al numero e/o al tipo del/i parametro/i di ingresso, l’oggetto è determinato dinamicamente grazie al binding dinamico, i parametri sono invece determinati staticamente, in caso di overloading all’interno della catena d’eredità si potrebbero avere situazioni non chiarissime!

<div>
<div title="Page 42">
<div>
<div>

public class ProvaDispatching{ class A {

public void m(DDD d){
System.out.println("eDDD");

}

public void m(D d){
System.out.println("eD");

</div>
</div>
</div>
</div>
<div>

   }

}
<div title="Page 42">

class AA extends A{

public void m(DD d){
<pre> System.out.println("eDD");
  }
}</pre>
<div title="Page 42">

class D {} class DD extends D{} class DDD extends DD{}
<div title="Page 42">
<pre>public static void main(String [] args){
    A a = new AA(); 
    D d = new DD();
    a.m(d); //stampa: eD 
   }
}</pre>
</div>
</div>
</div>
</div>