I parametri sono passati per valore, ma modificarli modificherà l'oggetto reale (come passato per riferimento)? [duplicare]

8

Ho appena scoperto un enorme buco nella mia conoscenza di Java. Sapevo che Java passa i parametri in base al valore. Ho pensato di capirlo e ogni volta che dovevo modificare l'oggetto campo di una classe, creo un campo. Ad esempio,

private int mNumber;

void main(){
   mNumber = 1;
}

void otherMethod(){
   mNumber = 3;
}

Questo cambierebbe l'oggetto del campo, tutti sono felici.

Stavo eseguendo il debug e dovevo rivedere le classi all'interno di Android SDK e ho notato questo pezzo di codice

public static String dumpCursorToString(Cursor cursor) {
    StringBuilder sb = new StringBuilder();
    dumpCursor(cursor, sb);
    return sb.toString();
}

public static void dumpCursor(Cursor cursor, StringBuilder sb) {
    sb.append(">>>>> Dumping cursor " + cursor + "\n");
    if (cursor != null) {
        int startPos = cursor.getPosition();
        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
            dumpCurrentRow(cursor, sb);
        }
        cursor.moveToPosition(startPos);
    }
    sb.append("<<<<<\n");
}

Stanno cambiando la variabile locale sb passandola come parametro ad un altro metodo.

Nel mio caso, sb sarebbe un campo e lo cambierei da un altro metodo.

Come è possibile? Un riferimento (puntatore) è passato come parametro, quindi stiamo SEMPRE cambiando il valore sorgente (proprio come lo passiamo per riferimento in altre lingue)? Il mio approccio con il campo non è stato necessario per tutto questo tempo?

Quindi il mio approccio potrebbe essere facilmente cambiato in

void main(){
   int number = 1;
   otherMethod(number)
}

void otherMethod(int number){
   number = 3;
}

Non essere arrabbiato con me se questo sembra una domanda per principianti. Potrebbe essere, ma ho appena scoperto un enorme buco nella mia conoscenza di Java.

    
posta deviDave 06.06.2015 - 11:05
fonte

2 risposte

41

L'affermazione che "java è sempre pass-by-value" è tecnicamente corretta, ma può essere molto fuorviante, perché come hai appena visto, quando si passa un oggetto a una funzione, la funzione può modificare il contenuto dell'oggetto, quindi sembra che l'oggetto sia stato passato per riferimento e non per valore. Allora, cosa sta succedendo?

Puntatori. Ecco cosa sta succedendo.

Uno dei principali punti di forza di Java era che "è un linguaggio sicuro perché privo di indicatori".

Questa affermazione è vera nel senso che non è possibile fare la maggior parte delle cose non sicure come l'aritmetica dei puntatori che storicamente è stata la causa di molti bug mostruosi in linguaggi come C e C ++. Tuttavia, questa affermazione ha anche ostacolato la comprensione di java per molti programmatori alle prime armi.

Vedete, java è in realtà pieno di puntatori; I principianti programmatori iniziano a dare un senso al linguaggio solo quando capiscono che in Java tutto ciò che non è un primitivo è in realtà un puntatore; i puntatori sono ovunque; non puoi dichiarare un oggetto staticamente, o nello stack, o come elemento di un array come puoi fare in C ++; l'unico modo per dichiarare e utilizzare un oggetto è tramite puntatore; anche le matrici di oggetti non sono in realtà altro che matrici di indicatori di oggetti; tutto ciò che assomiglia ad un oggetto in java non è mai realmente un oggetto, è sempre un puntatore a un oggetto.

Javafaunachiaradistinzionetraprimitivieoggetti.

Iprimitivi(int,boolean,float,ecc.),notianchecometipivalore,vengonosemprepassatipervaloreesicomportanoesattamentecomecisiaspetterebbeinbaseallamassima"java è sempre pass-by-value".

Gli oggetti, d'altra parte, noti anche come tipi reference , sono sempre accessibili per riferimento, cioè tramite un puntatore, quindi il loro comportamento è un po 'più complicato. Quando si passa un oggetto a un metodo, java non passa realmente l'oggetto stesso; passa un puntatore ad esso. Questo puntatore viene passato per valore, quindi la massima "java è sempre pass-by-value" vale ancora da una prospettiva strettamente tecnica. Ad esempio, se si imposta questo puntatore su null nel metodo, il puntatore tenuto dal metodo chiamante non viene modificato, quindi ovviamente quello che si ha è una copia del valore del puntatore del metodo di chiamata.

Quindi, l'affermazione "java è sempre pass-by-value" è vera da un punto di vista strettamente tecnico, ma in definitiva non molto utile, secondo cui gli oggetti mai sono realmente passati ovunque, vengono passati solo i puntatori agli oggetti.

In realtà, quando si scrive codice, è molto utile pensare che gli oggetti siano passati alle funzioni e poiché java passa i puntatori (in base al valore) sotto il cofano, gli oggetti stessi sembrano sempre essere passati per riferimento.

    
risposta data 06.06.2015 - 11:31
fonte
6

In breve, stai confondendo la riassegnazione di una variabile locale con la modifica di un oggetto condiviso. sb.append() non modifica la variabile sb , modifica l'oggetto a cui fa riferimento la variabile. Ma number = 3 modifica la variabile number assegnandole un nuovo valore. Gli oggetti sono condivisi tra le chiamate ai metodi, ma le variabili locali no. Quindi assegnando una variabile locale a un nuovo valore non avrà alcun effetto al di fuori del metodo, ma la modifica di un oggetto lo farà. Un po 'semplificato: se usi un punto ( . ), stai modificando un oggetto, non una variabile locale.

La confusione è probabilmente dovuta a una terminologia confusa. "Riferimento" indica due cose diverse in contesti diversi.

1) Quando si parla di oggetti e variabili Java, un riferimento è un "handle" per un oggetto. In senso stretto, una variabile non può contenere un oggetto, contiene un riferimento a un oggetto. I riferimenti possono essere copiati, quindi due variabili possono ottenere un riferimento allo stesso oggetto. Per es.

StringBuilder a = new StringBuilder();
StringBuilder b = a; // a and b now references the same object
a.writeLine("Hello"); // modify the shared object
b.toString() // -> "Hello"

Quando un oggetto viene passato a un metodo come parametro, è anche il riferimento che viene copiato, quindi il metodo chiamato può modificare lo stesso oggetto. Questo è anche chiamato "Chiama per condivisione".

void main(){
   String builder b = new StringBuilder();
   otherMethod(b);
   b.toString(); // --> "Hello" because the shared object is modified
}

void otherMethod(StringBuilder b){
   b.write("Hello"); // modify the shared object
}

Ma otherMethod può solo modificare l'oggetto condiviso, non può modificare nessuna variabile locale nel metodo chiamante. Se la funzione chiamata assegna un nuovo valore ai propri parametri, non influisce sulla funzione di chiamata.

void main(){
   String builder b = new StringBuilder();
   b.writeLine("Hello"); 
   otherMethod(b);
   b.toString(); // --> "Hello" because the shared object is NOT modified
}

void otherMethod(StringBuilder b){
   b = new StringBuilder(); // assign a new object to b
   b.write("Goodbye"); // modify the new object
}

Quindi devi essere chiaro sulla distinzione tra la modifica di un oggetto condiviso (attraverso la modifica di campi o il metodo di chiamata sull'oggetto) e l'assegnazione di un nuovo valore a una variabile o parametro locale.

2) Quando si parla di strategia di valutazione (come i parametri sono passati in funzione) nella teoria generale del linguaggio informatico, call-by-reference e call-by-value significa qualcosa di molto diverso.

Call-by-value, che supporta Java, significa che i valori degli argomenti vengono copiati nei parametri quando si chiamano i metodi. Il valore può essere un riferimento, motivo per cui la funzione chiamata otterrà un riferimento all'oggetto condiviso. In modo confuso, Java si dice "passi i riferimenti per valore", che è abbastanza diverso dalla chiamata per riferimento.

Se una lingua supporta call-by-reference, una funzione può cambiare il valore di una variabile locale nella funzione di chiamata . Java non supporta questo, che è mostrato nell'esempio sopra. Chiamata per riferimento significa che la funzione chiamata ottiene un riferimento alla variabile locale utilizzata come argomento nella chiamata. Questa nozione è completamente estranea a Java, dove semplicemente non abbiamo il concetto di riferimento a una variabile locale. Abbiamo solo riferimenti agli oggetti, mai alle variabili locali.

Per mostrare come funziona la chiamata per riferimento, possiamo guardare C # che fa supporto per riferimento con il modificatore ref :

void main(){
   String builder b = new StringBuilder();
   b.writeLine("Hello"); 
   otherMethod(ref b);
   b.toString(); // --> "Goodbye" because otherMethod changed b to point to the new object
}

void otherMethod(ref StringBuilder b){
   b = new StringBuilder(); // assign a new object to b
   b.write("Goodbye"); // modify the new object
}

Qui otherMethod () modifica effettivamente la variabile locale b in main ().

Quindi per quanto riguarda le primitive come interi, booleani ecc.? Questi sono immutable che significa che non possono essere modificati come possono fare gli oggetti. Dal momento che non possono essere modificati, la domanda se sono condivisi o meno è discutibile, poiché non vi è alcuna differenza osservabile se sono condivisi o copiati.

Nota: non ho inventato la terminologia, quindi non incolpare il messenger. Preferisco di gran lunga i termini chiamata per condivisione o chiamata per oggetto che è più intuitivo per Java, ma resta il fatto che i termini call-by-value e call-by-reference è una terminologia stabilita e la specifica Java stessa utilizza il termine call-by-value.

    
risposta data 06.06.2015 - 11:58
fonte

Leggi altre domande sui tag