Passando uma String por Referência em Java?

161

Estou acostumado a fazer o seguinte em C:

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

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

E a saída é:

foo

No entanto, em Java, isso não parece funcionar. Presumo que o Stringobjeto seja copiado em vez de passado por referenciado . Eu pensei que Strings eram objetos, que são sempre passados ​​por referência.

O que está acontecendo aqui?

Soren Johnson
fonte
2
Mesmo que eles foram passados por referência (em Java que é passado é uma cópia do valor de referência, mas isso é outra discussão) objetos String são imutáveis, de modo que não iria funcionar de qualquer maneira
OscarRyz
8
Não é um código C.
Alston
@ Phil: Isso também não funcionará em C #.
Mangesh 07/07

Respostas:

196

Você tem três opções:

  1. Use um StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
    
  2. Crie uma classe de contêiner e passe uma instância do contêiner para o seu método:

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

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

Do ponto de vista do desempenho, o StringBuilder é geralmente a melhor opção.

Aaron Digulla
fonte
11
Lembre-se de que o StringBuilder não é seguro para threads. No ambiente multithread, use a classe StringBuffer ou cuide manualmente da sincronização.
Boris Pavlović
12
@ Boris Pavlovic - sim, mas a menos que o mesmo StringBuilder seja usado por threads diferentes, o que é improvável na IMO em qualquer cenário, não deve ser um problema. Não importa se o método é chamado por threads diferentes, recebendo StringBuilder diferente.
Ravi Wallau
Eu gosto mais da terceira opção (array) porque é a única geral. Permite também passar booleano, int, etc. Você pode explicar um pouco mais detalhadamente o desempenho desta solução - você afirma que o primeiro é melhor do ponto de vista do desempenho.
meolic
O @meolic StringBuilderé mais rápido que, s += "..."uma vez que não aloca novos objetos String sempre. De fato, quando você escreve código Java s = "Hello " + name, o compilador gera um código de bytes que cria a StringBuildere chama append()duas vezes.
Aaron Digulla 7/19
86

Em Java, nada é passado por referência . Tudo é passado por valor . Referências a objetos são passadas por valor. Além disso, as seqüências de caracteres são imutáveis . Então, quando você anexa à String passada, obtém uma nova String. Você pode usar um valor de retorno ou passar um StringBuffer.

zedoo
fonte
2
Este não é um equívoco, é um conceito
Patrick Cornelissen
41
Ummm ... não, é um equívoco que você esteja passando um objeto por referência. Você está passando uma referência por valor.
Ed S.
30
Sim, é um equívoco. É um equívoco enorme e generalizado. Isso leva a uma pergunta de entrevista que eu odeio: ("como o Java passa argumentos"). Eu odeio isso, porque aproximadamente metade dos entrevistadores parece querer a resposta errada ("primitivas por valor, objetos por referência"). A resposta certa leva mais tempo para dar e parece confundir alguns deles. E eles não ficarão convencidos: eu juro que fui reprovado em uma tela de tecnologia porque o examinador do tipo CSMajor ouvira o equívoco na faculdade e acreditava nisso como evangelho. Feh.
cperkins
4
@CPerkins Você não tem idéia do quão zangado isso me deixa. Isso faz meu sangue ferver.
6
Tudo é passado por valor em todas as línguas. Alguns desses valores são endereços (referências).
Gerardw
52

O que está acontecendo é que a referência é passada por valor, ou seja, uma cópia da referência é passada. Nada no java é passado por referência e, como uma sequência é imutável, essa atribuição cria um novo objeto de sequência para o qual a cópia da referência agora aponta. A referência original ainda aponta para a cadeia vazia.

Isso seria o mesmo para qualquer objeto, ou seja, defini-lo com um novo valor em um método. O exemplo abaixo apenas torna o que está acontecendo mais óbvio, mas concatenar uma string é realmente a mesma coisa.

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

java.lang.String é imutável.

Eu odeio colar URLs, mas https://docs.oracle.com/javase/10/docs/api/java/lang/String.html é essencial para você ler e entender se estiver em Java.

Ryan Fernandes
fonte
Eu não acho que Soren Johnson não saiba o que é uma String, essa não foi a pergunta e não o que eu procurei aqui após 15 anos de Java.
AHH
8

objetos são passados ​​por referência, primitivos são passados ​​por valor.

String não é um primitivo, é um objeto e é um caso especial de objeto.

Isso é para fins de economia de memória. Na JVM, há um conjunto de cadeias. Para cada sequência criada, a JVM tentará verificar se a mesma sequência existe no conjunto de sequências e apontará para ela se já houver uma.

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

/* RESULTADO */

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

o == está verificando o endereço da memória (referência) e o .equals está verificando o conteúdo (valor)

janetsmith
fonte
3
Não é inteiramente verdade. Java sempre passa por VALUE; o truque é que os objetos são passados ​​por referência, que são passados por valor. (complicado, eu sei). Verifique este: javadude.com/articles/passbyvalue.htm
adhg
1
@ adhg você escreveu "Os objetos são passados ​​por referência, que são passados ​​por valor". <--- isso é sem sentido. Você objetos médios têm referências que são passados por valor
barlop
2
-1 Você escreveu "os objetos são passados ​​por referência", <- FALSE. Se isso fosse verdade, ao chamar um método e transmitir uma variável, o método poderia fazer a variável apontar / se referir a um objeto diferente, mas não pode. Tudo é passado por valor em java. E você ainda vê o efeito de alterar os atributos de um objeto, fora do método.
barlop
veja a resposta aqui stackoverflow.com/questions/40480/…
barlop 5/16
7

Todos os argumentos em Java são passados ​​por valor. Quando você passa a Stringpara uma função, o valor passado é uma referência a um objeto String, mas você não pode modificar essa referência, e o objeto String subjacente é imutável.

A atribuição

zText += foo;

é equivalente a:

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

Ou seja, (localmente) reatribui o parâmetro zTextcomo uma nova referência, que aponta para um novo local de memória, no qual é um novo Stringque contém o conteúdo original zTextcom "foo"anexado.

O objeto original não é modificado e a main()variável local do método zTextainda aponta para a string original (vazia).

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

impressões:

Original value:
Local value: foo
Final value:

Se você deseja modificar a string, pode usar como observado, StringBuilderou algum contêiner (uma matriz ou uma AtomicReferenceclasse de contêiner personalizada) que fornece um nível adicional de indireto de ponteiro. Como alternativa, basta retornar o novo valor e atribuí-lo:

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

impressões:

Original value:
Local value: foo
Final value: foo

Essa é provavelmente a solução mais parecida com Java no caso geral - consulte o item Java eficaz "Favor da imutabilidade".

Porém, conforme observado, StringBuildermuitas vezes proporcionará um melhor desempenho - se você tiver muito o que fazer, especialmente dentro de um loop, use StringBuilder.

Mas tente passar imutável, em Stringsvez de mutável, StringBuildersse puder - seu código será mais fácil de ler e mais sustentável. Considere criar seus parâmetros finale configurar seu IDE para avisá-lo quando você reatribuir um parâmetro de método para um novo valor.

David Moles
fonte
6
Você diz "estritamente falando" como se isso fosse quase irrelevante - enquanto isso está no cerne do problema. Se o Java realmente tivesse passado por referência, não seria um problema. Por favor, tente evitar a propagação do mito de "Java passa objetos por referência" - explicar o modelo (correto) "referências são passadas por valor" é muito mais útil, IMO.
9139 Jon Skeet
Ei, para onde foi meu comentário anterior? :-) Como eu disse antes, e como Jon acrescentou agora, o verdadeiro problema aqui é que a referência é passada por valor. Isso é muito importante e muitas pessoas não entendem a diferença semântica.
13559 Ed S.
1
Justo. Como programador Java, não vejo nada realmente ser passado por referência há mais de uma década, então minha linguagem ficou desleixada. Mas você está certo, eu deveria ter tomado mais cuidado, especialmente para um público de programação em C.
David Moles
5

String é um objeto imutável em Java. Você pode usar a classe StringBuilder para executar o trabalho que está tentando realizar, da seguinte maneira:

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

Outra opção é criar uma classe com uma String como uma variável de escopo (altamente desencorajada) da seguinte maneira:

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
fonte
1
String não é um tipo primitivo em Java.
VWeber
É verdade, mas o que exatamente você está tentando chamar minha atenção?
Fadi Hanna AL-Kass
Desde quando o Java String é um primitivo ?! TÃO! String passada por referência.
Thedp
2
'objeto imutável' é o que eu pretendia dizer. Por algum motivo, eu não reli minha resposta depois que o @VWeber fez um comentário, mas agora vejo o que ele estava tentando dizer. foi mal. resposta modificada
Fadi Hanna AL-Kass
2

A resposta é simples. Em Java, as strings são imutáveis. Portanto, é como usar o modificador 'final' (ou 'const' em C / C ++). Portanto, uma vez atribuído, você não pode alterá-lo da maneira que fez.

Você pode alterar qual valor para o qual uma string aponta, mas NÃO pode alterar o valor real para o qual essa string está apontando no momento.

Ou seja. String s1 = "hey". Você pode criar s1 = "woah", e isso é totalmente aceitável, mas na verdade não é possível alterar o valor subjacente da string (neste caso: "ei") para ser outra coisa uma vez atribuída usando plusEquals, etc. (ou seja, s1 += " whatup != "hey whatup").

Para fazer isso, use as classes StringBuilder ou StringBuffer ou outros contêineres mutáveis ​​e chame .toString () para converter o objeto novamente em uma string.

note: As strings são frequentemente usadas como chaves de hash, portanto, isso é parte da razão pela qual são imutáveis.

ak_2050
fonte
Se é mutável ou imutável, não é relevante. =em uma referência NUNCA afeta o objeto usado para apontar, independentemente da classe.
Newacct
2

String é uma classe especial em Java. É Thread Safe, o que significa "Depois que uma instância de String é criada, o conteúdo da instância de String nunca será alterado".

Aqui está o que está acontecendo

 zText += "foo";

Primeiro, o compilador Java obterá o valor da instância do zText String e, em seguida, criará uma nova instância do String cujo valor é o zText acrescentando "foo". Então você sabe por que a instância para a qual o zText aponta não mudou. É totalmente uma nova instância. De fato, mesmo String "foo" é uma nova instância de String. Portanto, para esta declaração, o Java criará duas instâncias de String, uma é "foo", outra é o valor do zText, anexado "foo". A regra é simples: o valor da instância String nunca será alterado.

Para o método fillString, você pode usar um StringBuffer como parâmetro ou pode alterá-lo assim:

String fillString(String zText) {
    return zText += "foo";
}
DeepNightTwo
fonte
... embora se você definisse 'fillString' de acordo com esse código, realmente deveria dar um nome mais apropriado!
Stephen C
Sim. Este método deve ser nomeado para "appendString" ou mais.
DeepNightTwo
1

Strings são imutáveis em Java.

Grzegorz Oledzki
fonte
1

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

Melhor ainda usar o 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
fonte
1

String é imutável em java. você não pode modificar / alterar, uma string / objeto existente existente.

String s = "Olá"; s = s + "oi";

Aqui a referência anterior s é substituída pela nova referência s apontando para o valor "HelloHi".

No entanto, para trazer mutabilidade, temos StringBuilder e StringBuffer.

StringBuilder s = novo StringBuilder (); s.append ("Oi");

isso acrescenta o novo valor "Hi" à mesma referência s. //

gauravJ
fonte
0

Aaron Digulla tem a melhor resposta até agora. Uma variação de sua segunda opção é usar a classe wrapper ou container MutableObject da biblioteca commang lang versão 3+:

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

você salva a declaração da classe de contêiner. A desvantagem é uma dependência dos comuns lang lib. Mas a lib tem muitas funções úteis e quase qualquer projeto maior em que trabalhei a utilizou.

user2606740
fonte
0

Para passar um objeto (incluindo String) por referência em java, você pode transmiti-lo como membro de um adaptador circundante. Uma solução com um genérico está aqui:

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
fonte
0

Para alguém que é mais 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");
    }
}

Agora, executando esse código acima, você pode ver como o endereço hashcodesmuda com a variável String s. um novo objeto é alocado para a variável s na função changetoquando s é alterado

Rajesh kumar
fonte