Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
I corsi di Scienze Matematiche Fisiche e Naturali
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Marco Faella » 3.Il sistema dei tipi


Il sistema dei tipi

Il inguaggio Java è staticamente tipato

  • “tipato” vuol dire che ad ogni espressione viene assegnato un tipo, che rappresenta l’insieme di valori che l’espressione potrebbe assumere;
  • “staticamente” vuole dire che il tipo di ogni espressione deve essere noto al momento della compilazione.

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”.

I tipi Java

Java prevede i seguenti tipi:

  • I tipi base.
  • I tipi riferimento.
  • I tipi array.
  • Il tipo speciale “nullo”.

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”.

Esempio di type checking

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?

Esempio di type checking (segue)

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).

La relazione di sottotipo

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:

  • T è sottotipo di se stesso;
  • T è sottotipo di Object;
  • Se T estende U oppure implementa U, T è sottotipo di U;
  • Il tipo nullo è sottotipo di T;
  • Se T è sottotipo di U allora T[] è sottotipo di U[].

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.

L’operatore instanceof

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à

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

  • T è sottotipo di U, oppure.
  • T ed U sono tipi base e c’è una conversione implicita da T ad U.

Array e controllo dei tipi

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.

Conversioni esplicite di tipo: cast

Java permette alcune conversioni esplicite di tipo tramite cast.

Cast tra tipi base:

  • si può utilizzare un cast per effettuare esplicitamente una promozione
    • in questo caso il cast è superfluo;
  • si può utilizzare un cast per effettuare una promozione al contrario
    • ad esempio, per convertire un double in un int;
    • in questi casi, è facile incorrere in perdite di informazioni;
    • ad esempio, nel passaggio da numeri in virgola mobile a numeri interi, si può perdere sia in precisione che in magnitudine (ordine di grandezza).

Cast tra riferimenti

Sono consentiti dal compilatore i seguenti cast tra un tipo riferimento (o array) A ad un tipo riferimento (o array) B:

  • se B è supertipo di A
    • si chiama “upcast”;
    • è superfluo, perché i valori di tipo A sono di per sé assegnabili al tipo B;
  • se B è sottotipo di A
    • si chiama “downcast”;
    • al run-time, la JVM controlla che l’oggetto da convertire appartenga effettivamente ad una sottoclasse di B;
    • in caso contrario, viene sollevata l’eccezione ClassCastException;
    • si deve cercare di evitare i downcast, perché aggirano il type checking svolto dal compilatore;
    • a tale scopo, i tipi parametrici introdotti da Java 1.5 possono aiutare;
    • se proprio si deve usare un downcast, esso andrebbe preceduto da un controllo instanceof, che assicuri la correttezza della conversione.

Non sono possibili cast se tra A e B non vi è nessuna relazione di sottotipo tra i due.

Esercizio

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));

}

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion