Passare una stringa per riferimento in Java?


161

Sono abituato a fare quanto segue in C:

void main() {
    String zText = "";
    fillString(zText);
    printf(zText);
}

void fillString(String zText) {
    zText += "foo";
}

E l'output è:

foo

Tuttavia, in Java, questo non sembra funzionare. Presumo perché l' Stringoggetto viene copiato anziché passato per riferimento . Ho pensato che le stringhe fossero oggetti, che sono sempre passati per riferimento.

Che cosa sta succedendo qui?


2
Anche se sono stati passati per riferimento (in Java ciò che viene passato è una copia del valore di riferimento ma è un altro thread) Gli oggetti stringa SONO immutabili, quindi non funzionerebbero comunque
OscarRyz

8
Non è il codice C.
Alston,

@Phil: questo non funzionerà anche in C #.
Mangesh,

Risposte:


196

Hai tre opzioni:

  1. Usa uno StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
  2. Crea una classe contenitore e passa un'istanza del contenitore al tuo metodo:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
  3. Crea un array:

    new String[] zText = new String[1];
    zText[0] = "";
    
    void fillString(String[] zText) { zText[0] += "foo"; }

Dal punto di vista delle prestazioni, StringBuilder è di solito l'opzione migliore.


11
Tieni presente che StringBuilder non è thread-safe. In ambiente multithread utilizzare la classe StringBuffer o occuparsi manualmente della sincronizzazione.
Boris Pavlović,

12
@Boris Pavlovic - sì, ma a meno che lo stesso StringBuilder non sia utilizzato da thread diversi, il che IMO è improbabile in un dato scenario, non dovrebbe essere un problema. Non importa se il metodo viene chiamato da thread diversi, ricevendo StringBuilder diverso.
Ravi Wallau,

Mi piace di più la terza opzione (array) perché è l'unica generale. Permette anche di passare booleani, int, ecc. Potete per favore spiegare un po 'di più in dettaglio le prestazioni di questa soluzione - affermate che il primo è migliore dal punto di vista delle prestazioni.
Meolic,

@meolic StringBuilderè più veloce s += "..."dal momento che non alloca nuovi oggetti String ogni volta. In effetti quando si scrive codice Java come s = "Hello " + name, quindi il compilatore genererà il codice byte che crea un StringBuildere chiama append()due volte.
Aaron Digulla,

86

In Java nulla viene passato per riferimento . Tutto è passato per valore . I riferimenti agli oggetti vengono passati per valore. Inoltre, le stringhe sono immutabili . Quindi quando aggiungi alla stringa passata ottieni solo una nuova stringa. È possibile utilizzare un valore restituito o passare invece StringBuffer.


2
Questo non è un malinteso, è un concetto
Patrick Cornelissen,

41
Ummm..no, è un'idea sbagliata che stai passando un oggetto per riferimento. Stai passando un riferimento per valore.
Ed S.

30
Sì, è un'idea sbagliata. È un malinteso enorme e diffuso. Conduce a una domanda di intervista che odio: ("come fa Java a passare argomenti"). Lo odio perché circa la metà degli intervistatori sembra davvero voler la risposta sbagliata ("primitivi per valore, oggetti per riferimento"). La risposta giusta richiede più tempo e sembra confonderne alcune. E non ne saranno convinti: giuro di aver bocciato uno schermo tecnico perché lo screener di tipo CSMajor aveva sentito il malinteso al college e lo credeva come un vangelo. Feh.
CPerkins,

4
@CPerkins Non hai idea di quanto mi arrabbi. Mi fa ribollire il sangue.

6
Tutto è passato per valore in tutte le lingue. Alcuni di questi valori sono indirizzi (riferimenti).
Gerardw,

52

Ciò che sta accadendo è che il riferimento viene passato per valore, ovvero viene passata una copia del riferimento. Nulla in java viene passato per riferimento e poiché una stringa è immutabile, tale assegnazione crea un nuovo oggetto stringa a cui ora punta la copia del riferimento . Il riferimento originale punta ancora alla stringa vuota.

Questo sarebbe lo stesso per qualsiasi oggetto, ovvero impostandolo su un nuovo valore in un metodo. L'esempio seguente rende più ovvio ciò che sta succedendo, ma concatenare una stringa è davvero la stessa cosa.

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}

6
Ottimo esempio!
Nickolas,


8

gli oggetti vengono passati per riferimento, le primitive vengono passate per valore.

La stringa non è una primitiva, è un oggetto ed è un caso speciale di oggetto.

Questo è a scopo di risparmio di memoria. In JVM, c'è un pool di stringhe. Per ogni stringa creata, JVM proverà a vedere se esiste la stessa stringa nel pool di stringhe e indicherà se ce n'era già una.

public class TestString
{
    private static String a = "hello world";
    private static String b = "hello world";
    private static String c = "hello " + "world";
    private static String d = new String("hello world");

    private static Object o1 = new Object();
    private static Object o2 = new Object();

    public static void main(String[] args)
    {
        System.out.println("a==b:"+(a == b));
        System.out.println("a==c:"+(a == c));
        System.out.println("a==d:"+(a == d));
        System.out.println("a.equals(d):"+(a.equals(d)));
        System.out.println("o1==o2:"+(o1 == o2));

        passString(a);
        passString(d);
    }

    public static void passString(String s)
    {
        System.out.println("passString:"+(a == s));
    }
}

/* PRODUZIONE */

a==b:true
a==c:true
a==d:false
a.equals(d):true
o1==o2:false
passString:true
passString:false

== sta controllando l'indirizzo di memoria (riferimento) e .equals sta controllando il contenuto (valore)


3
Non del tutto vero. Java è sempre passato da VALUE; il trucco è che gli oggetti vengono passati per riferimento che sono passati per valore. (difficile, lo so). Dai
un'occhiata a

1
@adhg hai scritto "Gli oggetti vengono passati per riferimento che sono passati per valore." <--- è incomprensibile. Vuoi dire che gli oggetti hanno riferimenti che sono passati per valore
barlop

2
-1 Hai scritto "gli oggetti vengono passati per riferimento" <- FALSE. Se ciò fosse vero, quando si chiama un metodo e si passa una variabile, il metodo potrebbe fare in modo che la variabile punti / faccia riferimento a un oggetto diverso, ma non è possibile. Tutto è passato per valore in Java. E vedi ancora l'effetto di cambiare gli attributi di un oggetto, al di fuori del metodo.
barlop


7

Tutti gli argomenti in Java vengono passati per valore. Quando si passa Stringa una funzione, il valore che viene passato è un riferimento a un oggetto String, ma non è possibile modificarlo e l'oggetto String sottostante è immutabile.

L'incarico

zText += foo;

è equivalente a:

zText = new String(zText + "foo");

Cioè, (localmente) riassegna il parametro zTextcome un nuovo riferimento, che punta a una nuova posizione di memoria, in cui è un nuovoString che contiene il contenuto originale di zTextcon "foo"allegato.

L'oggetto originale non viene modificato e la main()variabile locale del metodo zTextpunta ancora alla stringa originale (vuota).

class StringFiller {
  static void fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

stampe:

Original value:
Local value: foo
Final value:

Se si desidera modificare la stringa, è possibile utilizzare come indicato StringBuilder oppure un contenitore (un array o una AtomicReferenceo una classe di contenitore personalizzata) che fornisce un ulteriore livello di indirizzamento del puntatore. In alternativa, è sufficiente restituire il nuovo valore e assegnarlo:

class StringFiller2 {
  static String fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
    return zText;
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    zText = fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

stampe:

Original value:
Local value: foo
Final value: foo

Questa è probabilmente la soluzione più simile a Java nel caso generale - vedi il elemento Java efficace "Favorisci l'immutabilità".

Come notato, però, StringBuilder spesso ti darà prestazioni migliori - se hai molto da aggiungere, in particolare all'interno di un ciclo, usa StringBuilder.

Ma se possibile, cerca di passare da immutabile Stringspiuttosto che mutabile StringBuilders: il tuo codice sarà più facile da leggere e più gestibile. Prendi in considerazione la possibilità di impostare i parametri finale configurare l'IDE per avvisarti quando riassegni un parametro di metodo a un nuovo valore.


6
Dici "in senso stretto" come se fosse quasi irrilevante, mentre è al centro del problema. Se Java avesse davvero passato il riferimento, non sarebbe stato un problema. Si prega di cercare di evitare di propagare il mito di "Java passa oggetti per riferimento" - che spiega la (corretta) "i riferimenti sono passati per valore" modello è molto più utile, IMO.
Jon Skeet,

Ehi, dove è andato il mio commento precedente? :-) Come ho detto prima, e come ha ora aggiunto Jon, il vero problema qui è che il riferimento viene passato per valore. Questo è molto importante e molte persone non comprendono la differenza semantica.
Ed S.

1
Giusto. Come programmatore Java non ho visto nulla di veramente passato per riferimento da oltre un decennio, quindi il mio linguaggio è diventato sciatto. Ma hai ragione, avrei dovuto occuparmi di più, soprattutto per un pubblico di programmazione in C.
David Moles,

5

String è un oggetto immutabile in Java. È possibile utilizzare la classe StringBuilder per eseguire il lavoro che si sta tentando di eseguire, come segue:

public static void main(String[] args)
{
    StringBuilder sb = new StringBuilder("hello, world!");
    System.out.println(sb);
    foo(sb);
    System.out.println(sb);

}

public static void foo(StringBuilder str)
{
    str.delete(0, str.length());
    str.append("String has been modified");
}

Un'altra opzione è quella di creare una classe con una stringa come variabile di ambito (altamente sconsigliata) come segue:

class MyString
{
    public String value;
}

public static void main(String[] args)
{
    MyString ms = new MyString();
    ms.value = "Hello, World!";

}

public static void foo(MyString str)
{
    str.value = "String has been modified";
}

1
String non è un tipo primitivo in Java.
VWeber,

Vero, ma a cosa stai esattamente cercando di attirare la mia attenzione?
Fadi Hanna AL-Kass

Da quando Java String è una primitiva ?! COSÌ! Stringa passata per riferimento.
thedp

2
"oggetto immutabile" è ciò che intendevo dire. Io, per qualche motivo, non ho riletto la mia risposta dopo che @VWeber ha fatto un commento, ma ora vedo cosa stava cercando di dire. colpa mia. risposta modificata
Fadi Hanna AL-Kass,

2

La risposta è semplice Nelle stringhe Java sono immutabili. Quindi è come usare il modificatore 'final' (o 'const' in C / C ++). Quindi, una volta assegnato, non puoi cambiarlo come hai fatto tu.

È possibile modificare il quale il valore a cui punta uno stringa, ma non è possibile modificare il valore effettivo a cui questa stringa sta puntando.

Vale a dire. String s1 = "hey". Puoi fare s1 = "woah", e questo è del tutto ok, ma non puoi effettivamente cambiare il valore sottostante della stringa (in questo caso: "hey") in qualcos'altro una volta assegnato usando plusEquals, ecc. (Es. s1 += " whatup != "hey whatup").

Per fare ciò, usa le classi StringBuilder o StringBuffer o altri contenitori mutabili, quindi chiama .toString () per riconvertire l'oggetto in una stringa.

nota: le stringhe vengono spesso utilizzate come chiavi hash, quindi fa parte del motivo per cui sono immutabili.


Che sia mutabile o immutabile non è rilevante. =su un riferimento MAI influisce sull'oggetto a cui puntava, indipendentemente dalla classe.
newacct

2

String è una classe speciale in Java. È Thread Safe che significa "Una volta creata un'istanza String, il contenuto dell'istanza String non verrà mai modificato".

Ecco cosa sta succedendo

 zText += "foo";

Innanzitutto, il compilatore Java otterrà il valore dell'istanza String zText, quindi creerà una nuova istanza String il cui valore è zText aggiungendo "pippo". Quindi sai perché l'istanza a cui punta zText non è cambiata. È totalmente una nuova istanza. In effetti, anche String "pippo" è una nuova istanza di String. Quindi, per questa affermazione, Java creerà due istanze String, una è "pippo", un'altra è il valore di zText append "pippo". La regola è semplice: il valore dell'istanza String non verrà mai modificato.

Per il metodo fillString, puoi utilizzare StringBuffer come parametro oppure puoi modificarlo in questo modo:

String fillString(String zText) {
    return zText += "foo";
}

... anche se se dovessi definire 'fillString' secondo questo codice, dovresti davvero dargli un nome più appropriato!
Stephen C,

Sì. Questo metodo dovrebbe essere denominato "appendString" o giù di lì.
DeepNightDue


1

Questo funziona con StringBuffer

public class test {
 public static void main(String[] args) {
 StringBuffer zText = new StringBuffer("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuffer zText) {
    zText .append("foo");
}
}

Ancora meglio usare StringBuilder

public class test {
 public static void main(String[] args) {
 StringBuilder zText = new StringBuilder("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuilder zText) {
    zText .append("foo");
}
}

1

String è immutabile in Java. non è possibile modificare / cambiare una stringa / oggetto letterale esistente.

String s = "Hello"; s = s + "hi";

Qui i riferimenti precedenti sono sostituiti dai nuovi riferimenti che puntano al valore "HelloHi".

Tuttavia, per apportare mutabilità abbiamo StringBuilder e StringBuffer.

StringBuilder s = new StringBuilder (); s.append ( "Ciao");

questo aggiunge il nuovo valore "Ciao" agli stessi riferimenti. //


0

Aaron Digulla ha finora la risposta migliore. Una variante della sua seconda opzione consiste nell'utilizzare la classe wrapper o container MutableObject della libreria lang commons versione 3+:

void fillString(MutableObject<String> c) { c.setValue(c.getValue() + "foo"); }

si salva la dichiarazione della classe contenitore. Lo svantaggio è una dipendenza dalla lingua comune. Ma la lib ha molte funzioni utili e quasi tutti i progetti più grandi su cui ho lavorato l'hanno usata.


0

Per passare un oggetto (incluso String) per riferimento in Java, è possibile passarlo come membro di un adattatore circostante. Una soluzione con un generico è qui:

import java.io.Serializable;

public class ByRef<T extends Object> implements Serializable
{
    private static final long serialVersionUID = 6310102145974374589L;

    T v;

    public ByRef(T v)
    {
        this.v = v;
    }

    public ByRef()
    {
        v = null;
    }

    public void set(T nv)
    {
        v = nv;
    }

    public T get()
    {
        return v;
    }

// ------------------------------------------------------------------

    static void fillString(ByRef<String> zText)
    {
        zText.set(zText.get() + "foo");
    }

    public static void main(String args[])
    {
        final ByRef<String> zText = new ByRef<String>(new String(""));
        fillString(zText);
        System.out.println(zText.get());
    }
}

0

Per qualcuno che è più curioso

class Testt {
    static void Display(String s , String varname){
        System.out.println(varname + " variable data = "+ s + " :: address hashmap =  " + s.hashCode());
    }

    static void changeto(String s , String t){
        System.out.println("entered function");
        Display(s , "s");
        s = t ;
        Display(s,"s");
        System.out.println("exiting function");
    }

    public static void main(String args[]){
        String s =  "hi" ;
        Display(s,"s");
        changeto(s,"bye");
        Display(s,"s");
    }
}

Ora eseguendo questo codice sopra puoi vedere come hashcodescambia l' indirizzo con la variabile String s. un nuovo oggetto viene assegnato alla variabile s in funzione changetoquando s viene modificato

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.