Visualizzazione dei risultati da 1 a 8 su 8
  1. #1

    [Java] ObjectReader ma classe non nota a priori?

    Buonasera,
    sono nuovo del forum, quindi se ho sbagliato qualcosa, correggetemi.

    Il problema è il seguente. Come faccio a capire di che classe è un oggetto che prendo da un file??
    La mia idea era di scrivere una classe generica, da poter riutilizzare, che mi consenta lettura e scrittura di oggetti su file.
    Sono partito dalla semplice lettura e scrittura di una classe "Highscore", ed ho cercato poi di astrarla per ottenere il risultato (è l'approccio giusto?)

    Per quanto riguarda la scrittura non ci sono problemi, passo al metodo della classe un oggetto di tipo "X", e lo faccio scrivere tramite WriteObject().
    Metodo writeElement(X x) ---> outputstream.writeObject(x);

    La lettura mi rimane invece un pelo più difficile. Finchè utilizzo una classe concreta (es. "Highscore"), non ho problemi, ma se utilizzo un generico, il compilatore mi chiede (giustamente) un cast...
    Questo è il banale codice (che mi sembra funzionare) con "Highscore"
    codice:
    public List<Highscore> readElement(){        List<Highscore> list = new ArrayList<>();
            try (
                    final InputStream file = new FileInputStream (this.fileName);
                    final InputStream bstream = new BufferedInputStream (file);
                    final ObjectInputStream ostream = new ObjectInputStream (bstream);
                    )
            {
                while (true) {
                    list.add(ostream.readObject());
                }
            } catch.....
    E' necessario passare la classe al metodo "readElement()" o è possibile capire di che classe siano gli oggetti letti in qualche modo?
    Scusate se è una domanda da novellini >.<
    Grazie

  2. #2
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,254
    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Finchè utilizzo una classe concreta (es. "Highscore"), non ho problemi
    Alt. Se list è di tipo List<Highscore>

    allora la riga

    list.add(ostream.readObject());

    NON ti compila proprio! Perché readObject restituisce Object e con una lista parametrizzata Highscore, è il compilatore che NON accetta di passare un Object dove è richiesto un Highscore.

    Dovresti mettere un cast:

    list.add((Highscore) ostream.readObject());

    allora sì, compila. Ma il cast "potrebbe" fallire a runtime. Se nello stream in input c'è un oggetto che non supera il test IS-A Highscore, il cast fallisce. Ma ovviamente è un caso di errore che devi considerare perché vuol dire che lo stream è "malformato".

    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    ma se utilizzo un generico, il compilatore mi chiede (giustamente) un cast...
    Se il metodo è "generico" ovvero invece di restituire un List<Highscore> ha una type variable <T> e restituisce un List<T>, allora hai almeno due opzioni:

    a) Al metodo fai ricevere una interface Java che ha 1 metodo che "sa" come "mappare" (nel senso di verificare/castare) un oggetto da Object a T (quello che sarà poi parametrizzato nell'uso concreto).

    b) Al metodo fai ricevere un java.lang.Class che viene usato dal metodo come type "token". E userai il cast(Object) del Class che è sostanzialmente l'equivalente ma "dinamico" del cast statico che si scrive fisso nel codice.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    Java Versions Cheat Sheet

  3. #3
    Quote Originariamente inviata da andbin Visualizza il messaggio
    Alt. Se list è di tipo List<Highscore>

    allora la riga

    list.add(ostream.readObject());

    NON ti compila proprio!
    Si, perdonami,ci stavo lavorando con i generici, copiandola qui ho solo cambiato il tipo.

    Speravo ci fosse una maniera più semplice fornita già dalla readObject() >.<
    Procederò con la modalità "b" da te proposta, mi sembra quella più intuitiva e che avrei fatto io.
    Grazie ancora, gentilissimo e limpido nella spiegazione
    Ultima modifica di Mirco993; 18-06-2018 a 19:43

  4. #4
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,254
    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Speravo ci fosse una maniera più semplice fornita già dalla readObject() >.<
    No, purtroppo il readObject​() da solo non ti può aiutare in questo.

    Se puoi usare almeno Java 9 c'è una funzionalità nuovissima che si potrebbe usare: la possibilità di passare un "filter" (ObjectInputFilter) al ObjectInputStream che serve per effettuare una validazione sullo stream. Questa funzionalità è stata introdotta principalmente per proteggersi dagli "attacchi" derivanti da stream fatti ad hoc per "impallare" la de-serializzazione o per altre cose malevole.

    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Procederò con la modalità "b" da te proposta, mi sembra quella più intuitiva e che avrei fatto io.
    In entrambi i modi a) e b) che ho detto, ad un certo punto devi comunque prevedere un cast, che può naturalmente anche fallire (ClassCastException). Ed è un caso che devi considerare perché vorrebbe dire che lo stream è "malformato".

    Comunque se scegli la b) la forma del metodo (non guardare il nome, quello lo scegli tu) dovresti farla:

    public <T> List<T> deserializeItems(Class<T> aClass)

    Quindi se gli passi un class "literal" come Highscore.class (che è di tipo Class<Highscore>) il compilatore dedurrà che il tipo restituito da quella invocazione è un List<Highscore> e quindi puoi assegnare il risultato direttamente ad una variabile List<Highscore> senza alcun cast o altro che non sia type-safe.


    P.S. vedo che il file lo prendi da this.fileName, quindi avrai di certo una classe di cui crei una istanza. La type variable <T> la puoi mettere volendo a livello di classe (e il Class lo passeresti al costruttore). Dipende dal "design" che intendi fare ...
    Ultima modifica di andbin; 18-06-2018 a 21:39
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    Java Versions Cheat Sheet

  5. #5
    Grazie andbin.
    Ieri, lavorandoci un pò su, ho tirato fuori questo.
    codice:
    public List<X> readElement(Class<? extends X> cl, String fileName){
            List<X> list = new ArrayList<>();
    
            try (
                    final InputStream file = new FileInputStream (fileName);
                    final InputStream bstream = new BufferedInputStream (file);
                    final ObjectInputStream ostream = new ObjectInputStream (bstream);
                    )
            {
                while (true) {
                    Object obj = ostream.readObject();
                        list.add(cl.cast(obj));
                }
            } catch (EOFException e) {
                System.out.println("Ok: file finito, esce");
            } catch (FileNotFoundException e) {
                System.out.println("Ok: file non trovato, lo crea");
                File f = new File(fileName);
                try {
                    f.createNewFile();
                } catch (IOException e1) {
                    /*Non entra mai qui*/
                    e1.printStackTrace();
                }
            } catch (IOException e) {
                System.out.println("Errore: file corrotto");
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (ClassCastException e) {
                System.out.print("Errore: cast errato");
            }
            
            return list;
            
        }
    Ho deciso, come hai giustamente fatto notare, che era poco intuitivo passare il file, o comunque il path, al costruttore.
    Così. istanziando un solo oggetto della classe, posso scrivere e leggere da qualunque file senza vincoli (mi è venuto il dubbio...non è forse meglio farli statici a sto punto? Al costruttore alla fine non passo nulla)


    Quote Originariamente inviata da andbin Visualizza il messaggio
    public <T> List<T> deserializeItems(Class<T> aClass)
    Ero partito anche io così, ma ho riscontrato un problema.
    Quando vado a istanziare la classe, se provo a passare al metodo una classe diversa da quella utilizzata per la creazione, nemmeno mi compila.
    Per intenderci:
    codice:
    ReaderWriter<Highscore> rw = new ReaderWriter<>();
    rw.readElement(Highscore.class, "miofile.txt"); // fino qui tutto ok
    rw.readElement(ClasseABC.class, "miofile.txt");//non mi compila nemmeno
    Notato questo, sono arrivato alla soluzione sopra (anche se non mi convince troppo).
    Adesso è così:
    codice:
    ReaderWriter<Object> rw = new ReaderWriter<>();
    rw.readElement(Highscore.class, "miofile.txt");
    rw.readElement(ClasseABC.class, "miofile.txt");
    rw.readElement(ClasseABCDEF.class, "miofile.txt");
    Quindi, ricapitolando.
    Così può andare bene o c'è un errore concettuale di fondo? (Il fatto di istanziare la classe come ReaderWriter<Object> mi sembra un "trucco", non una soluzione)
    Però se non scrivo "Object" dovrei istanziare un ReaderWriter per ogni classe, e se devo istanziare un ReaderWriter per ogni classe, inutile che gli passo una classe come parametro
    Sarebbe meglio cambiare facendo diventare tutto statico?
    Grazie
    Ultima modifica di Mirco993; 19-06-2018 a 09:53

  6. #6
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,254
    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Ieri, lavorandoci un pò su, ho tirato fuori questo.
    Ok ma .. vediamo alcune questioni:

    - meno importante, hai usato X come type variable. Nulla di male o errato ma convenzionalmente si usa T (o E nel caso delle collezioni). O altre lettere in casi più particolari (es. K, V per le map). La X è usata molto raramente (c'è un caso d'uso, in Java 8 la nuova classe Optional ha il orElseThrow che ha una type variable <X extends Throwable> dedicata appunto alla exception).

    - Il ? extends X va bene, così è semplicemente più "ampio". Se passi un Highscore.class, l'inferenza dei tipi permette ad esempio di assegnare ad un List<Object> (perché logicamente qualunque Highscore ci sta in una lista di Object!).
    Senza il extends, se passi Highscore.class, allora devi assegnare ad un List<Highscore> . Tutto qui.

    - La gestione delle eccezioni è un po' fumosa. Sarebbe meglio far uscire l'eccezione fuori dal metodo. E se vogliamo fare i fini, sarebbe anche buono creare una eccezione di più alto livello che incapsula l'eccezione di più basso livello.

    - il fatto di creare il file ... no, non è buono. Non è readElement che dovrebbe occuparsene.

    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Ho deciso, come hai giustamente fatto notare, che era poco intuitivo passare il file, o comunque il path, al costruttore.
    No, nel P.S. di prima non dicevo questo! Intendevo che non so (sapevo) come avevi fatto il design della classe e che ci possono essere varie forme possibili.

    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Così. istanziando un solo oggetto della classe, posso scrivere e leggere da qualunque file senza vincoli (mi è venuto il dubbio...non è forse meglio farli statici a sto punto? Al costruttore alla fine non passo nulla)

    Ero partito anche io così, ma ho riscontrato un problema.
    Quando vado a istanziare la classe, se provo a passare al metodo una classe diversa da quella utilizzata per la creazione, nemmeno mi compila.
    Per intenderci:
    codice:
    ReaderWriter<Highscore> rw = new ReaderWriter<>();
    rw.readElement(Highscore.class, "miofile.txt"); // fino qui tutto ok
    rw.readElement(ClasseABC.class, "miofile.txt");//non mi compila nemmeno
    Notato questo, sono arrivato alla soluzione sopra (anche se non mi convince troppo).
    Adesso è così:
    codice:
    ReaderWriter<Object> rw = new ReaderWriter<>();
    rw.readElement(Highscore.class, "miofile.txt");
    rw.readElement(ClasseABC.class, "miofile.txt");
    rw.readElement(ClasseABCDEF.class, "miofile.txt");
    Allora qui è meglio chiarire, perché stai facendo confusione.

    Se dichiari la type variable al livello della classe ReaderWriter (che quindi diventa "generica"), va bene, è una scelta possibile. Ma così vai a fissare anche il tipo di ritorno di readElement che è List<X>. Se nell'uso concreto fai un ReaderWriter<Object>, allora vai a fissare che readElement ritorna un List<Object>. Certo ... con lo STESSO oggetto ReaderWriter<Object> puoi invocare readElement con ClasseABC.class e poi un'altra invocazione con ClasseXYZ.class. Ma in uscita hai un List<Object> e questo NON è poi molto utile.

    Se fai un ReaderWriter<Highscore> allora è abbastanza chiaro che quella istanza la dovrai usare solo per leggere oggetti Highscore (quindi List<Highscore>). Per trattare un'altra classe, dovrai creare un'altra istanza, parametrizzata diversamente.
    Se non è questo che vuoi ... non è quello il design giusto!

    Poi c'è anche la questione del filename. Se non lo passi al costruttore, allora le istanze di ReaderWriter a cosa servono? O per dire meglio: due istanze di ReaderWriter in cosa si distinguerebbero e per cosa? Appunto .... Poi magari al momento potrebbero non avere alcun "stato" ma magari in futuro potrebbero avere qualche configurazione o dato specifico per quella istanza.

    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Sarebbe meglio cambiare facendo diventare tutto statico?
    Tecnicamente sì, puoi rendere readElement un metodo statico (di "utilità") che è "generico" e riceve il filename.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    Java Versions Cheat Sheet

  7. #7
    - meno importante, hai usato X come type variable. Nulla di male o errato ma convenzionalmente si usa T
    Non lo sapevo, grazie!

    sarebbe anche buono creare una eccezione di più alto livello che incapsula l'eccezione di più basso livello.
    Quindi creo tipo "MyException extends - le varie eccezioni -"?
    La creazione del file, poi, andrebbe poi in MyException o in ReaderWriter (con un metodo apposito)?

    No, nel P.S. di prima non dicevo questo
    Anche io non intendevo questo >.<
    Nel mio caso, ritengo sia più appropriato passarlo al metodo. Il discorso è questo. Nel miniprogettino che dovrei fare adesso, devo scrivere e leggere da file solo Highscore. Avrei voluto fare una cosa più generica, di lettura e scrittura su file, in maniera tale da poter riutilizzare il codice in qualunque altro eventuale progetto...una classe di utilità appunto.

    Se fai un ReaderWriter<Highscore> allora è abbastanza chiaro che quella istanza la dovrai usare solo per leggere oggetti Highscore (quindi List<Highscore>). Per trattare un'altra classe, dovrai creare un'altra istanza, parametrizzata diversamente.
    Se non è questo che vuoi ... non è quello il design giusto!

    Poi c'è anche la questione del filename. Se non lo passi al costruttore, allora le istanze di ReaderWriter a cosa servono? O per dire meglio: due istanze di ReaderWriter in cosa si distinguerebbero e per cosa?
    Allora ho sbagliato design.
    La mia idea di partenza era di fare una classe che, indipendentemente dal file e dal tipo degli elementi all'interno dello stesso, potesse leggerli, scriverli, aggiungerli...Le istanze della classe ReaderWriter non avrebbero dovuto differire, sarebbe stata solo una classe che racchiudeva dei metodi utili.

    Grazie ancora del tuo tempo e della tua esperienza

  8. #8
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,254
    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    Quindi creo tipo "MyException extends - le varie eccezioni -"?
    No, la tua possibile es. ReadElementException può estendere Exception (o magari IOException). Poi l'eccezione di più basso livello (la "causa") la passi al costruttore della tua eccezione.

    Guarda ad esempio un sorgente di IOException, vedi che ha 4 costruttori, che sono quelli tipici: nessun argomento / String / String + Throwable / solo Throwable. Non si è obbligati ovviamente a metterli tutti, dipende dal senso e uso della eccezione.

    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    La creazione del file, poi, andrebbe poi in MyException o in ReaderWriter (con un metodo apposito)?
    Sicuramente non nel readElement. E comunque non ha granché senso (in questo tuo scenario) creare un file "vuoto" tanto per crearlo come "default". La serializzazione degli oggetti si basa su un protocollo di dati ben preciso che prevede in testa allo stream un "header" di 4 byte speciali. Se fai il file vuoto con f.createNewFile() e poi NON vai a serializzare davvero oggetti, al prossimo readElement il file c'è (non becchi FileNotFoundException) ma l'header è mancante e il costruttore di ObjectInputStream si schianta perché è già lì che cerca di leggere l'header.

    Quote Originariamente inviata da Mirco993 Visualizza il messaggio
    La mia idea di partenza era di fare una classe che, indipendentemente dal file e dal tipo degli elementi all'interno dello stesso, potesse leggerli, scriverli, aggiungerli...Le istanze della classe ReaderWriter non avrebbero dovuto differire, sarebbe stata solo una classe che racchiudeva dei metodi utili.
    Puoi fare: classe non generica, poi readElement "generico" che riceve Class e filename. Non c'è nulla di male di per sé.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    Java 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 © 2024 vBulletin Solutions, Inc. All rights reserved.