Pagina 1 di 2 1 2 ultimoultimo
Visualizzazione dei risultati da 1 a 10 su 13

Discussione: Downcasting di oggetti

  1. #1

    Downcasting di oggetti

    Salve, ho qualche problema nel mettere a fuoco l'argomento del downcasting di oggetti...

    Ho scritto queste tre semplici classi.
    codice:
    public class Superclasse {
    	public void stampa() {
    		System.out.println("Sono la superclasse");
    	}
    	public void superclass() {
    		;
    	}
    }
    codice:
    public class Sottoclasse extends Superclasse {
    	@Override
    	public void stampa() {
    		System.out.println("Sono la sottoclasse");
    	}
    	public void subclass() {
    		;
    	}
    }
    codice:
    public class Programma {
    	public static void main(String args[]) {
    		Superclasse superclasse = new Superclasse();
    		Sottoclasse sottoclasse = new Sottoclasse();
    		// upcasting, tutto ok
    		// superclasse = sottoclasse;
    		// superclasse.stampa();
    		
    		// downcasting (commentando l'upcasting) causa Exception in runtime
    		sottoclasse = (Sottoclasse)superclasse;
    		// e non posso invocare stampa() della superclasse
    	}
    }
    Come si legge dai commenti, mentre l'upcasting va tranquillo, il downcasting no, come se non ci fosse ereditarietà tra le classi coinvolte.
    L'obiettivo dell'esercizio sarebbe invocare stampa() di Superclasse mediante un downcasting, che mi risulta lecito se c'è ereditarietà, ma a quanto pare mi sfugge qualcosa...

    Anche scrivendo
    codice:
    (Sottoclasse)superclasse).stampa();
    non risolvo, sebbene sia una istruzione presa di sana pianta da materiale didattico Oracle!

  2. #2
    Moderatore di Programmazione L'avatar di LeleFT
    Registrato dal
    Jun 2003
    Messaggi
    17,320
    Ed è corretto. Tu hai creato un oggetto di tipo "Superclasse" (new Superclasse) non hai creato un oggetto di tipo Sottoclasse referenziandolo con un riferimento di tipo "Superclasse"... di conseguenza il downcast non potrà mai funzionare.

    Ricorda che i cast non modificano gli oggetti, ma solo la loro visibilità... se un oggetto nasce di un tipo, tale rimane per tutta la sua vita... lo si può vedere come oggetti di tipo diverso a seconda dell'ereditarietà, ma non può cambiare tipo.


    Esempio per capire:

    codice:
    Animale a = new Gatto();

    Ho creato un gatto, ma lo sto vedendo come un animale generico. Posso sempre fare un downcast a Gatto perchè l'oggetto è un Gatto... ma non potrò mai fare un downcast a Cane perchè quell'oggetto non è un cane. Quello che hai fatto tu è diverso:

    codice:
    Animale a = new Animale();   // Supponendo Animale concreta, che abbia un senso
    Io qui non posso fare il downcast a nulla: l'oggetto è un Animale non potrà mai diventare per magia un Gatto, un Cane o un Pappagallo...


    Ciao.
    Ultima modifica di LeleFT; 05-07-2017 a 12:41
    "Perchè spendere anche solo 5 dollari per un S.O., quando posso averne uno gratis e spendere quei 5 dollari per 5 bottiglie di birra?" [Jon "maddog" Hall]
    Fatti non foste a viver come bruti, ma per seguir virtute e canoscenza

  3. #3
    Grazie della pronta risposta.

    A questo punto però mi chiedo: se so che potrei avere bisogno di usare dei metodi della classe Gatto, perché lo creo come animale generico per poi dover fare necessariamente il casting? Un certo buonsenso mi porta a dichiarare Gatto come istanza della classe Gatto sapendo che già eredita tutti i metodi concreti e astratti da implementare della classe Animale.

  4. #4
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Quote Originariamente inviata da Gas75 Visualizza il messaggio
    se so che potrei avere bisogno di usare dei metodi della classe Gatto, perché lo creo come animale generico per poi dover fare necessariamente il casting?
    Avere un super-tipo più generico, prendiamo l'esempio di Animale e poi Gatto/Cane/ecc..., è utile per sfruttare il polimorfismo. Pensa ad esempio ad un metodo che riceve (o restituisce) un Animale[] o List<Animale>. Possono contenere qualunque cosa che deriva da Animale e magari il metodo ha bisogno solo di "vedere" gli oggetti come Animale e non come uno dei tipi più specifici.

    Il punto in questi casi è: dato uno scenario in cui hai uno o più oggetti "visti" solo come il super-tipo (Animale), PERCHE' hai bisogno di fare operazioni che sono specifiche delle sotto-classi? Se è un caso particolare, ci sono motivi validi o cose del genere, allora può aver senso. Se invece ti trovi ad aver molto o continuamente bisogno di operazioni specifiche dei sotto-tipi, allora o la tua gerarchia è "disegnata" male oppure stai cercando di fare delle cose che sarebbe meglio risolvere in modo differente.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  5. #5
    Moderatore di Programmazione L'avatar di LeleFT
    Registrato dal
    Jun 2003
    Messaggi
    17,320
    Infatti nessuno mai andrebbe a scrivere un'istruzione come quella che ho scritto io nel mio primo esempio... istruzioni di quel tipo (lecite, sia chiaro!) le trovi solo nei libri didattici, nei manuali, nei tutorial, ecc... sono esempi "accademici" per far comprendere il concetto. Nessuno, nella pratica, li usa mai...

    Quello che succede, invece, è proprio l'esempio citato da andbin: un metodo che riceve un generico oggetto di una superclasse X (o di una interfaccia Y!) perchè a lui non interessa il tipo specifico, ma solo la funzione "generica" comune a tutti i tipi.

    Facciamo un esempio un po' più concreto... il metodo add() dei contenitori grafici di Swing. Al metodo add() non interessa se gli viene passato un JButton, una JLabel, un JList, un JPanel o un qualsiasi altro componente... a lui interessa che gli venga passato un generico "JComponent" perchè sa che qualunque cosa essa sia la tratterà sempre allo stesso modo: andrà a metterla in un determinato punto del contenitore e magari ne sfrutterà caratteristriche comuni (come la posizione assoluta, la dimensione, il bordo, ecc). Quel metodo, quindi, riceverà un JComponent che, di volta in volta, sarà un JButton una JLabel, ecc. Quindi tale metodo potrebbe essere:

    codice:
    public void add(JComponent c) {
       ...
       int height = c.getHeight();
       int width = c.getWidth();
       ...
    }
    Se io gli passo una JLabel, il metodo lavorerà con altezza e larghezza della mia JLabel... se gli passo un JScrollPane lavorerà con altezza e larghezza del mio JScrollPane... ma se io gli ho passato una JLabel lui non può inventarsi di fare un downcast dell'oggetto "c" a JScrollPane... perchè ciò che io gli ho passato non è un JScrollPane.


    Spero di essere riuscito a chiarire almeno in parte l'utilità dell'ereditarietà (ma lo stesso discorso vale, forse ancor più, per il polimorfismo).


    Ciao.
    "Perchè spendere anche solo 5 dollari per un S.O., quando posso averne uno gratis e spendere quei 5 dollari per 5 bottiglie di birra?" [Jon "maddog" Hall]
    Fatti non foste a viver come bruti, ma per seguir virtute e canoscenza

  6. #6
    Il polimorfismo è l'argomento successivo che devo affrontare. Però mi sono/ero impantanato su questa storia delle dichiarazioni "miste" tra oggetti in gerarchia, e mi fa piacere trovare conferma che sono costrutti puramente accademici/didattici, poiché io, se mi serve un Gatto per dargli solo l'attributo/variabile "coda", già lo istanzio come Animale, non come Gatto; però poi negli esercizi (e nei quiz di certificazione!) queste stravaganze di mescolare le carte e fare downcasting ci sono perché lecite in Java, come per venire incontro a un programmatore che ha strutturato male il suo codice.

    Grazie mille a entrambi!
    Concetti immediati ed esposti con padronanza dell'argomento!

  7. #7
    Buongiorno.
    Scusate se torno sull'argomento...
    Con questa modifica il downcasting funziona.
    codice:
    public class Programma {
    	public static void main(String args[]) {
    		Superclasse superclasse = new Sottoclasse();
    		Sottoclasse sottoclasse = new Sottoclasse();
    				
    		// downcasting
    		superclasse = (Superclasse)superclasse;
    	}
    }
    L'obiettivo dell'esercizio sarebbe quello di invocare il metodo di una sottoclasse da un oggetto che è stato upcastato (che funziona) e viceversa, cioè invocare il metodo di una superclasse da un oggetto che è stato downcastato, ma pare che quest'ultimo passaggio non sia possibile per come viene costruito superclasse, che nasce già come Sottoclasse e col downcasting gli tolgo delle specificità ma ciò che era overriden e persiste nell'oggetto resta tale.

  8. #8
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Quote Originariamente inviata da Gas75 Visualizza il messaggio
    L'obiettivo dell'esercizio sarebbe quello di invocare il metodo di una sottoclasse da un oggetto che è stato upcastato (che funziona) e viceversa, cioè invocare il metodo di una superclasse da un oggetto che è stato downcastato, ma pare che quest'ultimo passaggio non sia possibile per come viene costruito superclasse, che nasce già come Sottoclasse e col downcasting gli tolgo delle specificità ma ciò che era overriden e persiste nell'oggetto resta tale.
    Ok, vediamo di chiarire la questione perché secondo me non ce l'hai ancora molto chiara. Usiamo uno scenario reale/sensato.

    codice:
    public class Persona {
        // .....
    }
    
    public class Studente extends Persona {
        // .....
    }
    
    public class Impiegato extends Persona {
        // .....
    }

    Non ho messo nulla nelle classi, perché ai fini del discorso non importa molto.

    A livello di tipi reference una conversione da un sottotipo ad un supertipo non causa mai alcun problema, non implica alcun controllo a runtime (perché il compilatore "sa" già che è corretto) e può sempre essere implicito. Se vuoi esplicitare la conversione con un "up" cast, è lecito ma superfluo.

    codice:
    Studente s = new Studente();
    Persona p1 = s;             // conversione implicita
    Persona p2 = (Persona) s;   // conversione esplicita, lecita ma superflua

    Un Studente è-un Persona, relazione IS-A come si dice in inglese nella OOP. Quindi un riferimento ad un Studente può essere lecitamente passato ovunque sia richiesto un Persona. Non c'è bisogno di un "up" cast esplicito (sebbene non fa alcun male).

    Ora vediamo il contrario.

    codice:
    Persona p = new Studente();
    Studente s = (Studente) p;

    Qui il "down" cast è obbligatorio, questa è una conversione controllata a runtime. Per il compilatore il cast è lecito perchè "è possibile" che una variabile di tipo Persona faccia riferimento ad un oggetto di tipo Studente. Ma il compilatore "non sa" cosa succede a runtime. Quindi è la JVM che deve controllare a runtime in quel punto che l'oggetto referenziato da p sia DAVVERO un Studente.
    Se lo è (e sopra è così), allora la conversione ha successo e tu puoi ri-vedere l'oggetto come Studente.

    codice:
    Persona p = new Impiegato();   // Un Impiegato !!!
    Studente s = (Studente) p;     // FALLISCE con ClassCastException

    Anche qui il cast è di nuovo lecito per il compilatore. Ma la conversione è di nuovo controllata a runtime. L'oggetto referenziato da p è davvero un Studente?? NO!!! Ora p fa riferimento ad un oggetto Impiegato (lecito perché Impiegato è-un Persona).
    Quindi il cast fallisce e a RUNTIME (NON in compilazione!) con un bel ClassCastException.
    Ultima modifica di andbin; 06-07-2017 a 11:08
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  9. #9
    Questa parte mi è chiara, ma grazie per l'approfondimento/riepilogo.
    Il mio dubbio è sulla possibilità di gestire i contenuti degli oggetti dopo questi passaggi.
    Esempio sensato: se Persona ha un certo metodo mangia() (concreto, non astratto), ovviamente comune alle sottoclassi Studente e Atleta dove però viene overridden (perché fanno attività diverse e quindi seguono diete diverse), scrivendo
    codice:
    Persona p = new Studente();
    Studente s = (Studente) p;
    Persona q = new Atleta();
    Atleta a = (Atleta) q;
    ho perso ogni riferimento al metodo mangia() di Persona (come sto verificando) o posso ancora invocarlo in qualche modo che non conosco e che quindi non so applicare?
    Tutto è nato da un esempio, probabilmente sbagliato dato che l'esecuzione viene lasciata al lettore, nel quale si pretendeva di invocare uno stesso metodo in maniera "incrociata" prima tramite upcasting e poi, alternativamente e non successivamente, downcasting.

  10. #10
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Quote Originariamente inviata da Gas75 Visualizza il messaggio
    Questa parte mi è chiara, ma grazie per l'approfondimento/riepilogo.
    Allora probabilmente hai dubbi su overload e/o override.

    Quote Originariamente inviata da Gas75 Visualizza il messaggio
    ho perso ogni riferimento al metodo mangia() di Persona (come sto verificando) o posso ancora invocarlo in qualche modo che non conosco e che quindi non so applicare?
    codice:
    public class Persona {
        public void mangia() {
            System.out.println("mangia di Persona");
        }
    }
    
    public class Studente extends Persona {
        public void mangia() {     // questo è un override !!
            System.out.println("mangia di Studente");
        }
    
        public String getMatricola() {
            return "12345";
        }
    }

    (non guardare tanto che getMatricola torna un valore fisso. Più realmente/sensatamente dovrebbe essere un campo di Studente)

    Dato questo:

    Persona p = new Studente();

    Allora:
    1) Una invocazione
    String m = p.getMatricola();

    NON è possibile. Il compilatore ha in mano solo un reference di tipo Persona e può "vedere" solo i metodi noti in Persona. Il compilatore NON può invocare getMatricola() su un Persona, sebbene l'oggetto realmente istanziato è Studente che HA il getMatricola().

    2) Una invocazione
    p.mangia();

    è perfettamente lecita per il compilatore. La variabile p è di tipo Persona che ha un mangia(). Ma qui entra in gioco il polimorfismo e il concetto di "override". Il metodo eseguito è quello dell'oggetto realmente ISTANZIATO. E l'oggetto è di tipo Studente.
    Quindi il mangia() eseguito è quello di Studente, NON quello di Persona.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.