Il inguaggio Java è staticamente tipato
In tal modo, il compilatore è in grado di controllare che tutte le operazioni (compresa, ad esempio, l’assegnazione) siano applicate ad operandi compatibili.
Questa fase della compilazione è chiamata appunto “type checking”.
Java prevede i seguenti tipi:
I tipi base sono già stati introdotti nella lezione 1.
I tipi riferimento sono quelli definiti dal nome di una classe, interfaccia o enumerazione.
I tipi array sono tipi composti, ovvero si definiscono a partire da un altro tipo, detto tipo “componente”, e si individuano sintatticamente per l’uso delle parentesi quadre.
Il tipo nullo prevede come unico valore possibile la costante “null”.
Proviamo a simulare la fase di type checking del compilatore Java sul seguente frammento di codice:
int n;
double d;
d = (new Object()).hashCode() + n/2.0;
Cominciamo dalla parte destra dell’assegnazione e procediamo dalle sottoespressioni più piccole via via fino all’intera parte destra.
La prima espressione base che incontriamo è “new Object()”
questa espressione è per definizione di tipo “riferimento alla classe Object”, o per brevità, di tipo “Object”.
Passiamo poi all’espressione “(new Object()).hashCode()”
in questo contesto il punto denota la chiamata ad un metodo
quindi, il tipo dell’espressione è quello di ritorno del metodo, in questo caso “int”.
Esercizio: quali altri significati del punto ci sono in Java?
int n;
double d;
d = (new Object()).hashCode() + n/2.0;
L’espressione “n” ha chiaramente tipo “int”, mentre “2.0″ è una costante di tipo “double”.
Quindi, i due operandi della divisione “n/2.0″ hanno tipo diverso; il primo operando viene quindi promosso da int a double tramite conversione implicita (si veda la lezione 1), complessivamente, la divisione ha a sua volta tipo double.
Infine, per lo stesso motivo la somma avrà tipo double.
A questo punto, il compilatore verifica che il tipo calcolato per il lato destro dell’assegnazione sia compatibile (si veda dopo) con il tipo del lato sinistro (d).
In questo caso, i due tipi coincidono esattamente.
Per completezza, anche l’intera assegnazione ha tipo “double”; questo consente di concatenare le assegnazioni, come in a=b=c.
Le regole di type checking sono specificate nel capitolo 15 della definizione del linguaggio Java (Java Language Specification).
Per permettere il polimorfismo (in particolare, la capacità di un riferimento di puntare ad oggetti di tipo diverso), esiste una relazione binaria tra tipi, chiamata relazione di sottotipo.
La relazione di sottotipo è definita dalle seguenti regole, in cui T ed U rappresentano tipi arbitrari, esclusi i tipi base:
La relazione di sottotipo non coinvolge i tipi base.
È facile verificare che la relazione di sottotipo è riflessiva, antisimmetrica e transitiva
pertanto, essa è una relazione d’ordine sull’insieme dei tipi (non base).
Queste regole non tengono conto dei tipi parametrici introdotti da Java 1.5, di cui si parlerà nelle successive lezioni.
La relazione di sottotipo permette di definire precisamente il comportamento dell’operatore instanceof.
Data un’espressione exp ed il nome di una classe o interfaccia T, l’espressione
exp instanceof T
restituisce vero se e solo se il tipo effettivo di exp non è nullo ed è sottotipo di T.
La relazione di compatibilità (o assegnabilità) tra tipi stabilisce quando è possibile assegnare un valore di un certo tipo T ad una variabile di tipo U.
Si dice che T è assegnabile ad U se
In base alla definizione di sottotipo, un array di qualunque tipo è sottotipo di “array di Object”
Questo consente, ad esempio, di passare qualunque array ad un metodo che abbia come parametro formale un array di Object
D’altra parte, in conseguenza di questa possibilità possono sorgere problemi al run-time, come mostra il seguente esempio:
A[] arr = new A[10];
Object[] oarr = arr;
oarr[5] = new Object();
A a = arr[5];
L’esempio risulta corretto per il compilatore.
Tuttavia, nell’ultima istruzione assegnamo alla variabile “a” un oggetto di tipo “Object”, senza nemmeno dover fare una conversione esplicita (cast).
In effetti, al run-time viene sollevata un’eccezione (ArrayStoreException) al momento della terza istruzione.
Questo perché al run-time gli array “ricordano” il tipo con il quale sono stati creati.
La JVM utilizza questa informazione per controllare che gli oggetti inseriti nell’array siano sempre di tipo compatibile con quello dichiarato in origine.
Java permette alcune conversioni esplicite di tipo tramite cast.
Cast tra tipi base:
Sono consentiti dal compilatore i seguenti cast tra un tipo riferimento (o array) A ad un tipo riferimento (o array) B:
Non sono possibili cast se tra A e B non vi è nessuna relazione di sottotipo tra i due.
Sapendo che sia la classe C sia la classe B estendono A, effettuare il type checking del seguente codice, evidenziando errori di tipo, cast non validi, ed anche cast validi ma potenzialmente pericolosi al run-time
boolean f(A a, B b) {
C c = (C) a;
A a1 = (A) b;
Object o = a;
A[] arr = new A[10];
A[5] = (Object) a;
A[6] = b;
return (a.equals(b) || b.equals(a));
}
4. Risoluzione dell'overloading e dell'overriding
5. Controllo di uguaglianza tra oggetti
6. Classi interne, locali ed anonime
7. Iteratori, teoria e pratica
8. Clonazione di oggetti. Confronto tra oggetti.
9. Elementi di programmazione di interfacce grafiche
10. Il paradigma Model-View-Controller. Il pattern Strategy
11. I pattern Composite e Decorator
12. I pattern Template Method e Factory Method
13. Classi e metodi parametrici
14. La libreria Java Collection Framework: le interfacce Iterable, ...
15. La libreria Java Collection Framework: la classe HashSet e le l...
16. Parametri di tipo con limiti
17. L'implementazione della programmazione generica: la cancellazio...
18. La riflessione
19. Introduzione al multi-threading
22. Classi enumerate