[Java] Dubbio sulla lettura del codice
Salve a tutti. Ho dei dubbi riguardo ad alcuni esercizi per l'esame di programmazione 2. Nella fattispecie, ci vengono dati dei pezzi di codice e noi dobbiamo dirne l'output.
Problema: ho poca dimistichezza con la "linea di lettura" in Java... Dato che sono reduce da 5 anni di superiori con il Pascal e 1 anno di C++ non Object oriented all'università sono abituata a pensare in maniera troppo lineare.
Esercizio 1
------------
L'output è 10.
Esercizio 2
------------
L'output è 12
Diciamo che io arrivo a dire:
1. Cerco il main e vedo cosa succede.
2. Ogni volta che c'è una new, invoco il costruttore corrispondente.
3. Se istanzio una sottoclasse, prima chiamo il costruttore void della superclasse (a meno che una chiamata super non indichi altro), poi quello della sottoclasse e infine stampo i risultati dei costruttori a cascata.
Nel primo esercizio non mi è chiaro perché la System.out.println(x) dovrebbe stampare 10. Se invoco new B() all'interno del costruttore di A, non dovrei vedere solo il 12?
Nel secondo invece, il perché del 2 mi è chiaro, ma l'1 no. Se io faccio un cast ed assicuro al compilatore che z è di tipo A, non dovrebbe essere invocato il metodo int k di A?
Qualcuno mi sa spiegare di preciso come dovrei fare per leggere il codice nell'ordine giusto in modo da arrivare a questi risultati? Grazie in anticipo.
Problema: ho poca dimistichezza con la "linea di lettura" in Java... Dato che sono reduce da 5 anni di superiori con il Pascal e 1 anno di C++ non Object oriented all'università sono abituata a pensare in maniera troppo lineare.
Esercizio 1
------------
package uno; public class A { int x=10; A(){int x=12; new B();} public static void main(String args[]){ int x=11; new A(); } class B{ B() {System.out.println(x);} } }
L'output è 10.
Esercizio 2
------------
package uno; public class A { void f(int k) { System.out.print(k*3); } public static void main (String args[]){ Object z = new B(); if (z instanceof uno.A) ((A) z).f(1); if (z instanceof uno.B) ((B) z).f(2); } } class B extends A{ void f(int k) { System.out.print(k); } }
L'output è 12
Diciamo che io arrivo a dire:
1. Cerco il main e vedo cosa succede.
2. Ogni volta che c'è una new, invoco il costruttore corrispondente.
3. Se istanzio una sottoclasse, prima chiamo il costruttore void della superclasse (a meno che una chiamata super non indichi altro), poi quello della sottoclasse e infine stampo i risultati dei costruttori a cascata.
Nel primo esercizio non mi è chiaro perché la System.out.println(x) dovrebbe stampare 10. Se invoco new B() all'interno del costruttore di A, non dovrei vedere solo il 12?
Nel secondo invece, il perché del 2 mi è chiaro, ma l'1 no. Se io faccio un cast ed assicuro al compilatore che z è di tipo A, non dovrebbe essere invocato il metodo int k di A?
Qualcuno mi sa spiegare di preciso come dovrei fare per leggere il codice nell'ordine giusto in modo da arrivare a questi risultati? Grazie in anticipo.
Risposte
Per prima cosa non devi preoccuparti più di tanto se hai difficoltà con questi esercizi in quanto sono fatti apposta per mettere alla prova la conoscenza del linguaggio. I costruttori, nonostante l'idea principale sembri abbastanza semplice, nascondono una marea di insidie ed è meglio limitare al minimo il loro uso (nel senso che è meglio limitare al massimo il codice al loro interno).
Nel primo esercizio, il punto principale consiste nella differenza tra classi interne e classi interne locali (ho qui qualche problema in realtà con l'italiano perché non ho imparato Java da fonti inglese - i termini inglesi sono inner class e local inner class). Le classi interne possono accedere ai campi della classe all'interno del quale sono state definite, mentre quelle locali possono anche accedere alle variabili locali. In questo caso, B è dichiarata all'interno della classe e non ha importanza se è stata creata in un metodo, non ha infatti alcun accesso alle variabili locali di quel metodo. Diverso sarebbe stato il discorso nel quale B fosse stata dichiarata nel modo seguente:
Nota che ho dovuto aggiungere il modificatore final. Se vuoi saperne di più ti consiglio di cercare l'argomento sul tuo manuale che ti fornirà molte più informazioni di quanto non abbia fatto io.
Nel secondo caso invece posso solo dirti che B è sempre anche di tipo A e il cast non ha alcun effetto se no limitare l'accesso ai metodi e campi di B a quelli anche definiti in A. Non credo che sia possibile chiamare il metodo di A. Di certo non esiste alcun metodo facile per farlo perché potrebbe mettere la sottoclasse in uno stato non valido nel caso generale.
Nel primo esercizio, il punto principale consiste nella differenza tra classi interne e classi interne locali (ho qui qualche problema in realtà con l'italiano perché non ho imparato Java da fonti inglese - i termini inglesi sono inner class e local inner class). Le classi interne possono accedere ai campi della classe all'interno del quale sono state definite, mentre quelle locali possono anche accedere alle variabili locali. In questo caso, B è dichiarata all'interno della classe e non ha importanza se è stata creata in un metodo, non ha infatti alcun accesso alle variabili locali di quel metodo. Diverso sarebbe stato il discorso nel quale B fosse stata dichiarata nel modo seguente:
public class A { int x=10; A(){ final int x=12; class B { B() {System.out.println(x);} } new B(); } public static void main(String args[]){ int x=11; new A(); } }
Nota che ho dovuto aggiungere il modificatore final. Se vuoi saperne di più ti consiglio di cercare l'argomento sul tuo manuale che ti fornirà molte più informazioni di quanto non abbia fatto io.
Nel secondo caso invece posso solo dirti che B è sempre anche di tipo A e il cast non ha alcun effetto se no limitare l'accesso ai metodi e campi di B a quelli anche definiti in A. Non credo che sia possibile chiamare il metodo di A. Di certo non esiste alcun metodo facile per farlo perché potrebbe mettere la sottoclasse in uno stato non valido nel caso generale.
di solito in questi esercizi è anche comodo disegnarsi cosa accade nella memoria. Dato che java ha una suddivisione della memoria a classi è comodo ragionare a Stack, cioè disegnara prorio la parte di memoria con le istanze degli oggetti e identificatori.
attenta che questo accade se c'è ereditarietà.
"~Rose":
3. Se istanzio una sottoclasse, prima chiamo il costruttore void della superclasse (a meno che una chiamata super non indichi altro), poi quello della sottoclasse e infine stampo i risultati dei costruttori a cascata.
attenta che questo accade se c'è ereditarietà.

In effetti ho notato che per quanto corti e all'apparenza stupidi questi esercizi riescono a farti perdere anche un bel pò di tempo. Direi che adesso ho capito. Se puoi darmi ancora un aiutino, ho notato che in tutti i testi degli anni passati c'è sempre un esercizio che forza la VM a chiamare il Garbage Collector, ma in questo in particolare, non mi è chiaro cosa il GC dovrebbe eliminare:
L'output è +10+21+22-21+23-22-23
Fino al +22 mi è tutto chiaro, il problema viene dopo. La System.gc(); per caso forza la chiamata alla finalize di C? Ma di preciso, che oggetti inutilizzati dovrebbe eliminare la gc? Nessuno è stato messo esplicitamente a null quando effettuo nel main la prima chiamata. Quando invece metto a=null, che finalize dovrebbe allora invocare?
Ho provato a cercare ovunque il funzionamento della System.gc() ma non mi risulta che debba invocare funzioni, solamente eliminare riferimenti ad oggetti che non servono più. Sapresti illuminarmi sul come "vado a finire" nella protected void finalize() ?
Sulla base di questi 3 esercizi cercherò poi di capire meglio gli altri da sola.
package uno; public class A { C c=null; public A() { System.out.print("+10"); C c1=new C(); c=new C(); } public static void main(String[] args) { A a = new A(); System.gc(); new C(); a = null; System.gc(); } } class C { static int count=0; String name=null; public C() { name="2"+(++count); System.out.print("+"+name); } protected void finalize() { System.out.print("-"+name); } }
L'output è +10+21+22-21+23-22-23
Fino al +22 mi è tutto chiaro, il problema viene dopo. La System.gc(); per caso forza la chiamata alla finalize di C? Ma di preciso, che oggetti inutilizzati dovrebbe eliminare la gc? Nessuno è stato messo esplicitamente a null quando effettuo nel main la prima chiamata. Quando invece metto a=null, che finalize dovrebbe allora invocare?
Ho provato a cercare ovunque il funzionamento della System.gc() ma non mi risulta che debba invocare funzioni, solamente eliminare riferimenti ad oggetti che non servono più. Sapresti illuminarmi sul come "vado a finire" nella protected void finalize() ?
Sulla base di questi 3 esercizi cercherò poi di capire meglio gli altri da sola.
"~Rose":
Ho provato a cercare ovunque il funzionamento della System.gc() ma non mi risulta che debba invocare funzioni, solamente eliminare riferimenti ad oggetti che non servono più. Sapresti illuminarmi sul come "vado a finire" nella protected void finalize() ?
la System.gc() forza la chiamata del garbage collector, eliminando i riferimenti non più attivi da indentificatori (tipo eliminando il vecchio riferimento di a).
Il metodo finalize() serve per aggiungere delle funzioni opzionali assieme al GC, se si utilizza SOLO System.gc() non è detto che finalize() venga chiamato, per varie motivazioni; per esser sicuri che ci sia la sua esecuzioni bisogna esplicitarlo con System.runFinalize() (da aggregare alla chiamata del GC).
D'accordo, ma allora se in questo esercizio non si chiama la System.runFinalize(), potresti dirmi le varie motivazioni per cui potrebbe venire chiamata senza esplicitazione? Come libro di riferimento ho Thinking In Java 3edition ma più di dire che il garbage collector pulisce la memoria non dice nulla... così come le API di Java non danno ulteriori spiegazioni al riguardo (forse non sono nemmeno necessarie ma vorrei capire!)
"~Rose":
D'accordo, ma allora se in questo esercizio non si chiama la System.runFinalize(), potresti dirmi le varie motivazioni per cui potrebbe venire chiamata senza esplicitazione? Come libro di riferimento ho Thinking In Java 3edition ma più di dire che il garbage collector pulisce la memoria non dice nulla... così come le API di Java non danno ulteriori spiegazioni al riguardo (forse non sono nemmeno necessarie ma vorrei capire!)
il bello di Java è che non dobbiamo preoccuparci della pulizia e degli identificatori non deallocati.
Ci pensa il GC a pulire quando vuole lui e quando ritiene necessario è una componente ATTIVA della VM, non si disattiva ma si mette in wait.
La System.gc() la richiama assieme al metodo finilize() se dichiarato nella classe (che non è più riferita). Ma quest'ultimo non viene attivato se ad esempio il programma si conclude nel momento della chiamata del GC, cioè non attende la sua esecuzione, invece se c'è la runFinilize() è garantita.
In questo semplice esercizio, dove è dichiarata la finalize(), tieni conto che è eseguita quando è richiamato il GC.
Ti scrivo alcune istruzioni come avvengono, così forse ti charisce cosa accade.
variabili statiche: count=0
identificatore c(a)
print +10
istanza c1=C
identificatore name (c1)
count=1
c1.name="21"
print +21
identificatore c=C
count=2
c.name="22"
print +22
richiamo GC e finalize()
GC: valuta che l'istanza di c1=C non è più riferita (era locale in A()) perciò vedi che viene chiamato c1.finalize() perciò il name riferito dal metodo è quello di c1.
print -21
solo arrivato nel main a:
A a = new A(); System.gc();
il resto lo lascio a te, vedi se ti è più chiaro con le istruzioni "srotolate" (non mi ricordo il termine corretto).

Direi che sei stato più che esauriente, ti ringrazio di cuore! Ora mi è chiaro e finalmente ho capito perché su un altro esercizio un certo output non veniva prodotto! ("srotolate" si addice molto
)
Grazie ancora! (avessimo professori così sarebbe tutta un'altra cosa!)

Grazie ancora! (avessimo professori così sarebbe tutta un'altra cosa!)