¿Pasar una cadena por referencia en Java?

161

Estoy acostumbrado a hacer lo siguiente en C:

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

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

Y la salida es:

foo

Sin embargo, en Java, esto no parece funcionar. Supongo que porque el Stringobjeto se copia en lugar de pasar por referenciado . Pensé que las cadenas eran objetos, que siempre se pasan por referencia.

¿Que esta pasando aqui?

Soren Johnson
fuente
2
Aunque hayan sido pasados por referencia (en Java lo que se transmite es una copia del valor de referencia pero eso es otro hilo) objetos String son inmutables, por lo que no quiere trabajar de todos modos
OscarRyz
8
No es el código C.
Alston
@ Phil: Esto tampoco funcionará en C #.
Mangesh

Respuestas:

196

Tienes tres opciones:

  1. Use un StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
  2. Cree una clase de contenedor y pase una instancia del contenedor a su método:

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

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

Desde el punto de vista del rendimiento, StringBuilder suele ser la mejor opción.

Aaron Digulla
fuente
11
Solo tenga en cuenta que StringBuilder no es seguro para subprocesos. En un entorno multiproceso, use la clase StringBuffer o cuide manualmente la sincronización.
Boris Pavlović
12
@ Boris Pavlovic: sí, pero a menos que diferentes hilos utilicen el mismo StringBuilder, lo cual es poco probable para la OMI en cualquier escenario dado, no debería ser un problema. No importa si el método es llamado por diferentes hilos, recibiendo diferentes StringBuilder.
Ravi Wallau
Me gusta más la tercera opción (matriz) porque es la única general. También permite pasar boolean, int, etc. ¿Puede explicar un poco más en detalle el rendimiento de esta solución? Afirma que la primera es mejor desde el punto de vista del rendimiento.
Meolic
@meolic StringBuilderes más rápido s += "..."ya que no asigna nuevos objetos String cada vez. De hecho, cuando escribe código Java como s = "Hello " + name, entonces el compilador generará un código de bytes que crea ay StringBuilderllama append()dos veces.
Aaron Digulla
86

En Java no se pasa nada por referencia . Todo pasa por valor . Las referencias a objetos se pasan por valor. Además, las cadenas son inmutables . Entonces, cuando anexas la cadena aprobada, obtienes una nueva cadena. Puede usar un valor de retorno o pasar un StringBuffer en su lugar.

zedoo
fuente
2
Esto no es una idea falsa, es un concepto
Patrick Cornelissen
41
Ummm ... no, es un error pensar que estás pasando un objeto por referencia. Estás pasando una referencia por valor.
Ed S.
30
Sí, es un error. Es una idea falsa enorme y generalizada. Conduce a una pregunta de entrevista que odio: ("¿Cómo pasa Java los argumentos")? Odio porque aproximadamente la mitad de los entrevistadores parecen querer la respuesta incorrecta ("primitivas por valor, objetos por referencia"). La respuesta correcta lleva más tiempo y parece confundir a algunos de ellos. Y no se convencerán: juro que encendí una pantalla de tecnología porque el examinador de tipo CSMajor había escuchado el error en la universidad y lo creía como un evangelio. Feh
CPerkins el
44
@CPerkins No tienes idea de lo enojado que me pone. Me hierve la sangre.
66
Todo se pasa por valor en todos los idiomas. Algunos de esos valores son direcciones (referencias).
gerardw
52

Lo que sucede es que la referencia se pasa por valor, es decir, se pasa una copia de la referencia. No se pasa nada en Java por referencia, y dado que una cadena es inmutable, esa asignación crea un nuevo objeto de cadena al que ahora apunta la copia de la referencia . La referencia original todavía apunta a la cadena vacía.

Esto sería lo mismo para cualquier objeto, es decir, establecerlo en un nuevo valor en un método. El siguiente ejemplo solo hace que lo que está sucediendo sea más obvio, pero concatenar una cadena es realmente lo mismo.

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}
Ed S.
fuente
66
Excelente ejemplo!
Nickolas
18

java.lang.String es inmutable.

Odio pegar las URL, pero https://docs.oracle.com/javase/10/docs/api/java/lang/String.html es esencial para que puedas leer y comprender si estás en Java.

Ryan Fernandes
fuente
No creo que Soren Johnson no sepa qué es un String, esa no era la pregunta ni lo que vine a buscar aquí después de 15 años de Java.
AHH
8

los objetos se pasan por referencia, las primitivas se pasan por valor.

La cadena no es una primitiva, es un objeto y es un caso especial de objeto.

Esto es para ahorrar memoria. En JVM, hay un grupo de cadenas. Para cada cadena creada, JVM intentará ver si existe la misma cadena en el grupo de cadenas y la señalará si ya existiera.

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

/ * SALIDA * /

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

el == está verificando la dirección de memoria (referencia), y el .equals está verificando el contenido (valor)

janetsmith
fuente
3
No del todo cierto. Java siempre pasa por VALOR; El truco es que los objetos se pasan por referencia, que son pases por valor. (complicado, lo sé). Comprueba este: javadude.com/articles/passbyvalue.htm
adhg
1
@adhg escribió "Los objetos se pasan por referencia, que son pases por valor". <--- eso es galimatías. Quieres decir que los objetos tienen referencias que se pasan por valor
barlop
2
-1 Escribiste "los objetos se pasan por referencia", <- FALSO. Si eso fuera cierto, al llamar a un método y pasar una variable, el método podría hacer que la variable señale / haga referencia a un objeto diferente, pero no puede. Todo se pasa por valor en java. Y aún ve el efecto de cambiar los atributos de un objeto, fuera del método.
barlop
mira la respuesta aquí stackoverflow.com/questions/40480/…
barlop
7

Todos los argumentos en Java se pasan por valor. Cuando pasa un Stringa una función, el valor que se pasa es una referencia a un objeto String, pero no puede modificar esa referencia, y el objeto String subyacente es inmutable.

La asignación

zText += foo;

es equivalente a:

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

Es decir, (localmente) reasigna el parámetro zTextcomo una nueva referencia, que apunta a una nueva ubicación de memoria, en la que hay una nueva Stringque contiene el contenido original de zTextcon "foo"agregado.

El objeto original no se modifica y la main()variable local del método zTexttodavía apunta a la cadena original (vacía).

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

huellas dactilares:

Original value:
Local value: foo
Final value:

Si desea modificar la cadena, puede, como se ha señalado, utilizar StringBuilderun contenedor (una matriz o una AtomicReferenceclase de contenedor personalizada) que le brinde un nivel adicional de indirección de puntero. Alternativamente, solo devuelva el nuevo valor y asígnelo:

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

huellas dactilares:

Original value:
Local value: foo
Final value: foo

Esta es probablemente la solución más similar a Java en el caso general: consulte el elemento de Java efectivo "Favorecer la inmutabilidad".

Sin embargo, como se señaló anteriormente, StringBuildera menudo le brindará un mejor rendimiento, si tiene que agregar mucho, particularmente dentro de un ciclo, use StringBuilder.

Pero intente pasar inmutable en Stringslugar de mutable StringBuilderssi puede: su código será más fácil de leer y más fácil de mantener. Considere hacer sus parámetros finaly configurar su IDE para que le avise cuando reasigne un parámetro de método a un nuevo valor.

David Moles
fuente
66
Dices "estrictamente hablando" como si fuera casi irrelevante, mientras que se encuentra en el corazón del problema. Si Java realmente tuviera una referencia, no sería un problema. Por favor trate de evitar propagar el mito de "Java pasa objetos por referencia" - explicando la (correcta) "referencias se pasan por valor" modelo es mucho más útil, la OMI.
Jon Skeet
Oye, ¿a dónde fue mi comentario anterior? :-) Como dije antes, y como Jon ha agregado ahora, el verdadero problema aquí es que la referencia se pasa por valor. Eso es muy importante y muchas personas no entienden la diferencia semántica.
Ed S.
1
Lo suficientemente justo. Como programador de Java, no he visto nada realmente aprobado por referencia en más de una década, por lo que mi lenguaje se ha vuelto descuidado. Pero tienes razón, debería haber tenido más cuidado, especialmente para una audiencia de programación C.
David Moles
5

La cadena es un objeto inmutable en Java. Puede usar la clase StringBuilder para hacer el trabajo que está tratando de lograr, de la siguiente manera:

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

Otra opción es crear una clase con una Cadena como variable de alcance (altamente desaconsejada) de la siguiente manera:

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";
}
Fadi Hanna AL-Kass
fuente
1
La cadena no es un tipo primitivo en Java.
VWeber
Es cierto, pero ¿a qué estás tratando de atraer mi atención exactamente?
Fadi Hanna AL-Kass
¿Desde cuándo Java String es primitivo? ¡ENTONCES! Cadena pasada por referencia.
thedp
2
'objeto inmutable' es lo que tenía la intención de decir. Yo, por alguna razón, no volví a leer mi respuesta después de que @VWeber hizo un comentario, pero ahora veo lo que estaba tratando de decir. culpa mía. respuesta modificada
Fadi Hanna AL-Kass
2

La respuesta es simple. En java las cuerdas son inmutables. Por lo tanto, es como usar el modificador 'final' (o 'const' en C / C ++). Entonces, una vez asignado, no puede cambiarlo como lo hizo.

Puede cambiar el valor al que apunta una cadena, pero NO puede cambiar el valor real al que apunta actualmente esta cadena.

Es decir. String s1 = "hey". Puede hacer s1 = "woah", y eso está totalmente bien, pero en realidad no puede cambiar el valor subyacente de la cadena (en este caso: "hey") para que sea algo diferente una vez que se asigna usando plusEquals, etc. (es decir s1 += " whatup != "hey whatup").

Para hacer eso, use las clases StringBuilder o StringBuffer u otros contenedores mutables, luego simplemente llame a .toString () para convertir el objeto nuevamente en una cadena.

nota: las cadenas a menudo se usan como claves hash, por lo tanto, esa es una de las razones por las que son inmutables.

ak_2050
fuente
Si es mutable o inmutable no es relevante. =en una referencia NUNCA afecta el objeto al que solía señalar, independientemente de la clase.
newacct
2

String es una clase especial en Java. Es seguro para subprocesos, lo que significa "Una vez que se crea una instancia de String, el contenido de la instancia de String nunca cambiará".

Esto es lo que está pasando por

 zText += "foo";

Primero, el compilador Java obtendrá el valor de la instancia de zText String, luego creará una nueva instancia de String cuyo valor sea zText agregando "foo". Entonces sabe por qué la instancia a la que apunta zText no cambia. Es totalmente una nueva instancia. De hecho, incluso String "foo" es una nueva instancia de String. Entonces, para esta declaración, Java creará dos instancias de String, una es "foo", otra es el valor de zText add "foo". La regla es simple: el valor de la instancia de String nunca cambiará.

Para el método fillString, puede usar un StringBuffer como parámetro, o puede cambiarlo así:

String fillString(String zText) {
    return zText += "foo";
}
DeepNightTwo
fuente
... aunque si tuviera que definir 'fillString' según este código, ¡ realmente debería darle un nombre más apropiado!
Stephen C
Si. Este método debe llamarse "appendString" más o menos.
DeepNightTwo
1

Esto funciona utiliza 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");
}
}

Incluso mejor usar 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");
}
}
Mohammed Rafeeq
fuente
1

La cadena es inmutable en Java. no puede modificar / cambiar, una cadena literal / objeto existente.

Cadena s = "Hola"; s = s + "hola";

Aquí la referencia anterior s se reemplaza por la nueva referencia que apunta al valor "HelloHi".

Sin embargo, para traer mutabilidad tenemos StringBuilder y StringBuffer.

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

esto agrega el nuevo valor "Hola" a la misma referencia s. //

gauravJ
fuente
0

Aaron Digulla tiene la mejor respuesta hasta ahora. Una variación de su segunda opción es usar el contenedor o la clase de contenedor MutableObject de la biblioteca commons lang versión 3+:

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

Guarda la declaración de la clase contenedor. El inconveniente es una dependencia de commons lang lib. Pero la lib tiene una gran cantidad de funciones útiles y casi cualquier proyecto más grande en el que he trabajado la usó.

user2606740
fuente
0

Para pasar un objeto (incluyendo String) por referencia en java, puede pasarlo como miembro de un adaptador circundante. Una solución con un genérico está aquí:

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());
    }
}
Sam Ginrich
fuente
0

Para alguien que es más 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");
    }
}

Ahora, al ejecutar este código anterior, puede ver cómo hashcodescambia la dirección con la variable de cadena s. se asigna un nuevo objeto a la variable s en función changetocuando se cambia s

Rajesh Kumar
fuente