Chiarimenti su copia di un oggetto

di il
7 risposte

Chiarimenti su copia di un oggetto

Non vorrei postare centinaia di righe di codice quindi provo a riassumere con il seguente codice:

... // nel main
Oggetto copia_di_x = x;
copia_di_x.togli_un_elem();  // sbagliato, modifica sia copia_di_x che x
...

... // sempre nel main
Oggetto copia_di_x = (Oggetto)((Oggetto)x).clone();
copia_di_x.togli_un_elem(); // giusto, modifica soltanto copia_di_x
...

... // definito nella classe Oggetto
public Object clone() throws CloneNotSupportedException 
	{
	    return super.clone();
	}
...
Non capisco come copiare un oggetto "direttamente" come nel primo esempio, ho bisogno di un costruttore di copia? se non è chiaro posso mettere tutto il codice.
Grazie in anticipo

7 Risposte

  • Re: Chiarimenti su copia di un oggetto

    Innanzitutto quando si ridefinisce un metodo che ha un tipo di ritorno "reference" (e questo vale anche e soprattutto per il clone() ), si può sfruttare il "tipo di ritorno covariante" introdotto in Java 5. Nel metodo che fa l'override è lecito dichiarare un tipo di ritorno che è un sottotipo di quello restituito dal metodo sovrascritto.
    public class MiaClasse implements Cloneable {
        public MiaClasse clone() {    // MiaClasse, non Object
            // ...
        }
    }
    Il clone() di Object restituisce Object ma nella ridefinizione puoi restituire MiaClasse. Questo è lecito da Java 5. E permette di evitare "brutti" cast.

    Nella tua riga tra l'altro:

    Oggetto copia_di_x = (Oggetto)((Oggetto)x).clone();

    il cast interno (in rosso) è assolutamente superfluo!


    All'interno di un proprio clone() è tipico fare un super.clone(). Questo (a meno che "sopra" ci sia un'altra classe specifica che anch'essa ridefinisce clone) fa invocare il clone di java.lang.Object.
    Il clone() di Object fa una cosa molto "banale": copia pari pari tutti i campi di istanza dall'oggetto originale a quello clonato. Non fa altro di specifico.

    Questione:
    - se un campo è un primitivo, nessun problema. Il valore copiato è ovviamente distinto.
    - se un campo fa riferimento ad un oggetto "immutabile", nessun problema. L'oggetto sarà condiviso tra originale e clonato ma essendo immutabile, la cosa è assolutamente innocua (ed è pure un "risparmio" di oggetti).
    - se un campo fa riferimento ad un oggetto "mutabile", PROBLEMA. In questo caso è il programmatore che DOPO il super.clone() deve clonare l'oggetto espressamente in modo che sia distinto.


    Il clone() di Object comunque lancia CloneNotSupportedException SOLO se l'oggetto non implementa Cloneable. Ma se nella classe abbiamo messo implements Cloneable, questo NON succederà mai.
    Quindi la cosa che si fa normalmente è catturare CloneNotSupportedException (ricordo che è "checked") e lanciare un Error nel caso "gravissimo" (che NON dovrebbe MAI capitare) in cui CloneNotSupportedException dovesse essere lanciato.

    Quindi in pratica:
    public class MiaClasse implements Cloneable {
        // ...
    
        @Override
        public MiaClasse clone() {
            try {
                MiaClasse miaClasseCopia = (MiaClasse) super.clone();
                // .... eventuali clonazioni specifiche di campi 
                return miaClasseCopia;
            } catch (CloneNotSupportedException e) {
                throw new Error("Grave! Lanciato CloneNotSupportedException");   // non dovrebbe MAI capitare
            }
        }
    }
  • Re: Chiarimenti su copia di un oggetto

    Grazie mille per la spiegazione utilissima sul clone()!
    Per quanto riguarda il primo codice
    
    ... // nel main
    Oggetto copia_di_x = x;
    copia_di_x.togli_un_elem();  // sbagliato, modifica sia copia_di_x che x
    ...
    
    Non ho ancora ben capito come ottenere il risultato che voglio, cioè che copia_di_x sia un altro oggetto, non un "reference" a x, e quindi se modifico copia_di_x non modifico x perchè sono due oggetti diversi. Come posso fare? (mentre con l'override di clone fa quello che desidero).
  • Re: Chiarimenti su copia di un oggetto

    aldorenati ha scritto:


    Non ho ancora ben capito come ottenere il risultato che voglio, cioè che copia_di_x sia un altro oggetto, non un "reference" a x, e quindi se modifico copia_di_x non modifico x perchè sono due oggetti diversi. Come posso fare? (mentre con l'override di clone fa quello che desidero).
    Devi appunto ottenere un nuovo oggetto, su questo non c'è molto su cui si può discutere. Il clone() è una possibilità. Un'altra è dotare la classe di un "costruttore di copia" ed usarlo.
    Ma se anche ti "inventi" un metodo specifico es. getCopia() non cambia molto (ovviamente lo conosci solo tu).
  • Re: Chiarimenti su copia di un oggetto

    Sono d'accordissimo che con clone() funziona tutto bene, ma l'esercizio chiede esplicitamente di risolverlo sia con il clone() sia con:
    Oggetto copia_di_x = x; metto tutto il codice
    
    public class PilaMain {
      
    	   public static void main (String[] args) {
    	        try {		    	    
    			  Pila a = new PilaArray(10);		    
    	          System.out.println("La pila 'a' e' " + a);
    	          a.push(3);
    		      a.push(5);
    		      a.push(7);
    	          System.out.println("La pila 'a' e' " + a);
    		      int x = (Integer)a.pop();
    		      System.out.println("Elemento tolto dalla pila 'a': " + x);
    	          if (!a.isEmpty()) System.out.println("La pila 'a' e' " + a);	 
    	          System.out.println("La pila 'a' ha " + a.size() + " elementi");
    	          Pila copiaDi_a = a;
    	          // provare anche con Pila copiaDi_a = (PilaArray)((PilaArray)a).clone();
    	          System.out.println("La pila 'copiaDi_a' e' " + copiaDi_a);
    	          copiaDi_a.pop();
    	          System.out.println("La pila 'copiaDi_a' e' " + copiaDi_a);	
    	          System.out.println("La pila 'a' e' rimasta invariata: " + a);	 
    	          a.clear();
    	          System.out.println("La pila 'a' e' " + a + "\n");
    	          Pila a1 = new PilaArray(2);
    	          a1.push(1);
    	          a1.push(4);
    			  ((PilaArray)a1).ensureCapacity(3);  // senza questo, la push genera eccezione
    	          a1.push(6);
    	          System.out.println("La pila 'a1' e' " + a1);
    	          System.out.println("Secondo elemento di pila 'a1': " + ((PilaArray)a1).get(1) + '\n');	          
    		      a1.push(8);
    		      System.out.println("Sono arrivato qui");
    	        }
    	        catch (PilaVuota e) {  
    	        	System.out.println("Errore: pop su pila vuota\n");
    	        }
    	        catch (PilaPiena e) {  
    	        	System.out.println("Errore: push su pila piena\n");
    	        }
    	        catch (Throwable e) {    // catch all
    	        	System.out.print("Altre eccezioni: ");
    	        	System.out.println(e);
    	        }
    	        		    
    	    }   
    	}
    
    
    public interface Pila<T>
    {
    	public void push(Object x);
    	public Object pop();
    	public boolean isEmpty();
    	public int size();
    	public void clear();
    }
    
    
    public class PilaArray<T> implements Pila<T>, Cloneable 
    {
    	private int dim;
    	private int top;
    	private Object [] A;
    
    	public PilaArray()
    	{
    		dim = 5;
    		top = 0;
    		A = new Object [dim];
    	}
    
    	public PilaArray(int n)
    	{
    		if (n < 0)
    			throw new Error("la dimensione deve essere maggiore di zero");
    		dim = n;
    		top = 0;
    		A = new Object [dim];
    	}
    	
    	public PilaArray(PilaArray p)
    	{
    		dim = p.dim;
    		top = p.top;
    		A = new Object [dim];
    		// non entra mai comunque non saprei come copiare l'oggetto p in this
    	}
    
    	public void push(Object x)
    	{
    		if (top+1 > dim)
    			throw new PilaPiena();
    		A[top] = x;
    		top++;
    	}
    
    	public Object pop()
    	{
    		if (top == 0)
    			throw new PilaVuota();
    		A[top] = null;
    		top--;
    		return A[top];
    	}
    
    	public boolean isEmpty()
    	{
    		if (top == 0)
    			return true;
    		else
    			return false;
    	}
    
    	public int size()
    	{
    		return top;
    	}
    
    	public void clear()
    	{
    		while (!this.isEmpty())
    			this.pop();
    	}
    
    	public PilaArray clone() throws CloneNotSupportedException
    	{
    		return (PilaArray) super.clone();
    	}
    	    
    	public String toString()
    	{
    		String r = "[";
    		for (int i = 0; i < top; i++)
    		{
    			r = r + A[i].toString();
    			if (i != top - 1)
    				r = r + ",";
    		}
    		r = r + "]";
    		return r;
    	}
    
    	public Object get(int i)
    	{
    		return A[i];
    	}
    
    	public void ensureCapacity(int m)
    	{
    		T[] C = (T[]) new Object[m];
    		for (int i = 0; i < top; i ++)
    		{
    		    C[i] = (T) A[i];
    		}
    		dim = m;
    		A = C;
    	}
    }
    
    
    
    public class PilaPiena extends Error
    {
    	public String toString()
    	{
    		return "push su pila piena";
    	}
    }
    
    
    public class PilaVuota extends Error
    {
    	public String toString()
    	{
    		return "pop su pila vuota";
    	}
    }
    

    Lascia perdere tutti gli altri eventuali errori è uno dei miei primi programmi in Java, concentriamoci sul costruttore di copia, perchè non entra mai, e soprattutto cosa devo scrivere nel corpo perchè faccia ciò che desidero? Scusa se ho postato tutto questo codice ma senza non sapevo proprio come spiegarmi, se non hai voglia di leggere tutto pazienza
  • Re: Chiarimenti su copia di un oggetto

    aldorenati ha scritto:


    Oggetto copia_di_x = x;
    Così non ti funzionerà mai. Questa è solo la copia del valore del riferimento ... non è una creazione/duplicazione di un oggetto!!

    aldorenati ha scritto:


    concentriamoci sul costruttore di copia, perchè non entra mai
    Il costruttore "di copia" (questo termine è preso in prestito più che altro dal C++ dove questa forma è più frequentemente usata) l'hai scritto, è quel:

    public PilaArray(PilaArray p)

    ma da quanto vedo, NON sta facendo la cosa giusta.
    E se anche la facesse, nel tuo main() di test NON ne vedo l'uso di questo costruttore.
    Il costruttore di copia (ammesso che faccia la cosa "giusta") lo devi invocare esplicitamente tu, non esiste alcun meccanismo per cui possa essere invocato implicitamente!

    PilaArray pilaOrig = new PilaArray( ...... );
    PilaArray pilaCopia = new PilaArray(pilaOrig); // USI il costruttore di copia

    aldorenati ha scritto:


    e soprattutto cosa devo scrivere nel corpo perchè faccia ciò che desidero?
    Va bene copiare dim/top. Va anche bene creare un nuovo array Object[] della dimensione originale.
    Ma ... pensi che basta? E gli oggetti CONTENUTI nell'array del PilaArray originale??

    Problema: PilaArray "vede" gli oggetti nell'array ovviamente solo come dei Object. Non "sa" di che tipo sono e non "sa" neanche quale è la parametrizzazione concreta che usi altrove (es. se fai un PilaArray<String> o un PilaArray<Date> ).
    Inoltre, Object NON ha un clone() visibile (è protected infatti). E Cloneable è solo una interfaccia di "marcatura", non ha metodi (nemmeno il clone).

    Morale della favola: è parecchio difficile clonare in modo "profondo" quell'array. Dovresti usare la reflection per andare a scoprire se ciascun oggetto nell'array originale possiede un clone() PUBBLICO e quindi invocarlo sempre tramite reflection. E' una cosa un po' "avanzata" ....

    E' purtroppo lo stesso problema che hanno tutte le collection standard di Java. Sono clonabili ma il loro clone() fa solo una "shallow" copy. Il clone() di ArrayList ad esempio restituisce un NUOVO oggetto ArrayList con la sua struttura dati distinta. Ma i riferimenti agli oggetti sono solo copiati come valore del reference (come fare copia_di_x = x) e quindi non è una copia "profonda". Entrambi gli ArrayList, originale e copia, fanno riferimento agli stessi oggetti! Se però aggiungi/rimuovi qualcosa nel ArrayList originale, questa modifica NON la vedi nel ArrayList copia (e viceversa).

    Si potrebbe risolvere solo se passi a PilaArray, in qualche modo, un "comportamento" (detto in senso generale) che "sa" come clonare un singolo elemento. Ovviamente è il chiamante che usa PilaArray che deve sapere poi cosa farci.


    P.S.: hai fatto Pila/PilaArray come tipi "generici" ma poi non hai applicato i generics in modo appropriato e coerente.

    P.S. 2: PilaPiena/PilaVuota che estendono java.lang.Error è davvero inappropriato.
  • Re: Chiarimenti su copia di un oggetto

    Ok grazie mille adesso mi hai tolto ogni dubbio, grazie anche per le righe finali adesso proverò a mettere a posto, sei stato gentilissimo
  • Re: Chiarimenti su copia di un oggetto

    P.S.3: il clone() che hai messo in PilaArray non è di per sé tecnicamente sbagliato. Ma solo così è poco utile. Come ho detto prima, il clone() di Object fornisce un oggetto "clone" in cui viene fatta solamente una banale copia pari-pari del valore di tutti i campi dell'oggetto.

    Questo vuol dire che il tuo PilaArray originale e il PilaArray "clonato" con clone() hanno il riferimento allo STESSO array Object[] . Quindi ti lascio immaginare cosa succede se su uno dei due invochi un pop() .....
Devi accedere o registrarti per scrivere nel forum
7 risposte