2009-02-05 7 views
8

Sono abbastanza nuovo per Java (scritto altre cose per molti anni) e, a meno che mi manca qualcosa (e sono felice di essere sbagliato qui) il seguente è un difetto fatale ...Qualcuno può spiegarmi quale sia la ragione dietro cui passa il "valore" e non il "riferimento" in Java?

String foo = new String(); 
thisDoesntWork(foo); 
System.out.println(foo);//this prints nothing 

public static void thisDoesntWork(String foo){ 
    foo = "howdy"; 
} 

Ora, sono ben consapevole del concetto (abbastanza poco formulato) che in java tutto è passato per "valore" e non "riferimento", ma String è un oggetto e ha tutti i tipi di campane e fischietti, quindi, uno ci si aspetterebbe che a differenza di un int un utente sarebbe in grado di operare sulla cosa che è passata nel metodo (e non essere bloccato con il valore impostato da overloaded =).

Qualcuno può spiegarmi quale fosse il ragionamento alla base di questa scelta progettuale? Come ho detto, non sto cercando di essere proprio qui, e forse mi manca qualcosa di ovvio?

+0

io non sono davvero sicuro di quello che si sta cercando di dire qui. Per favore chiariscilo un po '? – jrockway

+0

il titolo precedente non era buono, perché sarebbe impossibile cercare il contenuto della tua domanda per titolo. –

+0

nessun problema, questo titolo ha più senso :) –

risposta

11

This rant spiega tutto meglio di quanto io possa mai nemmeno provare a:

In Java, le primitive sono passati per valore. Tuttavia, gli oggetti non sono passati per riferimento. Una dichiarazione corretta sarebbe Oggetto I riferimenti sono passati per valore.

+4

Sono contento che ti sia piaciuto il mio rant;) –

+0

Questo link è carino, penso che questa dovrebbe essere la risposta migliore. – Reddy

9

Quando si passa "foo", si passa il riferimento a "foo" come valore a ThisDoesntWork(). Ciò significa che quando si esegue l'assegnazione a "foo" all'interno del proprio metodo, si sta semplicemente impostando un riferimento di variabile locale (foo) come riferimento alla nuova stringa.

Un'altra cosa da tenere a mente quando si pensa a come le stringhe si comportano in Java è che le stringhe sono immutabili. Funziona allo stesso modo in C#, e per alcuni buoni motivi:

  • Sicurezza: Nessuno può incepparsi dati nella vostra stringa e causare un errore di buffer overflow se nessuno può modificarlo!
  • Velocità: Se si può essere sicuri che le stringhe siano immutabili, si sa che le sue dimensioni sono sempre le stesse e non si deve mai fare una mossa della struttura dati in memoria quando la si manipola. Anche tu (il progettista di linguaggi) non devi preoccuparti di implementare String come una lista a collegamenti lenti. Questo taglia in entrambi i modi, però. L'aggiunta di stringhe usando solo l'operatore + può essere costosa in termini di memoria, e dovrete usare un oggetto StringBuilder per farlo in un modo efficiente a memoria e ad alte prestazioni.

Ora sulla tua domanda più grande. Perché gli oggetti sono passati in questo modo? Bene, se Java ha passato la tua stringa come quello che tradizionalmente chiamerai "per valore", dovrebbe effettivamente copiare l'intera stringa prima di passarla alla tua funzione. È piuttosto lento. Se ha passato la stringa per riferimento e ti ha permesso di cambiarla (come fa C), avresti i problemi che ho appena elencato.

+1

in realtà non ha a che fare con il fatto che le stringhe sono immutabili. Se ha passato una lista, la stessa cosa accadrebbe e l'elenco è mutabile –

+0

Il problema non ha nulla a che fare con l'immutabilità di String. Il comportamento sarebbe esattamente lo stesso con qualsiasi altro oggetto. Non sono sicuro che l'immutabilità di String abbia molto a che fare con la sicurezza, penso che sia molto più un problema di prestazioni. –

+0

Modificato di conseguenza. –

-2

Vai a fare il tutorial davvero grande sul sito web di soli.

Sembra che tu non capisca la differenza che le variabili possono essere. "foo" è locale per il tuo metodo. Niente al di fuori di quel metodo può cambiare i punti "foo". Il "foo" è riferito al tuo metodo è un campo completamente diverso - è un campo statico sulla tua classe che racchiude.

Scoping è particolarmente importante in quanto non desideri che tutto sia visibile a tutto il resto del sistema.

+0

no, capisco intimamente lo scoping. Non capisco il valore di rendere impossibile modificare il valore di un oggetto non primitivo passato in un metodo. Immagino, alla fine, desidero solo l'opzione di fare entrambe le cose, come nel caso di C, e di chiedere perché questo è stato rimosso in Java. –

+0

E guarda il casino che è C, nessun incapsulamento in nessuna forma. Qualsiasi riga di codice in qualsiasi parte del sistema può modificare magicamente i valori e distruggere il programma se si è fortunati. –

+0

Dr Dredel. La tua domanda era mal formulata. È apparso dal tuo esempio che non hai capito gli scopi. Se lo facessi, apprezzeresti che anche i riferimenti sono mirati. Quando si passano i riferimenti oggetto attorno a te si ottiene una copia del puntatore di riferimento. Gli oggetti possono avere molti riferimenti a loro –

0

Sei sicuro che stampa nulla? Penso che sarà solo vuoto come quando hai inizializzato la variabile foo che hai fornito String vuota.

L'assegnazione di foo in questo metodo DoesntWork non modifica il riferimento della variabile foo definita in classe, quindi il comando foo in System.out.println (foo) continuerà a puntare al vecchio oggetto stringa vuoto.

+0

Non inizializzato con una stringa vuota, AFAICS. –

+0

sì, mi spiace, errore di battitura ... Correggerò –

+0

Aceto intendevo vuoto Oggetto stringa –

-1

Gli argomenti digitati di riferimento vengono passati come riferimenti agli oggetti stessi (non i riferimenti alle altre variabili che fanno riferimento agli oggetti). È possibile chiamare metodi sull'oggetto che è stato passato. Tuttavia, nel codice di esempio:

public static void thisDoesntWork(String foo){ 
    foo = "howdy"; 
} 

si sta solo memorizzando un riferimento alla stringa "howdy" in una variabile che è locale al metodo. Quella variabile locale (foo) è stata inizializzata sul valore di foo del chiamante quando è stato chiamato il metodo, ma non ha alcun riferimento alla variabile stessa del chiamante. Dopo l'inizializzazione:

caller  data  method 
------ ------ ------ 
(foo) --> "" <-- (foo) 

Dopo l'assegnazione nel metodo:

caller  data  method 
------ ------ ------ 
(foo) --> "" 
      "hello" <-- (foo) 

Hai un altro problema là: String casi sono immutabili (in base alla progettazione, per la sicurezza) in modo non è possibile modificare il valore.

Se si vuole veramente il tuo metodo per fornire un valore iniziale per la stringa (o qualsiasi volta nella sua vita, è per questo), poi il metodo di tornare un valore String, che si assegna al chiamante di variabile al punto della chiamata. Qualcosa di simile, per esempio:

String foo = thisWorks(); 
System.out.println(foo);//this prints the value assigned to foo in initialization 

public static String thisWorks(){ 
    return "howdy"; 
} 
+0

No, i tipi di riferimento * non sono * passati per riferimento. Il valore dell'argomento (che è un riferimento) viene passato per valore.Questo * non * equivale a passare dalla semantica di riferimento. –

+0

Java utilizza la semantica della copia e passerà una copia del riferimento a un metodo. Quindi, i riferimenti vengono passati per valore. – eljenso

2

Il problema è che si sta istanziare un tipo di riferimento Java. Quindi si passa quel tipo di riferimento a un metodo statico e lo si riassegna a una variabile con ambito locale.

Non ha nulla a che fare con l'immutabilità. Esattamente la stessa cosa sarebbe accaduta per un tipo di riferimento mutevole.

3

In java tutte le variabili passate vengono effettivamente passate attorno agli oggetti valore-pari. Tutte le variabili passate a un metodo sono in realtà copie del valore originale. Nel caso del tuo esempio di stringa il puntatore originale (in realtà è un riferimento - ma per evitare che la confusione usi male una parola diversa) viene copiato in una nuova variabile che diventa il parametro del metodo.

Sarebbe un dolore se tutto fosse di riferimento. Uno avrebbe bisogno di fare copie private dappertutto, il che sarebbe sicuramente un vero dolore. Tutti sanno che l'utilizzo dell'immutabilità per i tipi di valore ecc rende i tuoi programmi infinitamente più semplici e scalabili.

Alcuni vantaggi includono: - Non è necessario effettuare copie difensive. - Threadsafe - non c'è bisogno di preoccuparsi di bloccare nel caso in cui qualcun altro voglia cambiare l'oggetto.

+0

Tutto è per riferimento in Java. –

+0

SBAGLIATO SBAGLIATO SBAGLIATO. I valori primitivi non sono di riferimento. Non è possibile passare un riferimento a un "int" che vive in un metodo, ovvero una variabile locale in un altro metodo e modificare la prima variabile locale. –

+0

Bene, ogni oggetto è passato per riferimento. Passare i tipi di valore per riferimento non ha comunque senso. –

4

La tua domanda come richiesto non ha davvero a che fare con il passare per valore, passando per riferimento, o il fatto che le stringhe sono immutabili (come altri hanno affermato).

All'interno del metodo, si crea effettivamente una variabile locale (che chiamerò "localFoo") che punta allo stesso riferimento della variabile originale ("originalFoo").

Quando si assegna "howdy" a localFoo, non si modifica il punto in cui punta il puntatore originale.

se hai fatto qualcosa di simile:

String a = ""; 
String b = a; 
String b = "howdy"? 

ci si può aspettare:

System.out.print(a) 

per stampare fuori "Howdy"? Stampa "".

Non è possibile modificare a cosa punta il Punto Originale cambiando ciò a cui punta LocalFoo. È possibile modificare l'oggetto che puntano entrambi (se non era immutabile). Ad esempio,

List foo = new ArrayList(); 
System.out.println(foo.size());//this prints 0 

thisDoesntWork(foo); 
System.out.println(foo.size());//this prints 1 

public static void thisDoesntWork(List foo){ 
    foo.add(new Object); 
} 
+0

Ha tutto a che fare con il passare del valore o passare per riferimento. Con i parametri * pass * di riferimento passivi (ad esempio "ref" in C#) potresti passare la variabile string per riferimento e verrebbe modificata. –

0

Dave, dovete perdonarmi (beh, immagino non si "deve", ma preferirei che hai fatto), ma questa spiegazione non è eccessivamente convincente. I guadagni di sicurezza sono abbastanza minimi in quanto chiunque ha bisogno di cambiare il valore della stringa troverà un modo per farlo con qualche brutto trucco. E la velocità ?! Tu stesso (abbastanza correttamente) asserisci che l'intero business con il + è estremamente costoso.

Il resto di voi ragazzi, vi prego di capire che MI RICEVERE come funziona, sto chiedendo PERCHÉ funziona così ... per favore smettete di spiegare la differenza tra le metodologie.

(e sinceramente non sto cercando alcun tipo di combattimento qui, btw, non vedo come questa sia stata una decisione razionale).

+0

Beh, ovviamente ti manca qualcosa. L'esempio che mostri ** non ha nulla a che fare con il passaggio dei nomi ** è solo una questione di come i parametri sono legati all'interno di un metodo (ed è effettivamente utile capire la differenza tra un parametro e una variabile). –

+0

@ Dr.Dredel: È per semplicità, sia nella lingua che nella lettura del codice. Quando chiamo un metodo e passo un riferimento a una stringa, so che il metodo non lo cambierà. Mentre il codice del male potrebbe (in alcuni casi) aggirare il problema, il caso più comune è il codice non malvagio - dove si vuole essere in grado (cont) –

+0

di ragionare sul codice facilmente. Quando si passano i riferimenti in base al valore, è più facile capire cosa farà il codice. In C#, puoi * passare * i parametri per riferimento - ma è ancora raro, perché rende il codice più difficile da capire. –

0

@Axelle

Mate pensi davvero conosce la differenza tra passando per valore e per riferimento?

In java, i riferimenti vengono passati per valore. Quando si passa un riferimento a un oggetto si ottiene una copia del puntatore di riferimento nella seconda variabile. Ecco perché la seconda variabile può essere modificata senza influenzare la prima.

+0

Sì. Si. Sono solo confuso dal (apparentemente) modo ritardato in cui Java implementa String. È un oggetto, DEVO essere in grado di modificare uno dei suoi membri (quello che tiene in mano i caratteri) a qualsiasi cosa io voglia, ovunque vada. Passi in una lista che puoi aggiungere() ad essa, giusto? –

+1

Rendere immutabile String è stata una * grande * decisione da parte dei designer Java (motivo per cui tale decisione è stata anche utilizzata in .NET). Altrimenti dovremmo copiare in modo difensivo dappertutto ...

1

Se vogliamo fare una ruvida C e assembler analogia:

void Main() 
{ 
    // stack memory address of message is 0x8001. memory address of Hello is 0x0001. 
    string message = "Hello"; 
    // assembly equivalent of: message = "Hello"; 
    // [0x8001] = 0x0001 

    // message's stack memory address 
    printf("%d", &message); // 0x8001 

    printf("%d", message); // memory pointed to of message(0x8001): 0x0001 
    PassStringByValue(message); // pass the pointer pointed to of message. 0x0001, not 0x8001 
    printf("%d", message); // memory pointed to of message(0x8001): 0x0001. still the same 

    // message's stack memory address doesn't change 
    printf("%d", &message); // 0x8001 
} 

void PassStringByValue(string foo) 
{ 
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001) 

    // foo(0x4001) contains the memory pointed to of message, 0x0001 
    printf("%d", foo); // 0x0001 
    // World is in memory address 0x0002 
    foo = "World"; // on foo's memory address (0x4001), change the memory it pointed to, 0x0002 
    // assembly equivalent of: foo = "World": 
    // [0x4001] = 0x0002 

    // print the new memory pointed by foo 
    printf("%d", foo); // 0x0002 

    // Conclusion: Not in any way 0x8001 was involved in this function. Hence you cannot change the Main's message value. 
    // foo = "World" is same as [0x4001] = 0x0002 

} 

void Main() 
{ 
    // stack memory address of message is 0x8001. memory address of Hello is 0x0001. 
    string message = "Hello"; 
    // assembly equivalent of: message = "Hello"; 
    // [0x8001] = 0x0001 

    // message's stack memory address 
    printf("%d", &message); // 0x8001 

    printf("%d", message); // memory pointed to of message(0x8001): 0x0001 
    PassStringByRef(ref message); // pass the stack memory address of message. 0x8001, not 0x0001 
    printf("%d", message); // memory pointed to of message(0x8001): 0x0002. was changed 

    // message's stack memory address doesn't change 
    printf("%d", &message); // 0x8001 
} 


void PassStringByRef(ref string foo) 
{ 
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001) 

    // foo(0x4001) contains the address of message(0x8001) 
    printf("%d", foo); // 0x8001 
    // World is in memory address 0x0002 
    foo = "World"; // on message's memory address (0x8001), change the memory it pointed to, 0x0002 
    // assembly equivalent of: foo = "World": 
    // [0x8001] = 0x0002; 


    // print the new memory pointed to of message 
    printf("%d", foo); // 0x0002 

    // Conclusion: 0x8001 was involved in this function. Hence you can change the Main's message value. 
    // foo = "World" is same as [0x8001] = 0x0002 

} 

Una possibile ragione per cui tutto è passato per valore in Java, le sue persone linguaggio di design vogliono semplificare il linguaggio e fai tutto in modo OOP.

Preferiscono che progettiate uno swapper intero utilizzando oggetti rispetto a quelli che forniscono un supporto di prima classe per il passaggio per riferimento, lo stesso per delegato (Gosling si sente icky con il puntatore alla funzione, preferirebbe racchiudere quella funzionalità negli oggetti) e enum.

Essi semplificano eccessivamente (tutto è oggetto) la lingua a scapito del fatto di non disporre del supporto di prima classe per la maggior parte dei costrutti linguistici, ad es. passando per riferimento, delegati, enum, proprietà vengono in mente.

0

È perché, crea una variabile locale all'interno del metodo.quello che sarebbe un modo semplice (che sono abbastanza sicuro che avrebbe funzionato) sarebbe:

String foo = new String();  

thisDoesntWork(foo);  
System.out.println(foo); //this prints nothing 

public static void thisDoesntWork(String foo) {  
    this.foo = foo; //this makes the local variable go to the main variable  
    foo = "howdy";  
} 
+0

-1 L'OP non sta cercando di ottenere il codice corretto. Questo non risponde alla domanda dell'OP: _ Qualcuno può spiegarmi quale fosse il ragionamento alla base di questa scelta progettuale? _ – Alberto

0

Se pensate di un oggetto, come solo i campi nell'oggetto poi gli oggetti vengono passati per riferimento in Java perché un metodo può modificare i campi di un parametro e un chiamante può osservare la modifica. Tuttavia, se si pensa anche a un oggetto come all'identità, gli oggetti vengono passati per valore perché un metodo non può cambiare l'identità di un parametro in un modo che il chiamante può osservare. Quindi direi che Java è pass-by-value.

5

Poiché la mia risposta originale era "Perché è successo" e non "Perché il linguaggio è stato progettato così è successo", darò un altro andare.

Per semplificare le cose, mi libererò della chiamata al metodo e mostrerò cosa sta succedendo in un altro modo.

String a = "hello"; 
String b = a; 
String b = "howdy" 

System.out.print(a) //prints hello 

Per ottenere l'ultima dichiarazione di stampare "ciao", b deve puntare alla stessa "buco" in memoria che un punti a (un puntatore). Questo è quello che vuoi quando vuoi passare per riferimento. Ci sono un paio di ragioni Java ha deciso di non andare in questo senso:

  • puntatori sono confuse I progettisti di Java hanno cercato di rimuovere alcune delle cose più confuse circa altre lingue. I puntatori sono uno dei costrutti più incomprensibili e usati impropriamente di C/C++ insieme all'overloading dell'operatore.

  • I puntatori sono rischi per la sicurezza I puntatori causano molti problemi di sicurezza in caso di utilizzo improprio. Un programma dannoso assegna qualcosa a quella parte di memoria, quindi quello che pensavi fosse il tuo oggetto è in realtà quello di qualcun altro. (Java già sbarazzato del più grande problema di sicurezza, buffer overflow, con gli array controllato)

  • Astrazione dispersione Quando si avvia trattare con "Cosa c'è nella memoria e dove" esattamente, la tua astrazione diventa meno di un'astrazione. Mentre la perdita di astrazione quasi certamente si insinua in un linguaggio, i progettisti non hanno voluto infilarlo direttamente.

  • Gli oggetti sono tutto ciò che ti interessa In Java, tutto è un oggetto, non lo spazio occupato da un oggetto. puntatori Aggiunta avrebbero rendere lo spazio un oggetto importantant occupa, anche se .......

Si potrebbe emulare ciò che si vuole con la creazione di un oggetto "Hole". Potresti anche usare i generici per renderlo sicuro. Ad esempio:

public class Hole<T> { 
    private T objectInHole; 

    public void putInHole(T object) { 
     this.objectInHole = object; 
    } 
    public T getOutOfHole() { 
     return objectInHole; 
    } 

    public String toString() { 
     return objectInHole.toString(); 
    } 
    .....equals, hashCode, etc. 
} 


Hole<String> foo = new Hole<String)(); 
foo.putInHole(new String()); 
System.out.println(foo); //this prints nothing 
thisWorks(foo); 
System.out.println(foo);//this prints howdy 

public static void thisWorks(Hole<String> foo){ 
    foo.putInHole("howdy"); 
} 
+0

Buona risposta, solo l'ultima parte con l'esempio di codice è forse un po 'troppo artificiosa e distrae dalle cose importanti che hai scritto sopra . – eljenso

+0

I puntatori "incontrollati" possono essere confusi e rischiosi. Non tutte le lingue usano puntatori come C/C++ e ti permettono di puntare in modo casuale a luoghi o oggetti dispari. Java ha semplicemente dei puntatori ben controllati. –

0

Questo perché all'interno di "thisDoesntWork", si sta effettivamente distruggendo il valore locale di foo. Se si desidera passare in base a un riferimento in questo modo, è sempre possibile incapsulare la stringa all'interno di un altro oggetto, ad esempio in una matrice.

class Test { 

    public static void main(String[] args) { 
     String [] fooArray = new String[1]; 
     fooArray[0] = new String("foo"); 

     System.out.println("main: " + fooArray[0]); 
     thisWorks(fooArray); 
     System.out.println("main: " + fooArray[0]); 
    } 

    public static void thisWorks(String [] foo){ 
     System.out.println("thisWorks: " + foo[0]); 
     foo[0] = "howdy"; 
     System.out.println("thisWorks: " + foo[0]); 
    } 
} 

risultati nel seguente output:

main: foo 
thisWorks: foo 
thisWorks: howdy 
main: howdy