Qualcuno può spiegare a me che cosa il ragionamento dietro di passaggio “valore” e non da “riferimento” in Java?

StackOverflow https://stackoverflow.com/questions/514478

Domanda

Io sono abbastanza nuovo di Java (scritto altre cose per molti anni) e a meno che mi manca qualcosa (e io sono felice di essere di sbagliato qui) il seguente è un difetto fatale...

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 (abbastanza mal formulata) concetto che in java tutto è passato dal "valore" e non "di riferimento", ma la Stringa è un oggetto e ha tutti i tipi di campane e fischietti, così, uno si aspetta che a differenza di un int un utente potrebbe essere in grado di operare sulla cosa che è passato al metodo (e non essere bloccato con il valore impostato per il sovraccarico =).

Qualcuno può spiegare a me che cosa il ragionamento che sta dietro questa scelta progettuale è stata?Come ho detto, io non sto cercando di essere qui, e forse mi sto perdendo qualcosa di ovvio?

È stato utile?

Soluzione

Questo sproloquio lo spiega meglio di quanto io possa mai nemmeno provare a:

  

In Java, primitive sono passati per valore. Tuttavia, gli oggetti non sono   passato per riferimento. Una dichiarazione corretta sarebbe riferimenti agli oggetti   sono passati per valore.

Altri suggerimenti

Quando si passa "foo", si sta passando il di riferimento a "foo" come valore da ThisDoesntWork (). Ciò significa che quando si fa il compito di "pippo" all'interno del vostro metodo, si sono semplicemente impostando una variabile locale (foo) s 'riferimento ad essere un riferimento per la nuova stringa.

Un'altra cosa da tenere a mente quando si pensa a come si comportano le stringhe 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 sono immutabili, si sa la sua dimensione è sempre lo stesso e non sempre hanno a che fare una mossa della struttura dati in memoria quando si manipolarla. Si (il progettista del linguaggio) Inoltre non devono preoccuparsi di attuare la stringa come un lento-lista collegata, sia. Questo riduce in entrambi i modi, però. L'aggiunta dei stringhe solo utilizzando l'operatore + può essere costoso memory-saggio, e si dovrà utilizzare un oggetto StringBuilder per fare questo in un ad alte prestazioni, la memoria modo efficiente.

Ora sul vostro più grande domanda. Perché gli oggetti passati in questo modo? Beh, se Java passata la stringa come quello che si chiama tradizionalmente "per valore", avrebbe dovuto copiare in realtà l'intera stringa prima di passarlo alla funzione. Questo è piuttosto lento. Se passasse la stringa per riferimento e lasciare che si modifica (come C fa), si avrebbe i problemi che ho appena elencato.

Dal momento che la mia risposta originale era: "Perché è successo" e non "Perché il linguaggio progettato così è successo," Ti darò questo altro andare.

Per semplificare le cose, mi sbarazzarsi della chiamata al metodo e mostrare ciò che sta accadendo in un altro modo.

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

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

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

  • I puntatori sono confuse I progettisti di Java provato a rimuovere alcune delle cose più confuse circa altre lingue. I puntatori sono uno dei costrutti più frainteso e impropriamente usati di C / C ++ insieme con l'overloading degli operatori.

  • I puntatori sono rischi per la sicurezza Puntatori causare molti problemi di sicurezza quando abusato. Un programma dannoso assegna qualcosa a quella parte della memoria, allora quello che si pensava fosse l'oggetto è in realtà di qualcun altro. (Java già sbarazzato del più grande problema di sicurezza, buffer overflow, con gli array controllato)

  • Astrazione fuga 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 si insinua quasi certamente in un linguaggio, i progettisti non volevano cuocere in diretta.

  • oggetti sono tutto ciò che interessa In Java, ogni cosa è un oggetto, non lo spazio un oggetto occupa. L'aggiunta di puntatori sarebbe rendere lo spazio un oggetto occupa importantant, anche se .......

Si potrebbe emulare ciò che si vuole con la creazione di un oggetto "Hole". Si potrebbe anche usare farmaci generici per renderlo SICURO tipo. 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");
}

La tua domanda come chiesto in realtà non hanno a che fare con il passare per valore, passando per riferimento, o il fatto che le stringhe sono immutabili (come altri hanno detto).

All'interno del metodo, in realtà si crea una variabile locale (che chiamerò che uno "localFoo") che punta allo stesso riferimento la variabile originale ( "originalFoo").

Quando si assegna "Howdy" per localFoo, non si cambia dove originalFoo sta puntando.

Se hai fatto qualcosa di simile:

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

Ti aspetti:

System.out.print(a)

di stampare "Howdy"? Esso stampa "".

Non si può cambiare ciò che originalFoo punti per cambiando ciò che localFoo punti. È possibile modificare l'oggetto che sia punto (se non fosse 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);
}

In Java tutte le variabili passate sono effettivamente passati in giro da a valore anche oggetti. Tutte le variabili passate ad un metodo sono in realtà le copie del valore originale. Nel caso del vostro esempio stringa il puntatore originale (la sua realtà un riferimento - ma per evitare confusione cattivo uso una parola diversa) viene copiato in una nuova variabile che diventa il parametro al metodo

.

Sarebbe un dolore se tutto fosse per riferimento. Uno avrebbe bisogno di fare copie private di tutto il posto che sarebbe sicuramente un vero e proprio dolore. Tutti sanno che l'uso di immutabilità per i tipi di valore ecc rende i programmi infinitamente più semplice e scalabile.

Alcuni vantaggi includono: - Non c'è bisogno di fare copie difensive. - threadsafe -. Non c'è bisogno di preoccuparsi di blocco nel caso in cui qualcun altro vuole cambiare l'oggetto

Il problema è che si sta istanziare un tipo di riferimento Java. Poi si passa che tipo di riferimento ad un metodo statico, e riassegnare ad un ambito locale variabile.

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

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 rendere tutto fatto in maniera OOP.

Che avrebbero preferito avere a progettare un intero swapper utilizzando oggetti di fornire loro un supporto di prima classe per il passaggio per riferimento, lo stesso per delegato (Gosling si sente icky con puntatore a funzione, avrebbe preferito stipare che funzionalità per gli oggetti) e enum.

Si over-semplificano (tutto è oggetto) la lingua a scapito di non avere il supporto di prima classe per la maggior parte dei costrutti del linguaggio, per esempio passaggio per riferimento, delegati, enum, proprietà viene in mente.

Sei sicuro esso stampa nulla? Penso che sarà solo in bianco come quando si inizializzato la variabile pippo che hai fornito stringa vuota.

L'assegnazione di foo nel metodo thisDoesntWork non sta cambiando il riferimento della variabile foo definito nella classe in modo foo in System.out.println (foo) sarà ancora puntare al vecchio oggetto stringa vuota.

Dave, devi perdonarmi (beh, immagino non si "deve", ma preferirei avete fatto), ma questa spiegazione non è eccessivamente convincente. I guadagni di sicurezza sono piuttosto limitata in quanto chiunque abbia necessità di modificare il valore della stringa sarà trovare un modo per farlo con un po 'brutta soluzione. E la velocità ?! Lei stesso (giustamente) affermi che tutta la faccenda con il + è estremamente costoso.

Il resto di voi ragazzi, vi prego di capire che ho come funziona, mi sto chiedendo perché funziona in questo modo ... si prega di smettere spiegare la differenza tra le metodologie.

(e sinceramente non sto cercando qualsiasi tipo di lotta qui, btw, io non vedo come questa è stata una decisione razionale).

@Axelle

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

In Java anche i riferimenti sono passati per valore. Quando si passa un riferimento a un oggetto che si stanno ottenendo una copia del puntatore di riferimento nella seconda variabile. Tahts perché la seconda variabile possono essere modificate senza influenzare la prima.

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

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";    
}

Se si pensa 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 di un oggetto in quanto è l'identità, allora 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 Java è pass-by-value.

Questo è perché dentro "thisDoesntWork", si sta effettivamente distruggendo il valore locale di foo. Se si desidera passare per riferimento in questo modo, può sempre incapsulare la stringa all'interno di un altro oggetto, ad esempio in un array.

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

Riferimento digitato argomenti vengono passati come riferimenti agli oggetti stessi (non riferimenti ad altri variabili che si riferiscono ad oggetti).È possibile chiamare i metodi sull'oggetto che è stato passato.Tuttavia, nel codice di esempio:

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

si sono solo memorizzare un riferimento alla stringa "howdy" in una variabile locale per il metodo.La variabile locale (foo) è stata inizializzata al valore del chiamante foo quando il metodo è stato chiamato, ma non ha alcun riferimento al chiamante della variabile stessa.Dopo l'inizializzazione:

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

Dopo l'assegnazione nel metodo:

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

Hai un altro problema: String le istanze sono immutabili (dal design, per sicurezza), in modo che non può modificare il suo valore.

Se si vuole veramente il vostro metodo per fornire un valore iniziale di una stringa (o a qualsiasi volta nella sua vita, per quella materia), quindi avere il tuo metodo ritorno un String valore che si assegna al chiamante variabile al momento della chiamata.Qualcosa di simile a questo, per esempio:

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

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

Vai fare il veramente grande tutorial sul sito web soli.

Ti sembra di non capire la differenza ambiti variabili possono essere. "Foo" è locale al metodo. Nulla al di fuori di tale metodo può cambiare ciò che "foo" sottolinea anche. Il "foo" cui si fa riferimento il metodo è un campo completamente diverso - è un campo statico sulla classe di inclusione.

Scoping è particolarmente importante in quanto non volete tutto per essere visibili a tutto il resto nel vostro sistema.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top