Giuso ha scritto:
Come viene determinata la compatibilità dei tipi riferimento in Java? Come è influenzata questa regola dalla struttura delle istanze?
Differenza tra tipo statico e dinamico di un'espressione. Come viene modificato da un cast? In che modo questi tipi definiscono la risoluzione delle chiamate?
Se la classe A estende la classe B, quali sono le variabili e i metodi di B?
A quest'ultima mi viene da rispondere: B ha i suoi metodi e variabili, in più può utilizzare instanze della sottoclasse A in ogni punto del codice in cui possono essere utilizzate le isntanze di B. è giusto?
Alt ... andiamo con ordine.
La "compatibilità" tra i tipi reference innanzitutto dipende ovviamente dalla relazione di ereditarietà che c'è tra due tipi.
Java ha la ereditarietà
singola di classe ed ha la ereditarietà
multipla di interfaccia.
Vuol dire che una classe può solo estendere un'altra classe ma può dichiarare di implementare N interfacce. Inoltre una interfaccia può
estendere N altre interfacce.
La classe Integer ad esempio è:
public final class Integer extends Number implements Comparable<Integer>
mentre Number è:
public abstract class Number implements java.io.Serializable
Quindi: Integer deriva da Number e Number deriva (implicitamente) da Object. Ma Integer è anche Comparable<Integer> ed in più eredita il fatto di essere anche Serializable.
In sostanza le relazioni IS-A ("è-un") sono:
Integer
è-un Number
ma anche
Integer
è-un Object
ma anche
Integer
è-un Comparable
ma anche
Integer
è-un Serializable
Quindi un oggetto Integer si può assegnare ad una variabile di uno di quei 5 tipi: Integer stesso, oppure Number, o Object, o Comparable o Serializable.
Se tu fai:
Object o = new Integer(123);
Il tipo "statico" (nel senso che è noto a livello di compilazione) del reference è Object, questo è il tipo della variabile 'o'. Il tipo "dinamico" (che vuol dire quello noto a
runtime) è Integer.
Sulla variabile 'o' si possono invocare SOLO i metodi noti in Object. Java funziona così, è un linguaggio tipizzato staticamente.
Noi sappiamo che Integer ha il intValue() ma su o direttamente non si può invocare, perché non è di Object. Il compilatore infatti non può "provare" a livello di compilazione che è lecito invocare intValue() su
o. L'oggetto realmente istanziato ed assegnato ad
o potrebbe essere un altro.
Se vuoi poter invocare intValue() si deve fare un cast (un down-cast o "narrowing").
Integer i = (Integer) o;
System.out.println(i.intValue()); // ok
Questo cast è controllato a runtime. La JVM si chiede: l'oggetto referenziato da
o è davvero realmente un Integer? Se sì, allora il cast ha successo e quella assegnazione avviene. Se no, viene lanciato ClassCastException (e l'assegnazione NON avviene).
Il cast NON cambia nulla nell'oggetto in sé. Cambia solo il modo di "vedere" l'oggetto, ovvero cambia il tipo "statico" (noto a livello di compilazione) con cui si referenzia l'oggetto. E' come mettere un paio di occhiali differenti (migliori o peggiori): cambiando occhiali l'oggetto osservato NON cambia in sé .... cambia come lo "vede" chi indossa gli occhiali.