edutecnica

Differenza tra classe e tipo

   

Quando si introduce una nuova classe con un certo nome (identificatore) si introduce anche un tipo di dato con lo stesso nome. Questo può far pensare che classe e tipo di dato siano la stessa cosa. In pratica ci sono diversi contesti in cui tra le due cose si nota una certa differenza. I linguaggi fortemente tipati sono tali allo scopo di impedire che il programmatore scriva cose scorrette come chiedere ad un oggetto di eseguire un metodo che in realtà non possiede.

class provaTipo{
public static void main (String args []){
   Test t=new Test();
   Object y=new Test();
   t.saluta();
   y.saluta();//ERRORE di compilazione ((Test)y).saluta();
}//fine main
}// fine classe

class Test {
public void saluta(){
System.out.println("ciao");
}
}//fine Test

3/2 //___>1
(double)3/2 //___>1.5

Si vede come entrambe le variabili t ed y ricevono un oggetto di classe Test. La variabile t è dichiarata di tipo Test mentre la seconda è di tipo Object.
L’invocazione y.saluta(); viene respinta dal compilatore anche se y contiene un oggetto che conosce il metodo saluta().
Il compilatore distingue tra il tipo della variabile (Object) e la classe dell’oggetto che in quel momento è contenuto in essa (Test).
La limitazione si giustifica con il fatto che il compilatore adotta sempre per sicurezza ed uniformità l’ipotesi peggiore: cioè che l’oggetto a cui si riferisce x possegga solo i metodi definiti nella classe Object. Per evitare ambiguità di questo genere bisogna entrare nell’ottica che la classe di un oggetto è unica, ed è quella che ha il nome del costruttore che lo ha creato. Può però avere più tipi: quello corrispondente alla classe e tutti i suoi supertipi compreso Object.


Sottotipi e supertipi

   

Dobbiamo aver presente che in un linguaggio orientato agli oggetti esiste sempre una struttura di tipi come si può rappresentare qui in forma esemplificativa.

In uno schema di questo genere si legge che
B è un sottotipo di A
A è un supertipo di B

Si tratta di un ordinamento parziale, perché ci possono essere tipi inconfrontabili tra loro, come A e C oppure B e Test.
In ogni linguaggio dotato della nozione di sottotipo/supertipo vale il principio di sostituibilità.

Principio di sostituibilità: ogni espressione t di tipo T può essere sostituita con un'espressione t' di un sottotipo T' di T.

Ad es. un assegnamento come
x=new Test();
deve anche accettare l'assegnamento
x=new W();
essendo W un sottotipo di Test.
Parafrasando un motto popolare "Dove ci sta il più grande ci può stare anche il più piccolo".

Facendo riferimento alla classe rettangolo R, supertipo della classe quadrato Q (il quadrato è un caso specifico e particolare di rettangolo).

Il tipo della superclasse è un tipo più ampio di quello della sottoclasse
R r;
Q q = new Q(6);
r = q;

I riferimenti a oggetti della sottoclasse possono essere promossi al tipo della superclasse.
R r = new Q(6);


Regole sui tipi

   

Se un oggetto o un espressione è di tipo T, è anche di tipo T' dove T' è un qualsiasi supertipo di T.
Una variabile di tipo T può contenere solo oggetti di tipo T.
Se un parametro di un metodo m è stato dichiarato di tipo T allora nelle invocazioni di m il corrispondente parametro attuale deve essere un'espressione di tipo T.
Esiste un tipo che non ha alcun supertipo (la classe Object) e di cui tutti i tipi sono sottotipi.

Queste regole hanno conseguenze importanti

Si può assegnare ad una variabile di tipo T qualsiasi espressione di tipo T o di un suo sottotipo.
Non si può assegnare ad una variabile di tipo T qualsiasi espressione di un suo supertipo.
Ad una variabile di tipo Object possiamo assegnare qualsiasi espressione: basta che sia un oggetto (e non un valore degli 8 tipi primitivi, ricordando che in Java array e String sono oggetti).
Non si può assegnare ad una variabile di tipo T un oggetto che sia un supertipo di T.


Upcasting/Downcasting

   

Tornando all'esempio iniziale, l'errore di compilazione verrebbe risolto con un casting, cioè con una conversione di tipo.

((Test)y).saluta();//downcasting

Le operazioni di casting hanno la seguente sintassi

(tipoOggetto)espressioneOggetto

Il significato di questa operazione è: valuta espressioneOggetto. Se uno dei suoi tipi è tipoOggetto allora restituisci un riferimento di tipo tipoOggetto all'oggetto risultante. Altrimenti viene sollevata l'eccezione ClassCastException. Ad es.

Object s=(Object)"ciao";

è un'operazione di upcasting cioè di conversione ad un supertipo, ma è raro vi sia la necessità di farla. Molto più frequente è l'operazione di downcasting, come visto prima, perché ci permette di fare operazioni che sarebbero altrimenti proibite dal compilatore.

Per evitare di sollevare un'eccezione ClassCastException si utilizza un operatore per sapere se un oggetto è di un certo tipo o meno.

espressioneOggetto instanceOf tipoOggetto

q instanceof Q //__>true
r instanceof C //__>false

viene valutata l' espressioneOggetto, se il riferimento non è null e la classe dell'oggetto puntato ha tipoOggetto come uno dei suoi tipi allora restituisce true, altrimenti false.

Nel seguente scritto viene valutato l'operatore instanceof assieme al metodo getClass().getName() che permette di individuare il nome della classe cui appartiene un oggetto.

class provaIstanza{
public static void main (String args []){
   A a=new A();
   System.out.println(a.getClass().getName());
   System.out.println(a instanceof A);
}//fine main
}//fine classe
class A{}


Classi involucro (classi wrapper)

   

Le variabili di tipo Object possono ospitare qualsiasi tipo di oggetto esclusi (ovviamente) i non oggetti, cioè i tipi di dati primitivi (numeri, caratteri..etc.) anche se, talvolta, questa possibilità potrebbe essere utile. Per superare questo limite sono messe a disposizione le classi involucro (wrapper class).

tipo primitivo classe involucro boxing unboxing
byte Byte new Byte(byte) byteValue()
short Short new Short(short) shortValue()
int Integer new Integer(int) intValue()
long Long new Long(long) longValue()
char Character new Character(char) charValue()
float Float new Float(float) floatValue()
double Double new Double(double) doubleValue()
boolean Boolean new Boolean(boolean) booleanValue()

Ogni classe involucro consente di mettere in un box un valore primitivo caricandolo in un attributo privato di un oggetto. L'attributo può poi essere osservato tramite un'istruzione nometipoValue().


Boxing/Unboxing

   

Se prendiamo una delle classi precedentemente citate, ad es. Integer, le possibili operazioni che possono essere fatte sono:

Boxing : costruire un box di classe Integer impostando un suo attributo di tipo int al valore di una certa espressione

new Integer(espressioneTipoInt)

Unboxing : estrarre il valore contenuto in un box di tipo Integer

espressioneInteger.intValue()

Downcasting+Unboxing : estrarre il valore contenuto in un box di tipo Object ma di classe di tipo Integer dopo aver fatto un downcasting

((Integer)espressioneObiect).intValue();

class boxUnbox{
public static void main (String args []){
Integer x=new Integer(7);//Boxing
int y=x.intValue();//Unboxing
System.out.println(y);
Object a=new Integer(9);//Boxing
int b=((Integer)a).intValue();//Downcasting+Unboxing
System.out.println(b);
}//fine main
}//fine classe

ma funziona anche senza il metodo intValue()

class boxUnbox{
public static void main (String args []){
Integer x=new Integer(7);//Upcasting
int y=++x;
System.out.println(y);
Object a=new Integer(9);//Boxing
int b=((Integer)a);//Downcasting
System.out.println(b);
}//fine main
}//fine classe

Ad esempio , le misure di temperatura effettuate in varie città sono:

Milano 8 9
Torino 5
Venezia 7 6 7

Si chiede di scrivere un metodo a cui passare un unico array scrivendo l'elenco delle città con la media delle temperature

Milano 8,5
Torino 5,0
Venezia 6,6666

la soluzione potrebbe essere la seguente:

class mediaTemp{
public static void main (String args []){
Object T[]=new Object[]
{"Milano",new Integer(8),new Integer(9),
"Torino",new Integer(5),//boxing di 5
"Venezia",new Integer(7),new Integer(6),new Integer(7)};
int i=0;
while(i<T.length){
System.out.print(T[i++]+" ");
int somma=((Integer)T[i++]).intValue();//unboxing 1°elem.
int n=1;
while(i<T.length && T[i] instanceof Integer){
somma+=((Integer)T[i++]).intValue();//unboxing
n++;//conta il num.di istanze Integer
}//end while
System.out.println( (float)somma/n );
}//end while

}//fine main
}//fine classe