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