Java é "passagem por referência" ou "passagem por valor"?

6580

Eu sempre pensei que o Java era passado por referência .

No entanto, eu vi algumas postagens de blog (por exemplo, este blog ) que afirmam que não são.

Acho que não entendo a distinção que eles estão fazendo.

Qual a explicação?

usuário
fonte
706
Acredito que grande parte da confusão sobre esse assunto esteja relacionada ao fato de pessoas diferentes terem definições diferentes do termo "referência". Pessoas provenientes de um plano de fundo do C ++ assumem que "referência" deve significar o que isso significava em C ++, pessoas de um plano de fundo do C assumem que "referência" deve ser o mesmo que "ponteiro" em seu idioma e assim por diante. Se é correto dizer que o Java passa por referência, depende realmente do significado de "referência".
Gravity
119
Tento usar consistentemente a terminologia encontrada no artigo Estratégia de Avaliação . Deve-se notar que, embora o artigo indique que os termos variam muito de acordo com a comunidade, enfatiza que a semântica call-by-valuee a call-by-referencediferença são muito importantes . (Pessoalmente eu prefiro usar call-by-object-sharingestes dias mais call-by-value[-of-the-reference], que este descreve a semântica em um alto nível e não cria um conflito com call-by-value, que é a implementação subjacente.)
59
@ Gravity: Você pode colocar seu comentário em um enorme outdoor ou algo assim? Essa é a questão toda em poucas palavras. E mostra que tudo isso é semântica. Se não concordar com a definição de base de uma referência, então não vamos chegar a acordo sobre a resposta a esta pergunta :)
MadConan
30
Eu acho que a confusão é "passar por referência" versus "semântica de referência". Java é transmitido por valor com semântica de referência.
spraff
11
@ Gravity, enquanto você estiver absolutamente certo de que as pessoas provenientes de C ++ terão instintivamente um conjunto de intuições diferente em relação ao termo "referência", eu pessoalmente acredito que o corpo está mais enterrado no "por". "Passar" é confuso, pois é absolutamente distinto de "Passar um" em Java. Em C ++, no entanto, coloquialmente não é. Em C ++, você pode dizer "passando uma referência" e entende-se que ele passará no swap(x,y)teste .

Respostas:

5844

Java é sempre transmitido por valor . Infelizmente, quando passamos o valor de um objeto, estamos passando a referência a ele. Isso é confuso para iniciantes.

É assim:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

No exemplo acima aDog.getName()ainda retornará "Max". O valor aDogdentro mainnão é alterado na função foocom a Dog "Fifi"medida que a referência do objeto é passada por valor. Se fosse passado por referência, o aDog.getName()in mainretornaria "Fifi"após a chamada para foo.

Da mesma forma:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

No exemplo acima, Fifié o nome do cão após a chamada para, foo(aDog)porque o nome do objeto foi definido dentro de foo(...). Quaisquer operações que fooexecuta em dsão tais que, para todos os efeitos práticos, eles são realizados em aDog, mas é não possível alterar o valor da variável aDogem si.

erlando
fonte
263
Não é um pouco confuso o problema com detalhes internos? Não há diferença conceitual entre 'passar uma referência' e 'passar o valor de uma referência', supondo que você queira dizer 'o valor do ponteiro interno para o objeto'.
izb
383
Mas há uma diferença sutil. Veja o primeiro exemplo. Se fosse puramente passado por referência, aDog.name seria "Fifi". Não é - a referência que você está recebendo é uma referência de valor que, se substituída, será restaurada ao sair da função.
erlando
300
@ Lorenzo: Não, em Java tudo passa por valor. As primitivas são passadas por valor e as referências de objeto são passadas por valor. Os próprios objetos nunca são passados ​​para um método, mas os objetos estão sempre na pilha e apenas uma referência ao objeto é passada ao método.
31700 Esko Luontola
294
Minha tentativa de uma boa maneira de visualizar a passagem de objetos: imagine um balão. Chamar um fxn é como amarrar uma segunda corda no balão e entregar a linha ao fxn. O parâmetro = new Balloon () cortará a corda e criará um novo balão (mas isso não afeta o balão original). parameter.pop () ainda o exibirá porque segue a string para o mesmo balão original. Java é transmitido por valor, mas o valor transmitido não é profundo, está no nível mais alto, isto é, um primitivo ou um ponteiro. Não confunda isso com um valor de passagem por valor profundo, onde o objeto é totalmente clonado e passado.
dhackner
150
O que é confuso é que as referências a objetos são realmente ponteiros. No começo, o SUN os chamou de ponteiros. Então o marketing informou que "ponteiro" era uma palavra ruim. Mas você ainda vê a nomenclatura "correta" em NullPointerException.
O contrato do Prof. Falken violou
3035

Acabei de perceber que você fez referência ao meu artigo .

A especificação Java diz que tudo em Java é transmitido por valor. Não existe "passagem por referência" em Java.

A chave para entender isso é que algo como

Dog myDog;

não é um cachorro; é na verdade um ponteiro para um cachorro.

O que isso significa é quando você tem

Dog myDog = new Dog("Rover");
foo(myDog);

você está basicamente passando o endereço do Dogobjeto criado para o foométodo

(Digo essencialmente porque os ponteiros Java não são endereços diretos, mas é mais fácil pensar neles dessa maneira)

Suponha que o Dogobjeto resida no endereço de memória 42. Isso significa que passamos 42 para o método.

se o método fosse definido como

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

vamos ver o que está acontecendo.

  • o parâmetro someDogestá definido para o valor 42
  • na linha "AAA"
    • someDogé seguido para o Dogque aponta (o Dogobjeto no endereço 42)
    • que Dog(aquele no endereço 42) é solicitado a mudar seu nome para Max
  • na linha "BBB"
    • um novo Dogé criado. Digamos que ele esteja no endereço 74
    • nós atribuímos o parâmetro someDoga 74
  • na linha "CCC"
    • someDog é seguido para o Dogque aponta (o Dogobjeto no endereço 74)
    • que Dog(aquele no endereço 74) é solicitado a mudar seu nome para Rowlf
  • então voltamos

Agora vamos pensar sobre o que acontece fora do método:

Mudou myDog?

Aí está a chave.

Tendo em mente que myDogé um ponteiro , e não um real Dog, a resposta é NÃO. myDogainda tem o valor 42; ainda está apontando para o original Dog(mas observe que, devido à linha "AAA", seu nome é agora "Max" - ainda é o mesmo cão; myDogo valor de Dog não mudou.)

É perfeitamente válido seguir um endereço e alterar o que está no final dele; isso não altera a variável, no entanto.

Java funciona exatamente como C. Você pode atribuir um ponteiro, passar o ponteiro para um método, seguir o ponteiro no método e alterar os dados apontados. No entanto, você não pode alterar para onde esse ponteiro aponta.

Em C ++, Ada, Pascal e outras linguagens que suportam passagem por referência, você pode realmente alterar a variável que foi passada.

Se o Java tivesse semântica de passagem por referência, o foométodo que definimos acima teria mudado para onde myDogestava apontando quando atribuído someDogna linha BBB.

Pense nos parâmetros de referência como aliases para a variável transmitida. Quando esse alias é atribuído, o mesmo ocorre com a variável transmitida.

Scott Stanchfield
fonte
164
É por isso que o refrão comum "Java não tem ponteiros" é tão enganador.
Beska
134
Você está errado, imho. "Tendo em mente que o myDog é um ponteiro, e não um cão real, a resposta é NÃO. O myDog ainda tem o valor 42; ainda está apontando para o cão original". myDog tem valor 42, mas seu argumento name agora contém "Max", em vez de "Rover" na linha // AAA.
Aprzgür
180
Pense nisso desta maneira. Alguém tem o endereço de Ann Arbor, MI (minha cidade natal, VAI AZUL!) Em um pedaço de papel chamado "annArborLocation". Você o copia em um pedaço de papel chamado "myDestination". Você pode dirigir até "myDestination" e plantar uma árvore. Você pode ter alterado algo sobre a cidade nesse local, mas isso não altera o LAT / LON que foi escrito em qualquer um dos papéis. Você pode alterar o LAT / LON em "myDestination", mas não altera "annArborLocation". Isso ajuda?
Scott Stanchfield
43
@ Scott Stanchfield: Eu li o seu artigo há cerca de um ano e realmente ajudou a esclarecer as coisas para mim. Obrigado! Posso sugerir humildemente uma pequena adição: você deve mencionar que, na verdade, existe um termo específico que descreve essa forma de "chamada por valor onde o valor é uma referência" que foi inventada por Barbara Liskov para descrever a estratégia de avaliação de sua linguagem CLU em 1974, para evitar confusão como a que o seu artigo aborda: chamada por compartilhamento (às vezes chamada chamada por compartilhamento de objeto ou simplesmente chamada por objeto ), que descreve perfeitamente a semântica.
Jörg W Mittag
29
@ Gevorg - As linguagens C / C ++ não possuem o conceito de "ponteiro". Existem outras linguagens que usam ponteiros, mas não permitem os mesmos tipos de manipulação de ponteiros que o C / C ++ permite. Java tem ponteiros; eles estão apenas protegidos contra travessuras.
Scott Stanchfield
1740

Java sempre passa argumentos por valor , NÃO por referência.


Deixe-me explicar isso através de um exemplo :

public class Main {

     public static void main(String[] args) {
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }

     public static void changeReference(Foo a) {
          Foo b = new Foo("b");
          a = b;
     }

     public static void modifyReference(Foo c) {
          c.setAttribute("c");
     }

}

Vou explicar isso em etapas:

  1. Declarando uma referência nomeada fdo tipo Fooe atribuir-lhe um novo objeto do tipo Foocom um atributo "f".

    Foo f = new Foo("f");

    insira a descrição da imagem aqui

  2. Do lado do método, uma referência do tipo Foocom um nome aé declarada e é inicialmente atribuída null.

    public static void changeReference(Foo a)

    insira a descrição da imagem aqui

  3. À medida que você chama o método changeReference, aserá atribuído à referência o objeto que é passado como argumento.

    changeReference(f);

    insira a descrição da imagem aqui

  4. Declarando uma referência nomeada bdo tipo Fooe atribuir-lhe um novo objeto do tipo Foocom um atributo "b".

    Foo b = new Foo("b");

    insira a descrição da imagem aqui

  5. a = bfaz uma nova atribuição à referência a, não f ao objeto cujo atributo é "b".

    insira a descrição da imagem aqui

  6. Como você chamar modifyReference(Foo c)método, uma referência cé criada e atribuída o objeto com o atributo "f".

    insira a descrição da imagem aqui

  7. c.setAttribute("c");mudará o atributo do objeto que faz referência ca ele e é o mesmo objeto que faz referência fa ele.

    insira a descrição da imagem aqui

Espero que você entenda agora como a passagem de objetos como argumentos funciona em Java :)

Eng.Fouad
fonte
82
+1 Coisas legais. bons diagramas. Eu também encontrei uma boa página sucinta aqui adp-gmbh.ch/php/pass_by_reference.html OK, admito que esteja escrita em PHP, mas é o princípio de entender a diferença que eu acho importante (e como manipular essa diferença para suas necessidades).
Davem
5
@ Eng.Fouad É uma boa explicação, mas se aapontar para o mesmo objeto que f(e nunca conseguir sua própria cópia do objeto fapontar para), quaisquer alterações feitas no objeto feitas adeverão ser modificadas ftambém (já que ambos estão trabalhando com o mesmo objeto) ), portanto, em algum momento, é anecessário obter sua própria cópia do objeto fpara o qual aponta.
0x6C38 15/07/2013
14
@MrD quando 'a' aponta para o mesmo objeto 'f' também aponta, então qualquer alteração feita nesse objeto via 'a' também é observável via 'f', MAS NÃO MUDOU 'f'. 'f' ainda aponta para o mesmo objeto. Você pode mudar totalmente o objeto, mas nunca pode mudar o que 'f' aponta. Esta é a questão fundamental que, por algum motivo, algumas pessoas simplesmente não conseguem compreender.
Mike Braun
@MikeBraun ... what? Agora você me confundiu: S. O que você escreveu não é contrário ao que 6. e 7. mostram?
Máquina de lavar malvada
6
Esta é a melhor explicação que encontrei. A propósito, e a situação básica? Por exemplo, o argumento requer um tipo int, ele ainda passa a cópia de uma variável int para o argumento?
allenwang
732

Isso lhe dará algumas idéias de como o Java realmente funciona, a ponto de, na sua próxima discussão sobre o Java passar por referência ou passar por valor, você apenas sorrirá :-)

Primeiro passo, apague sua mente a palavra que começa com 'p' "_ _ _ _ _ _ _", especialmente se você vem de outras linguagens de programação. Java e 'p' não podem ser escritos no mesmo livro, fórum ou mesmo txt.

A segunda etapa lembra que, quando você passa um Objeto para um método, está passando a referência a Objetos e não o próprio Objeto.

  • Aluno : Mestre, isso significa que Java é passado por referência?
  • Mestre : Gafanhoto, No.

Agora pense no que a referência / variável de um Objeto faz / é:

  1. Uma variável mantém os bits que informam à JVM como chegar ao objeto referenciado na memória (Heap).
  2. Ao passar argumentos para um método, você NÃO está passando a variável de referência, mas uma cópia dos bits na variável de referência . Algo assim: 3bad086a. 3bad086a representa uma maneira de chegar ao objeto passado.
  3. Então você está passando o 3bad086a que é o valor da referência.
  4. Você está passando o valor da referência e não a referência em si (e não o objeto).
  5. Esse valor é realmente COPIADO e fornecido ao método .

A seguir (por favor, não tente compilar / executar isso ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

O que acontece?

  • A pessoa variável é criada na linha 1 e é nula no início.
  • Um novo Objeto Pessoa é criado na linha 2, armazenado na memória, e a variável person recebe a referência ao objeto Pessoa. Ou seja, seu endereço. Digamos 3bad086a.
  • A pessoa variável que mantém o endereço do Objeto é passada para a função na linha # 3.
  • Na linha 4, você pode ouvir o som do silêncio
  • Verifique o comentário na linha # 5
  • Uma variável local do método - anotherReferenceToTheSamePersonObject - é criada e, em seguida, vem a mágica na linha # 6:
    • A variável / pessoa de referência é copiada bit a bit e passada para anotherReferenceToTheSamePersonObject dentro da função.
    • Nenhuma nova instância de Person é criada.
    • Tanto " pessoa " e " anotherReferenceToTheSamePersonObject " manter o mesmo valor de 3bad086a.
    • Não tente fazer isso, mas a pessoa == anotherReferenceToTheSamePersonObject seria verdadeira.
    • Ambas as variáveis ​​têm CÓPIAS IDÊNTICAS da referência e ambas se referem ao mesmo Objeto Pessoa, ao MESMO Objeto na Pilha e NÃO A UMA CÓPIA.

Uma imagem vale mais que mil palavras:

Passe por valor

Observe que as setas anotherReferenceToTheSamePersonObject são direcionadas para o Object e não para a variável person!

Se você não conseguiu, confie em mim e lembre-se de que é melhor dizer que Java é transmitido por valor . Bem, passe pelo valor de referência . Bem, melhor ainda é passar por cópia do valor variável! ;)

Agora, sinta-se à vontade para me odiar, mas observe que, dado isso, não há diferença entre passar tipos de dados primitivos e Objetos ao falar sobre argumentos de método.

Você sempre passa uma cópia dos bits do valor da referência!

  • Se for um tipo de dados primitivo, esses bits conterão o valor do próprio tipo de dado primitivo.
  • Se for um Objeto, os bits conterão o valor do endereço que informa à JVM como chegar ao Objeto.

Java é transmitido por valor porque, dentro de um método, você pode modificar o objeto referenciado o quanto quiser, mas, por mais que tente, nunca poderá modificar a variável transmitida que continuará referenciando (não p _ _ _ _ _ _ _) o mesmo objeto, não importa o quê!


A função changeName acima nunca poderá modificar o conteúdo real (os valores de bits) da referência passada. Em outras palavras, changeName não pode fazer com que Person person se refira a outro Objeto.


Claro que você pode abreviá-lo e apenas dizer que Java é pass-by-value!

Gevorg
fonte
5
Você quer dizer ponteiros? .. Se eu entendi corretamente, in public void foo(Car car){ ... }, caré local fooe contém a localização da pilha do objeto? Então, se eu alterar caro valor de car = new Car(), ele apontará para um objeto diferente na pilha? e se eu alterar caro valor da propriedade por car.Color = "Red", o Objeto no heap apontado por carserá modificado. Além disso, é o mesmo em c #? Por favor, responda! Obrigado!
dpp 25/02
11
@domanokz Você está me matando, por favor não diga essa palavra novamente! ;) Observe que eu poderia ter respondido a essa pergunta sem dizer 'referência' também. É uma questão de terminologia e os p pioram. Infelizmente, eu e Scot temos opiniões diferentes sobre isso. Acho que você entendeu como isso funciona em Java, agora você pode chamá-lo de passar por valor, por compartilhamento de objeto, por cópia do valor variável ou sinta-se à vontade para criar outra coisa! Eu realmente não me importo, desde que você entenda como funciona e o que está em uma variável do tipo Object: apenas um endereço de caixa postal! ;)
Marsellus Wallace
9
Parece que você acabou de passar uma referência ? Vou defender o fato de que o Java ainda é uma linguagem de passagem por referência copiada. O fato de ser uma referência copiada não altera a terminologia. Ambas as referências ainda apontam para o mesmo objeto. Este é o argumento de um purista ...
John Strickler
3
Então entre na linha teórica 9, System.out.println(person.getName());o que aparecerá? "Tom" ou "Jerry"? Esta é a última coisa que me ajudará a superar essa confusão.
TheBrenny
1
"O valor da referência " é o que finalmente me explicou.
Alexander Shubert
689

Java é sempre transmitido por valor, sem exceções, nunca .

Então, como é que alguém pode ficar confuso com isso e acreditar que o Java é passado por referência, ou acha que eles têm um exemplo de Java agindo como passado por referência? O ponto principal é que o Java nunca fornece acesso direto aos valores dos próprios objetos , em nenhuma circunstância. O único acesso a objetos é através de uma referência a esse objeto. Como os objetos Java são sempre acessados ​​por meio de uma referência, e não diretamente, é comum falar sobre campos e variáveis e argumentos de método como objetos , quando pedanticamente eles são apenas referências a objetos .A confusão decorre dessa mudança (estritamente falando, incorreta) da nomenclatura.

Então, ao chamar um método

  • Para argumentos primitivos ( int, long, etc.), a passagem pelo valor é o valor real da primitiva (por exemplo, 3).
  • Para objetos, o valor de passagem é o valor da referência ao objeto .

Portanto, se você doSomething(foo)e public void doSomething(Foo foo) { .. }os dois Foos copiaram referências que apontam para os mesmos objetos.

Naturalmente, passar por valor uma referência a um objeto se parece muito (e na prática é indistinguível) a passar um objeto por referência.

SCdF
fonte
7
Como os valores das primitivas são imutáveis ​​(como String), a diferença entre os dois casos não é realmente relevante.
Paŭlo Ebermann 03/02/11
4
Exatamente. Pelo que você pode dizer através do comportamento observável da JVM, as primitivas podem ser transmitidas por referência e podem permanecer no heap. Eles não, mas isso não é realmente observável de forma alguma.
Gravity
6
primitivos são imutáveis? isso é novo no Java 7?
user85421
4
Os ponteiros são imutáveis, os primitivos em geral são mutáveis. String também não é uma primitiva, é um objeto. Além disso, a estrutura subjacente da String é uma matriz mutável. A única coisa imutável é o comprimento, sendo essa a natureza inerente das matrizes.
kingfrito_5005
3
Essa é outra resposta que aponta a natureza amplamente semântica do argumento. A definição de referência fornecida nesta resposta faria o Java "passar por referência". O autor essencialmente admite isso no último parágrafo, afirmando que é "indistinguível na prática" de "passagem por referência". Duvido que o OP esteja perguntando por causa de um desejo de entender a implementação do Java, mas de entender como usar o Java corretamente. Se fosse indistinguível na prática, não haveria sentido em cuidar e até pensar nisso seria uma perda de tempo.
Loduwijk
331

Java passa referências por valor.

Portanto, você não pode alterar a referência que é passada.

ScArcher2
fonte
27
Mas o que continua repetindo "você não pode alterar o valor dos objetos passados ​​nos argumentos" é claramente falso. Talvez você não consiga fazê-los se referir a um objeto diferente, mas é possível alterar o conteúdo chamando seus métodos. Na IMO, isso significa que você perde todos os benefícios das referências e não recebe garantias adicionais.
Timmmm 24/07
34
Eu nunca disse "você não pode alterar o valor dos objetos passados ​​nos argumentos". Eu direi "Você não pode alterar o valor da referência do objeto passada como argumento de método", que é uma afirmação verdadeira sobre a linguagem Java. Obviamente, você pode alterar o estado do objeto (desde que não seja imutável).
ScArcher2
20
Lembre-se de que você não pode realmente passar objetos em java; os objetos ficam na pilha. Ponteiros para os objetos podem ser transmitidos (que são copiados no quadro da pilha para o método chamado). Portanto, você nunca altera o valor passado (o ponteiro), mas pode segui-lo e alterar a coisa na pilha para a qual ele aponta. Isso é passar por valor.
Scott Stanchfield
10
Parece que você acabou de passar uma referência ? Vou defender o fato de que o Java ainda é uma linguagem de passagem por referência copiada. O fato de ser uma referência copiada não altera a terminologia. Ambas as referências ainda apontam para o mesmo objeto. Este é o argumento de um purista ...
John Strickler
3
Java não passa um objeto, ele passa o valor do ponteiro para o objeto. Isso cria um novo ponteiro para o local da memória do objeto original em uma nova variável. Se você alterar o valor (o endereço de memória para o qual aponta) dessa variável de ponteiro em um método, o ponteiro original usado no chamador do método permanecerá inalterado. Se você está chamando o parâmetro de referência, o fato de ser uma cópia da referência original, não a referência original, de modo que agora existem duas referências ao objeto significa que ele é transmitido por valor
theferrit32
238

Sinto que discutir sobre "passagem por referência versus passagem por valor" não é muito útil.

Se você disser: "Java é aprovado por qualquer que seja (referência / valor)", em ambos os casos, você não fornecerá uma resposta completa. Aqui estão algumas informações adicionais que, com sorte, ajudarão a entender o que está acontecendo na memória.

Faça um curso intensivo sobre a pilha / pilha antes de chegarmos à implementação do Java: Os valores entram e saem da pilha de uma maneira ordenada, como uma pilha de pratos em uma lanchonete. A memória na pilha (também conhecida como memória dinâmica) é aleatória e desorganizada. A JVM encontra o espaço sempre que pode e o libera, pois as variáveis ​​que o utilizam não são mais necessárias.

OK. Primeiro, as primitivas locais vão para a pilha. Portanto, este código:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

resulta nisto:

primitivas na pilha

Quando você declara e instancia um objeto. O objeto real continua na pilha. O que se passa na pilha? O endereço do objeto na pilha. Os programadores de C ++ chamariam isso de ponteiro, mas alguns desenvolvedores de Java são contra a palavra "ponteiro". Tanto faz. Apenas saiba que o endereço do objeto está na pilha.

Igual a:

int problems = 99;
String name = "Jay-Z";

ab * 7ch não é um!

Uma matriz é um objeto e, portanto, também fica no heap. E o que dizer dos objetos na matriz? Eles obtêm seu próprio espaço de heap e o endereço de cada objeto fica dentro da matriz.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

irmãos marx

Então, o que é passado quando você chama um método? Se você passa um objeto, o que realmente está passando é o endereço do objeto. Alguns podem dizer o "valor" do endereço e outros dizem que é apenas uma referência ao objeto. Esta é a gênese da guerra santa entre os proponentes "referência" e "valor". O que você chama de não é tão importante quanto você entende que o que está sendo passado é o endereço do objeto.

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

Uma String é criada e o espaço para ela é alocado na pilha, e o endereço da string é armazenado na pilha e recebe o identificador hisName, já que o endereço da segunda String é o mesmo que o primeiro, nenhuma nova String é criada e nenhum novo espaço de heap é alocado, mas um novo identificador é criado na pilha. Então chamamos shout(): um novo quadro de pilha é criado e um novo identificador, nameé criado e atribuído o endereço da String já existente.

la da di da da da

Então, valor, referência? Você diz "batata".

cutmancometh
fonte
7
No entanto, você deve ter seguido um exemplo mais complexo em que uma função parece alterar uma variável para cujo endereço ela tem uma referência.
Brian Peterson
34
As pessoas não estão "dançando em torno do problema real" de pilha versus pilha, porque esse não é o problema real. É um detalhe de implementação na melhor das hipóteses, e absolutamente errado na pior. (É bem possível que objetos morem na pilha; "análise de escape" do google. E um grande número de objetos contém primitivas que provavelmente não vivem na pilha.) O problema real é exatamente a diferença entre tipos de referência e tipos de valor - em particular, que o valor de uma variável do tipo de referência é uma referência, não o objeto a que se refere.
Chao
8
É um "detalhe de implementação", pois o Java nunca é necessário para mostrar onde um objeto mora na memória e, de fato, parece determinado a evitar o vazamento dessas informações. Poderia colocar o objeto na pilha, e você nunca saberia. Se você se importa, está se concentrando na coisa errada - e, neste caso, isso significa ignorar o problema real.
cHao
10
E de qualquer maneira, "as primitivas vão para a pilha" está incorreta. Variáveis ​​locais primitivas vão para a pilha. (Se eles não foram otimizados, é claro.) Mas então, o mesmo acontece com as variáveis ​​de referência local . E membros primitivos definidos dentro de um objeto vivem onde quer que ele esteja.
cHao
9
Concorde com os comentários aqui. Pilha / pilha é uma questão secundária e não é relevante. Algumas variáveis ​​podem estar na pilha, outras estão na memória estática (variáveis ​​estáticas) e muitas ficam no heap (todas as variáveis ​​de membro do objeto). Nenhuma dessas variáveis ​​pode ser passada por referência: a partir de um método chamado, NUNCA é possível alterar o valor de uma variável que é passada como argumento. Portanto, não há passagem por referência em Java.
fishinperto de 02/03/2014
195

Apenas para mostrar o contraste, compare os seguintes trechos de C ++ e Java :

Em C ++: Nota: Código incorreto - vazamentos de memória! Mas isso demonstra o ponto.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

Em Java,

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java possui apenas os dois tipos de passagem: por valor para tipos internos e por valor do ponteiro para tipos de objetos.

Eclipse
fonte
7
+1 Eu também adicionaria Dog **objPtrPtrao exemplo C ++, dessa forma, podemos modificar o que o ponteiro "aponta".
Amro
1
Mas isso não responde à pergunta dada em Java. De fato, tudo no método Java é Pass By Value, nada mais.
Tauitdnmd 6/11
2
Este exemplo apenas prova que java usa o mesmo equivalente de ponteiro em C não? Onde passar por valor em C é basicamente somente leitura? No final, toda esta discussão é incompreensível
amdev
179

Java passa referências a objetos por valor.

John Channing
fonte
Esta é a melhor resposta. Curto e correto.
woens 28/04
166

Basicamente, a reatribuição de parâmetros de Objeto não afeta o argumento, por exemplo,

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

será impresso em "Hah!"vez de null. A razão pela qual isso funciona é porque baré uma cópia do valor de baz, que é apenas uma referência "Hah!". Se fosse a própria referência real, footeria redefinido bazpara null.

Hank Gay
fonte
8
Prefiro dizer que bar é uma cópia da referência baz (ou alias do baz), que aponta inicialmente para o mesmo objeto.
MaxZoom
não existe uma ligeira diferença entre a classe String e todas as outras classes?
Mehdi Karamosly
152

Não acredito que ninguém tenha mencionado Barbara Liskov ainda. Quando ela projetou a CLU, em 1974, encontrou o mesmo problema de terminologia e inventou o termo chamada por compartilhamento (também conhecido como chamada por compartilhamento de objeto e chamada por objeto ) para este caso específico de "chamada por valor em que o valor é uma referência".

Jörg W Mittag
fonte
3
Eu gosto dessa distinção na nomenclatura. É lamentável que o Java suporte a chamada compartilhando objetos, mas não a chamada por valor (como o C ++). Java suporta chamada por valor apenas para tipos de dados primitivos e não para tipos de dados compostos.
Derek Mahar
2
Realmente não acho que precisávamos de um termo extra - é simplesmente passar por valor para um tipo específico de valor. Adicionar "chamar por primitivo" adicionaria algum esclarecimento?
Scott Stanchfield 25/10/10
Para que eu possa passar por referência compartilhando algum objeto de contexto global ou mesmo passando um objeto de contexto que contém outras referências? Ainda passo por valor, mas pelo menos tenho acesso a referências que posso modificar e fazê-las apontar para outra coisa.
YoYo
1
@ Sanjeev: o compartilhamento de chamadas por objeto é um caso especial de passagem por valor. No entanto, muitas pessoas argumentam veementemente que Java (e linguagens semelhantes como Python, Ruby, ECMAScript, Smalltalk) são passadas por referência. Eu preferiria chamá-lo de passagem por valor também, mas o compartilhamento por chamada de objetos parece ser um termo razoável com o qual até as pessoas que argumentam que não é uma passagem por valor podem concordar.
Jörg W Mittag
119

O cerne da questão é que a palavra referência na expressão "passagem por referência" significa algo completamente diferente do significado usual da palavra referência em Java.

Normalmente, em referência a Java significa uma referência a um objeto . Mas os termos técnicos transmitidos por referência / valor da teoria da linguagem de programação estão falando de uma referência à célula de memória que contém a variável , que é algo completamente diferente.

JacquesB
fonte
7
@ Gevorg - Então o que é uma "NullPointerException"?
Hot Licks
5
@ Hot: Infelizmente, uma exceção nomeada antes de Java se estabelecer em uma terminologia clara. A exceção semanticamente equivalente em c # é chamada NullReferenceException.
precisa saber é o seguinte
4
Sempre me pareceu que o uso de "referência" na terminologia Java é uma afetação que dificulta a compreensão.
Hot Licks
Aprendi a chamar esses chamados "ponteiros para objetos" como um "identificador para o objeto". Essa ambiguidade reduzida.
Moronkreacionz
86

Em java, tudo é referência; portanto, quando você tem algo como: Point pnt1 = new Point(0,0);Java faz o seguinte:

  1. Cria um novo objeto Point
  2. Cria uma nova referência de ponto e inicializa essa referência ao ponto (consulte) no objeto Point criado anteriormente.
  3. A partir daqui, através da vida útil do objeto Point, você acessará esse objeto através da referência pnt1. Então, podemos dizer que em Java você manipula objetos através de sua referência.

insira a descrição da imagem aqui

Java não passa argumentos de método por referência; passa-os pelo valor. Vou usar o exemplo deste site :

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

Fluxo do programa:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

Criando dois objetos Point diferentes com duas referências diferentes associadas. insira a descrição da imagem aqui

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

Como o resultado esperado será:

X1: 0     Y1: 0
X2: 0     Y2: 0

Nesta linha, 'passagem por valor' entra em cena ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

Referências pnt1e pnt2são passados ​​por valor ao método complicado, o que significa que agora o seu faz referência pnt1e pnt2tem o copiesnome arg1e arg2.So pnt1e arg1 aponta para o mesmo objeto. (O mesmo para pnt2earg2 ) insira a descrição da imagem aqui

No trickymétodo:

 arg1.x = 100;
 arg1.y = 100;

insira a descrição da imagem aqui

Avançar no trickymétodo

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Aqui, você primeiro cria uma nova tempreferência de ponto que apontará no mesmo local como arg1referência. Então você move a referência arg1para apontar para o mesmo local como arg2referência. Finalmente arg2irá apontar para o mesmo lugar como temp.

insira a descrição da imagem aqui

A partir daqui âmbito do trickymétodo é ido e você não tem acesso mais às referências: arg1, arg2, temp. Mas nota importante é que tudo o que você faz com essas referências quando elas estão "na vida" afetará permanentemente o objeto para o qual elas apontam .

Então, depois de executar o método tricky, quando você retornar aomain , você terá esta situação: insira a descrição da imagem aqui

Então agora, a execução completa do programa será:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0
Srle
fonte
7
Em java, quando você diz "Em java, tudo é referência", você quer dizer todos os objetos são passados ​​por referência. Tipos de dados primitivos não são passados ​​por referência.
Eric
Eu sou capaz de imprimir o valor trocado no método principal. no método trickey, adicione a seguinte instruçãoarg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2; assim, como arg1 agora segurando de refrence PNT2 e arg2 segurando referência agora pnt1, por isso, sua impressãoX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor
85

Java é sempre passado por valor, não por referência

Antes de tudo, precisamos entender o que passam por valor e passam por referência.

Passagem por valor significa que você está fazendo uma cópia na memória do valor do parâmetro real que é passado. Esta é uma cópia do conteúdo do parâmetro real .

Passagem por referência (também chamada de passagem por endereço) significa que uma cópia do endereço do parâmetro real é armazenada .

Às vezes, o Java pode dar a ilusão de passar por referência. Vamos ver como funciona usando o exemplo abaixo:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

A saída deste programa é:

changevalue

Vamos entender passo a passo:

Test t = new Test();

Como todos sabemos, ele criará um objeto na pilha e retornará o valor de referência para t. Por exemplo, suponha que o valor de t seja 0x100234(não sabemos o valor interno real da JVM, este é apenas um exemplo).

primeira ilustração

new PassByValue().changeValue(t);

Ao passar a referência t para a função, ela não passará diretamente o valor de referência real do teste do objeto, mas criará uma cópia de t e a passará para a função. Como está passando por valor , passa uma cópia da variável em vez de sua referência real. Como dissemos que o valor de t era 0x100234, t e f terão o mesmo valor e, portanto, apontarão para o mesmo objeto.

segunda ilustração

Se você alterar alguma coisa na função usando a referência f, ela modificará o conteúdo existente do objeto. É por isso que obtivemos a saída changevalue, que é atualizada na função.

Para entender isso mais claramente, considere o seguinte exemplo:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

Isso vai dar um NullPointerException? Não, porque apenas passa uma cópia da referência. No caso de passar por referência, poderia ter lançado umNullPointerException , como visto abaixo:

terceira ilustração

Espero que isso ajude.

Ganesh
fonte
71

Uma referência é sempre um valor quando representada, independentemente do idioma que você usa.

Para obter uma visualização fora da caixa, vejamos o Assembly ou algum gerenciamento de memória de baixo nível. No nível da CPU, uma referência a qualquer coisa imediatamente se torna um valor se for gravada na memória ou em um dos registradores da CPU. (É por isso que ponteiro é uma boa definição. É um valor que tem um objetivo ao mesmo tempo).

Os dados na memória têm um local e nesse local há um valor (byte, palavra, qualquer que seja). No Assembly, temos uma solução conveniente para atribuir um nome a um determinado local (também conhecido como variável), mas ao compilar o código, o assembler simplesmente substitui o Name pelo local designado, assim como o navegador substitui os nomes de domínio pelos endereços IP.

Até o âmago, é tecnicamente impossível passar uma referência a qualquer coisa em qualquer idioma sem representá-lo (quando ele imediatamente se torna um valor).

Digamos que tenhamos uma variável Foo, sua localização está no 47º byte na memória e seu valor é 5. Temos outra variável Ref2Foo que está no 223rd byte na memória e seu valor será 47. Esse Ref2Foo pode ser uma variável técnica , não criado explicitamente pelo programa. Se você apenas observar 5 e 47 sem nenhuma outra informação, verá apenas dois Valores . Se você usá-las como referências, para chegar a 5nós, temos que viajar:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

É assim que as tabelas de salto funcionam.

Se quisermos chamar um método / função / procedimento com o valor de Foo, existem algumas maneiras possíveis de passar a variável para o método, dependendo do idioma e de seus vários modos de chamada de método:

  1. 5 é copiado para um dos registros da CPU (ou seja, EAX).
  2. 5 empurra PUSHd para a pilha.
  3. 47 é copiado para um dos registros da CPU
  4. 47 PUSHd para a pilha.
  5. 223 é copiado para um dos registros da CPU.
  6. 223 empurra PUSHd para a pilha.

Em todos os casos acima de um valor - uma cópia de um valor existente - foi criado, agora cabe ao método de recebimento lidar com ele. Quando você escreve "Foo" dentro do método, ele é lido no EAX ou automaticamente desreferenciado ou duplamente referenciado, o processo depende de como o idioma funciona e / ou de que tipo o Foo determina. Isso fica oculto para o desenvolvedor até que ele contorne o processo de cancelamento de referência. Portanto, uma referência é um valor quando representado, porque uma referência é um valor que deve ser processado (no nível do idioma).

Agora passamos o Foo para o método:

  • nos casos 1. e 2. se você alterar Foo ( Foo = 9), isso afeta apenas o escopo local, pois você possui uma cópia do Valor. De dentro do método, não podemos nem determinar onde na memória o Foo original estava localizado.
  • nos casos 3. e 4. se você usar construções de linguagem padrão e alterar Foo ( Foo = 11), ele poderá mudar Foo globalmente (depende da linguagem, por exemplo, Java ou como o procedure findMin(x, y, z: integer;var m de Pascal : integer);). No entanto, se o idioma permitir contornar o processo de desreferência, você poderá alterar 47, digamos 49. Nesse ponto, Foo parece ter sido alterado se você o ler, porque você mudou o ponteiro local para ele. E se você modificar este Foo dentro do método ( Foo = 12), provavelmente irá FUBAR a execução do programa (também conhecido como segfault) porque gravará em uma memória diferente da esperada, podendo até modificar uma área destinada a manter executável programa e escrever nele modificará o código em execução (o Foo agora não está em 47). MAS o valor de Foo de47não mudou globalmente, apenas aquele dentro do método, porque 47também era uma cópia do método.
  • nos casos 5. e 6. se você modificar 223dentro do método, ele cria o mesmo caos que em 3. ou 4. (um ponteiro, apontando para um valor agora ruim, que é novamente usado como ponteiro), mas ainda é um local problema, como 223 foi copiado . No entanto, se você puder desreferenciar Ref2Foo(ou seja 223), alcançar e modificar o valor apontado 47, digamos, para 49, isso afetará o Foo globalmente , porque nesse caso os métodos obtiveram uma cópia, 223 mas o referenciado 47existe apenas uma vez e alterando esse valor. to 49levará cada Ref2Fooreferência dupla a um valor errado.

Ao clicar em detalhes insignificantes, mesmo os idiomas que passam por referência transmitem valores para funções, mas essas funções sabem que precisam usá-lo para fins de desreferenciação. Essa passagem como referência como valor está apenas oculta do programador porque é praticamente inútil e a terminologia é apenas passagem por referência .

A passagem estrita por valor também é inútil, significaria que uma matriz de 100 Mbyte deve ser copiada toda vez que chamarmos um método com a matriz como argumento; portanto, o Java não pode ser estritamente transmitido por valor. Toda linguagem passaria uma referência a essa enorme matriz (como um valor) e empregaria o mecanismo de copiar na gravação se essa matriz pudesse ser alterada localmente dentro do método ou permitir que o método (como Java faz) modifique a matriz globalmente (de visualização do chamador) e alguns idiomas permitem modificar o valor da própria referência.

Portanto, resumidamente, e na própria terminologia de Java, Java é uma passagem por valor em que o valor pode ser: um valor real ou um valor que é uma representação de uma referência .

karatedog
fonte
1
Numa linguagem com passagem por referência, o que é passado (a referência) é efêmero; o destinatário não deve "copiá-lo". Em Java, passar uma matriz é passado como um "identificador de objeto" - equivalente a um pedaço de papel que diz "Objeto # 24601", quando o 24601 ° objeto construído era uma matriz. O destinatário pode copiar o "Objeto nº 24601" para onde quiser, e qualquer pessoa com um pedaço de papel dizendo "Objeto nº 24601" pode fazer o que quiser com qualquer um dos elementos da matriz. O padrão de bits que é passado não iria realmente dizer "objeto # 24601", é claro, mas ...
supercat
... o ponto principal é que o destinatário da identificação do objeto que identifica a matriz pode armazenar essa identificação de objeto onde quiser e dar a quem quiser, e qualquer destinatário poderá acessar ou modificar a matriz sempre que desejar. quer. Por outro lado, se uma matriz fosse passada por referência em uma linguagem como Pascal que suporta essas coisas, o método chamado poderia fazer o que quisesse com a matriz, mas não poderia armazenar a referência de maneira a permitir que o código modificasse a matriz. depois que ele voltou.
supercat
De fato, em Pascal, você pode obter o endereço de todas as variáveis, sejam elas passadas por referência ou copiadas localmente, addrnão são tipadas, @fazem com o tipo, e você pode modificar a variável referenciada posteriormente (exceto as copiadas localmente). Mas não vejo por que você faria isso. Esse pedaço de papel no seu exemplo (Objeto nº 24601) é uma referência, seu objetivo é ajudar a encontrar a matriz na memória, não contém dados da matriz em si. Se você reiniciar o programa, a mesma matriz poderá obter um ID de objeto diferente, mesmo que seu conteúdo seja o mesmo da execução anterior.
karatedog
Eu pensei que o operador "@" não fazia parte do Pascal padrão, mas foi implementado como uma extensão comum. Faz parte do padrão? Meu argumento foi que, em uma linguagem com verdadeira passagem por referência e sem capacidade de construir um ponteiro não efêmero para um objeto efêmero, código que contém a única referência a uma matriz, em qualquer lugar do universo, antes de passar a matriz por A referência pode saber que, a menos que o destinatário "trapaceie", ele ainda manterá a única referência posteriormente. A única maneira segura de conseguir isso em Java seria construir um objeto temporário ... #
308
... que encapsula um AtomicReferencee nem expõe a referência nem seu destino, mas inclui métodos para fazer coisas com o destino; uma vez que o código para o qual o objeto foi passado retorne, o AtomicReference[para o qual seu criador manteve uma referência direta] deve ser invalidado e abandonado. Isso forneceria a semântica adequada, mas seria lenta e nojenta.
supercat
70

Java é uma chamada por valor

Como funciona

  • Você sempre passa uma cópia dos bits do valor da referência!

  • Se for um tipo de dados primitivo, esses bits contêm o valor do próprio tipo de dado primitivo. Por isso, se alterarmos o valor do cabeçalho dentro do método, ele não refletirá as alterações externas.

  • Se for um tipo de dado de objeto como Foo foo = new Foo () , nesse caso, a cópia do endereço do objeto passa como um atalho de arquivo, suponha que tenhamos um arquivo de texto abc.txt em C: \ desktop e suponha que façamos um atalho de o mesmo arquivo e coloque-o dentro de C: \ desktop \ abc-shortcut; assim, quando você acessar o arquivo em C: \ desktop \ abc.txt e escrever 'Stack Overflow', feche o arquivo e, novamente, abra o arquivo a partir do atalho; escreva 'é a maior comunidade online para programadores aprender', então a alteração total do arquivo será 'Stack Overflow é a maior comunidade online para programadores aprender'o que significa que não importa de onde você abre o arquivo, sempre que acessamos o mesmo arquivo, podemos assumir oFoo como um arquivo e suponha que foo esteja armazenado no 123hd7h (endereço original como C: \ desktop \ abc.txt ) e 234jdid (endereço copiado comoC: \ desktop \ abc-atalho que realmente contém o endereço original do arquivo)

Himanshu arora
fonte
66

Já existem ótimas respostas que cobrem isso. Eu queria fazer uma pequena contribuição compartilhando um exemplo muito simples (que será compilado) contrastando os comportamentos entre Passagem por referência em c ++ e Passagem por valor em Java.

Alguns pontos:

  1. O termo "referência" é uma sobrecarga com dois significados separados. Em Java, significa simplesmente um ponteiro, mas no contexto de "Passagem por referência", significa um identificador para a variável original que foi passada.
  2. Java é Passagem por valor . Java é descendente de C (entre outras linguagens). Antes de C, vários idiomas (mas não todos) anteriores, como FORTRAN e COBOL, suportavam PBR, mas C não. O PBR permitiu que esses outros idiomas fizessem alterações nas variáveis ​​passadas nas sub-rotinas. Para realizar a mesma coisa (ou seja, alterar os valores das variáveis ​​dentro das funções), os programadores C passaram os ponteiros para as variáveis ​​para as funções. Idiomas inspirados em C, como Java, pegaram emprestada essa idéia e continuam passando o ponteiro para métodos como C, exceto que Java chama seus ponteiros de Referências. Novamente, esse é um uso diferente da palavra "Referência" do que em "Passagem por Referência".
  3. O C ++ permite a passagem por referência declarando um parâmetro de referência usando o caractere "&" (que passa a ser o mesmo caractere usado para indicar "o endereço de uma variável" em C e C ++). Por exemplo, se passarmos um ponteiro por referência, o parâmetro e o argumento não estarão apenas apontando para o mesmo objeto. Em vez disso, eles são a mesma variável. Se um é definido para um endereço diferente ou nulo, o outro também.
  4. No exemplo C ++ abaixo, estou passando um ponteiro para uma seqüência terminada nula por referência . E no exemplo de Java abaixo, estou passando uma referência de Java para uma String (novamente, o mesmo que um ponteiro para uma String) por valor. Observe a saída nos comentários.

C ++ passa pelo exemplo de referência:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Exemplo do Java Pass "a Java Reference" por valor

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

EDITAR

Várias pessoas escreveram comentários que parecem indicar que eles não estão vendo meus exemplos ou não recebem o exemplo c ++. Não tenho certeza de onde está a desconexão, mas adivinhar o exemplo de c ++ não está claro. Estou postando o mesmo exemplo em pascal porque acho que a passagem por referência parece mais limpa em pascal, mas posso estar errado. Eu poderia estar confundindo mais as pessoas; Espero que não.

No pascal, os parâmetros passados ​​por referência são chamados "parâmetros var". No procedimento setToNil abaixo, observe a palavra-chave 'var' que precede o parâmetro 'ptr'. Quando um ponteiro é passado para esse procedimento, ele é passado por referência . Observe o comportamento: quando este procedimento define ptr como nulo (isso é pascal speak for NULL), ele define o argumento como nulo - você não pode fazer isso em Java.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

EDIT 2

Alguns trechos de "THE Java Programming Language" de Ken Arnold, James Gosling (o cara que inventou o Java) e David Holmes, capítulo 2, seção 2.6.5

Todos os parâmetros para métodos são passados ​​"por valor" . Em outras palavras, os valores das variáveis ​​de parâmetro em um método são cópias do invocador especificado como argumentos.

Ele continua fazendo o mesmo ponto em relação aos objetos. . .

Você deve observar que, quando o parâmetro é uma referência a objeto, é a referência a objeto - e não o próprio objeto - que é passado "por valor" .

E no final da mesma seção, ele faz uma declaração mais ampla sobre o java sendo apenas passado por valor e nunca por referência.

A linguagem de programação Java não passa objetos por referência; ele passa referências de objeto por valor . Como duas cópias da mesma referência se referem ao mesmo objeto real, as alterações feitas através de uma variável de referência são visíveis na outra. Existe exatamente um parâmetro que passa de modo - passa por valor - e isso ajuda a manter as coisas simples.

Esta seção do livro tem uma ótima explicação sobre a passagem de parâmetros em Java e a distinção entre passagem por referência e passagem por valor e é do criador do Java. Eu encorajaria qualquer um a ler, especialmente se você ainda não está convencido.

Eu acho que a diferença entre os dois modelos é muito sutil e, a menos que você tenha feito a programação em que realmente utilizou a passagem por referência, é fácil perder a diferença entre dois modelos.

Espero que isso resolva o debate, mas provavelmente não.

EDIT 3

Eu posso estar um pouco obcecado com este post. Provavelmente porque sinto que os fabricantes de Java inadvertidamente espalharam desinformação. Se, em vez de usar a palavra "referência" para ponteiros, eles tivessem usado outra coisa, digamos dingleberry, não haveria problema. Você poderia dizer: "Java transmite os frutos silvestres por valor e não por referência", e ninguém ficaria confuso.

Essa é a razão pela qual apenas os desenvolvedores Java têm problemas com isso. Eles olham para a palavra "referência" e pensam que sabem exatamente o que isso significa, para que nem se preocupem em considerar o argumento oposto.

De qualquer forma, notei um comentário em um post antigo, que fez uma analogia de balão que eu realmente gostei. Tanto que decidi colar alguns clip-art para fazer um conjunto de desenhos animados para ilustrar o ponto.

Passando uma referência por valor - As alterações na referência não são refletidas no escopo do chamador, mas as alterações no objeto. Isso ocorre porque a referência é copiada, mas o original e a cópia se referem ao mesmo objeto. Passando referências de objeto por valor

Passar por referência - Não há cópia da referência. A referência única é compartilhada pelo chamador e pela função que está sendo chamada. Quaisquer alterações na referência ou nos dados do objeto são refletidas no escopo do chamador. Passe por referência

EDIT 4

Eu já vi posts sobre esse tópico que descrevem a implementação de baixo nível da passagem de parâmetros em Java, o que eu acho ótimo e muito útil porque torna concreta uma idéia abstrata. No entanto, para mim, a pergunta é mais sobre o comportamento descrito na especificação da linguagem do que sobre a implementação técnica do comportamento. Este é um esforço da Especificação da Linguagem Java, seção 8.4.1 :

Quando o método ou construtor é chamado (§15.12), os valores das expressões de argumento reais inicializam variáveis ​​de parâmetro recém-criadas, cada uma do tipo declarado, antes da execução do corpo do método ou construtor. O identificador que aparece no DeclaratorId pode ser usado como um nome simples no corpo do método ou construtor para se referir ao parâmetro formal.

O que significa que o java cria uma cópia dos parâmetros passados ​​antes de executar um método. Como a maioria das pessoas que estudaram compiladores na faculdade, eu usei "O Livro Dragon" que é O livro compiladores. Ele possui uma boa descrição de "Chamada por valor" e "Chamada por referência" no Capítulo 1. A descrição da Chamada por valor corresponde exatamente às especificações Java.

Quando eu estudei compiladores - nos anos 90, usei a primeira edição do livro de 1986, que antecedia o Java por cerca de 9 ou 10 anos. No entanto, acabei de encontrar uma cópia da 2ª Edição de 2007, que na verdade menciona Java! A Seção 1.6.6, denominada "Mecanismos de passagem de parâmetros", descreve a passagem de parâmetros muito bem. Aqui está um trecho sob o título "Chamada por valor", que menciona Java:

Na chamada por valor, o parâmetro real é avaliado (se for uma expressão) ou copiado (se for uma variável). O valor é colocado no local pertencente ao parâmetro formal correspondente do procedimento chamado. Este método é usado em C e Java e é uma opção comum em C ++, assim como na maioria das outras linguagens.

Sanjeev
fonte
4
Honestamente, você pode simplificar esta resposta dizendo que Java é transmitido por valor apenas para tipos primitivos. Tudo o que herda de Object é efetivamente passado por referência, onde a referência é o ponteiro que você está passando.
Steve Scuba Steve
1
@ JuanMendes, eu apenas usei C ++ como exemplo; Eu poderia dar exemplos em outros idiomas. O termo "Passagem por referência" existia muito antes de C ++ existir. É um termo de livro com uma definição muito específica. E, por definição, Java não é passado por referência. Você pode continuar usando o termo dessa maneira, se quiser, mas seu uso não será consistente com a definição do livro. Não é apenas um ponteiro para um ponteiro. É uma construção fornecida pelo idioma para permitir "Passagem por referência". Dê uma olhada no meu exemplo, mas não aceite minha palavra e procure por si mesmo.
precisa
2
@AutomatedMike Acho que descrever Java como "passagem por referência" também é enganoso, sua referência nada mais é do que indicadores. Como Sanjeev escreveu valor, referência e ponteiro são termos de livros didáticos que têm seu próprio significado, independentemente do que os criadores de Java usam.
Jędrzej Dudkiewicz 13/06/19
1
@ArtanisZeratul o ponto é sutil, mas não complicado. Por favor, dê uma olhada no desenho que publiquei. Eu senti que era bastante simples de seguir. Que Java é um passe por valor não é apenas uma opinião. É verdade pela definição do livro-texto de passagem por valor. Além disso, "as pessoas que sempre dizem que isso passa por valor" incluem cientistas da computação como James Gosling, o criador do Java. Por favor, veja as citações de seu livro em "Editar 2" no meu post.
Sanjeev
1
Que ótima resposta, "passar por referência" não existe em java (e em outros idiomas como JS).
newfolder 3/09/19
56

Tanto quanto eu sei, Java só sabe chamar por valor. Isso significa que, para tipos de dados primitivos, você trabalhará com uma cópia e para objetos, com uma cópia da referência aos objetos. No entanto, acho que existem algumas armadilhas; por exemplo, isso não funcionará:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

Isso preencherá o Hello World e não o World Hello, porque na função de troca você usa cópias que não têm impacto nas referências principais. Mas se seus objetos não são imutáveis, você pode alterá-lo por exemplo:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

Isso preencherá o Hello World na linha de comando. Se você alterar StringBuffer para String, ele produzirá apenas Olá porque String é imutável. Por exemplo:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

No entanto, você pode criar um invólucro para String como este, capaz de usá-lo com Strings:

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

edit: Eu acredito que este também é o motivo para usar o StringBuffer quando se trata de "adicionar" duas Strings porque você pode modificar o objeto original que você não pode com objetos imutáveis ​​como o String.

kukudas
fonte
+1 no teste de troca - provavelmente a maneira mais direta e fácil de distinguir entre passar por referência e passar por uma referência por valor . Se você pode escrever facilmente uma função swap(a, b)que (1) troca ae bdo ponto de vista do chamador, (2) é independente de tipo na medida em que a digitação estática permite (o que significa que usá-la com outro tipo não requer nada além de alterar os tipos declarados dea e b) , e (3) não exige que o chamador passe explicitamente um ponteiro ou nome, então o idioma suporta a passagem por referência.
precisa
"..para tipos de dados primitivos, você trabalhará com uma cópia e, para objetos, você trabalhará com uma cópia da referência aos objetos" - perfeitamente escrita!
Raúl
55

Não, não é passar por referência.

Java é transmitido por valor de acordo com a Java Language Specification:

Quando o método ou construtor é chamado (§15.12), os valores das expressões de argumento reais inicializam variáveis ​​de parâmetro recém-criadas , cada uma do tipo declarado, antes da execução do corpo do método ou construtor. O identificador que aparece no DeclaratorId pode ser usado como um nome simples no corpo do método ou construtor para se referir ao parâmetro formal .

rorrohprog
fonte
52

Deixe-me tentar explicar meu entendimento com a ajuda de quatro exemplos. Java é passagem por valor, e não passagem por referência

/ **

Passagem por valor

Em Java, todos os parâmetros são passados ​​por valor, ou seja, atribuir um argumento de método não é visível para o chamador.

* /

Exemplo 1:

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Resultado

output : output
value : Nikhil
valueflag : false

Exemplo 2:

/ ** * * Passagem por valor * * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Resultado

output : output
value : Nikhil
valueflag : false

Exemplo 3:

/ ** Este 'passe por valor' tem a sensação de 'passar por referência'

Algumas pessoas dizem que tipos primitivos e 'String' são 'passados ​​por valor' e objetos são 'passados ​​por referência'.

Mas, a partir deste exemplo, podemos entender que é apenas uma passagem de valor por valor, tendo em mente que aqui estamos passando a referência como valor. ou seja: a referência é passada por valor. É por isso que é capaz de mudar e ainda é válido após o escopo local. Mas não podemos alterar a referência real fora do escopo original. o que isso significa é demonstrado no próximo exemplo de PassByValueObjectCase2.

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

Resultado

output : output
student : Student [id=10, name=Anand]

Exemplo 4:

/ **

Além do mencionado no Exemplo3 (PassByValueObjectCase1.java), não podemos alterar a referência real fora do escopo original. "

Nota: Não estou colando o código para private class Student. A definição de classe paraStudent é igual ao Exemplo3.

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

Resultado

output : output
student : Student [id=10, name=Nikhil]
aranha
fonte
49

Você nunca pode passar por referência em Java, e uma das maneiras óbvias é quando você deseja retornar mais de um valor de uma chamada de método. Considere o seguinte bit de código em C ++:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

Às vezes você deseja usar o mesmo padrão em Java, mas não pode; pelo menos não diretamente. Em vez disso, você poderia fazer algo assim:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

Como foi explicado nas respostas anteriores, em Java você está passando um ponteiro para a matriz como um valor getValues. Isso é suficiente, porque o método modifica o elemento da matriz e, por convenção, você espera que o elemento 0 contenha o valor de retorno. Obviamente, você pode fazer isso de outras maneiras, como estruturar seu código, para que isso não seja necessário, ou construir uma classe que possa conter o valor de retorno ou permitir que ele seja definido. Mas o padrão simples disponível para você no C ++ acima não está disponível em Java.

Jared Oberhaus
fonte
48

Pensei em contribuir com esta resposta para adicionar mais detalhes das especificações.

Primeiro, qual é a diferença entre passar por referência e passar por valor?

Passar por referência significa que o parâmetro das funções chamadas será o mesmo que o argumento passado pelos chamadores (não o valor, mas a identidade - a própria variável).

Passagem por valor significa que o parâmetro das funções chamadas será uma cópia do argumento passado pelos chamadores.

Ou da wikipedia, sobre o assunto de passagem por referência

Na avaliação chamada por referência (também chamada de passagem por referência), uma função recebe uma referência implícita a uma variável usada como argumento, em vez de uma cópia do seu valor. Isso normalmente significa que a função pode modificar (ou seja, atribuir a) a variável usada como argumento - algo que será visto pelo chamador.

E em matéria de passagem por valor

Na chamada por valor, a expressão do argumento é avaliada e o valor resultante é vinculado à variável correspondente na função [...]. Se a função ou procedimento puder atribuir valores aos seus parâmetros, apenas sua cópia local é atribuída [...].

Segundo, precisamos saber o que Java usa em suas invocações de métodos. Os estados da especificação de linguagem Java

Quando o método ou construtor é chamado (§15.12), os valores das expressões de argumento reais inicializam variáveis ​​de parâmetro recém-criadas , cada uma do tipo declarado, antes da execução do corpo do método ou construtor.

Portanto, atribui (ou vincula) o valor do argumento à variável de parâmetro correspondente.

Qual é o valor do argumento?

Vamos considerar os tipos de referência, os estados da Java Virtual Machine Specification

Existem três tipos de tipos de referência : tipos de classe, tipos de matriz e tipos de interface. Seus valores são referências a instâncias de classe, matrizes ou instâncias de classe criadas dinamicamente ou matrizes que implementam interfaces, respectivamente.

A especificação da linguagem Java também declara

Os valores de referência (geralmente apenas referências) são ponteiros para esses objetos e uma referência nula especial, que não se refere a nenhum objeto.

O valor de um argumento (de algum tipo de referência) é um ponteiro para um objeto. Observe que uma variável, uma chamada de um método com um tipo de referência retorna um tipo e uma expressão de criação de instância (new ... ) são resolvidas para um valor de tipo de referência.

assim

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

todos vinculam o valor de uma referência a uma Stringinstância ao parâmetro recém-criado do método param,. É exatamente isso que a definição de passagem por valor descreve. Como tal, Java é transmitido por valor .

O fato de você poder seguir a referência para chamar um método ou acessar um campo do objeto referenciado é completamente irrelevante para a conversa. A definição de passagem por referência foi

Isso normalmente significa que a função pode modificar (ou seja, atribuir a) a variável usada como argumento - algo que será visto pelo chamador.

Em Java, modificar a variável significa reatribuí-la. Em Java, se você redesignasse a variável dentro do método, ela passaria despercebida ao chamador. Modificar o objeto referenciado pela variável é um conceito completamente diferente.


Os valores primitivos também são definidos na Java Virtual Machine Specification, aqui . O valor do tipo é o valor integral ou ponto flutuante correspondente, codificado adequadamente (8, 16, 32, 64, etc. bits).

Sotirios Delimanolis
fonte
42

Em Java, apenas as referências são passadas e são passadas por valor:

Todos os argumentos Java são passados ​​por valor (a referência é copiada quando usada pelo método):

No caso de tipos primitivos, o comportamento Java é simples: o valor é copiado em outra instância do tipo primitivo.

No caso de objetos, é o mesmo: variáveis ​​de objeto são ponteiros (baldes) contendo apenas o endereço do objeto que foi criado usando a palavra-chave "new" e são copiados como tipos primitivos.

O comportamento pode parecer diferente dos tipos primitivos: Como a variável-objeto copiada contém o mesmo endereço (para o mesmo Objeto). Conteúdo / membros do objeto ainda podem ser modificados dentro de um método e posteriormente acessados ​​externamente, dando a ilusão de que o próprio objeto (contendo) foi passado por referência.

Os objetos "String" parecem ser um bom contra-exemplo para a lenda urbana, dizendo que "os objetos são passados ​​por referência":

De fato, usando um método, você nunca poderá atualizar o valor de uma String passada como argumento:

Um String Object, mantém caracteres por uma matriz declarada final que não pode ser modificada. Somente o endereço do objeto pode ser substituído por outro usando "new". O uso de "new" para atualizar a variável não permitirá que o objeto seja acessado de fora, pois a variável foi inicialmente passada por valor e copiada.

user1767316
fonte
Então é byRef em relação aos objetos e byVal em relação aos primitivos?
Mox
@ mox leia: Objetos não são passados ​​por referência, este é um ledgend: String a = new String ("inalterado");
user1767316
1
@Aaron "Passar por referência" não significa "passar um valor que seja membro de um tipo que é chamado de 'referência' em Java". Os dois usos da "referência" significam coisas diferentes.
philipxy
2
Resposta bastante confusa. A razão pela qual você não pode alterar um objeto string em um método, que é passado por referência, é que os objetos String são imutáveis ​​por design e você não pode fazer coisas como strParam.setChar ( i, newValue ). Dito isso, as strings, como qualquer outra coisa, são passadas por valor e, como String é um tipo não primitivo, esse valor é uma referência à coisa que foi criada com new, e você pode verificar isso usando String.intern () .
Zakmck #
1
Em vez disso, você não pode alterar uma string via param = "outra string" (equivalente a nova String ("outra string"))) porque o valor de referência de param (que agora aponta para "outra string") não pode retornar do método corpo. Mas isso é verdade para qualquer outro objeto, a diferença é que, quando a interface da classe permitir, você pode fazer param.changeMe () e o objeto de nível superior que está sendo referido mudará porque o parâmetro está apontando para ele, apesar do próprio valor de referência do parâmetro (o local endereçado, em termos de C) não pode retornar do método.
Zakmck
39

A distinção, ou talvez a maneira como me lembro de ter a mesma impressão que o pôster original, é esta: Java é sempre transmitido por valor. Todos os objetos (em Java, qualquer coisa, exceto primitivos) em Java são referências. Essas referências são passadas por valor.

shsteimer
fonte
2
Acho sua penúltima sentença muito enganadora. Não é verdade que "todos os objetos em Java são referências". São apenas as referências a esses objetos que são referências.
Dawood ibn Kareem
37

Como muitas pessoas mencionaram antes, Java sempre passa por valor

Aqui está outro exemplo que o ajudará a entender a diferença ( o exemplo clássico de troca ):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

Impressões:

Antes: a = 2, b = 3
Depois: a = 2, b = 3

Isso acontece porque iA e iB são novas variáveis ​​de referência local que têm o mesmo valor das referências passadas (elas apontam para aeb respectivamente). Portanto, tentar alterar as referências de iA ou iB mudará apenas no escopo local e não fora deste método.

pek
fonte
32

Java só passa por valor. Um exemplo muito simples para validar isso.

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}
Gaurav
fonte
4
Essa é a maneira mais clara e simples de ver que o Java passa por valor. O valor de obj( null) foi passado para init, e não uma referência para obj.
David Schwartz
31

Eu sempre penso nisso como "passagem por cópia". É uma cópia do valor, seja primitivo ou de referência. Se for um primitivo, é uma cópia dos bits que são o valor e, se for um Objeto, é uma cópia da referência.

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

saída do java PassByCopy:

name = Maxx
name = Fido

As classes primitivas de wrapper e Strings são imutáveis, portanto, qualquer exemplo usando esses tipos não funcionará da mesma forma que outros tipos / objetos.

SWD
fonte
5
"passar por cópia" é o que passar por valor meio .
TJ Crowder
@TJCrowder. "Passar por cópia" pode ser uma maneira mais apropriada de expressar o mesmo conceito para fins Java: porque semanticamente torna muito difícil calçar uma idéia semelhante à passagem por referência, que é o que você deseja em Java.
Mike Rodent
@mikerodent - Desculpe, eu não segui isso. :-) "Passagem por valor" e "passagem por referência" são os termos de arte corretos para esses conceitos. Devemos usá-los (fornecendo suas definições quando as pessoas precisam) em vez de criar novas. Acima, eu disse "passar por cópia" é o que significa "passar por valor", mas, na verdade, não está claro o que significa "passar por cópia" - uma cópia do que? O valor que? (Por exemplo, referência a objeto.) O objeto em si? Então, eu mantenho os termos da arte. :-)
TJ Crowder
28

Diferentemente de outras linguagens, o Java não permite escolher entre passagem por valor e passagem por referência - todos os argumentos são passados ​​por valor. Uma chamada de método pode passar dois tipos de valores para um método - cópias de valores primitivos (por exemplo, valores de int e double) e cópias de referências a objetos.

Quando um método modifica um parâmetro do tipo primitivo, as alterações no parâmetro não têm efeito no valor do argumento original no método de chamada.

Quando se trata de objetos, os próprios objetos não podem ser passados ​​para métodos. Então passamos a referência (endereço) do objeto. Podemos manipular o objeto original usando essa referência.

Como Java cria e armazena objetos: Quando criamos um objeto, armazenamos o endereço do objeto em uma variável de referência. Vamos analisar a seguinte declaração.

Account account1 = new Account();

"Conta da conta1" é o tipo e o nome da variável de referência, "=" é o operador da atribuição, "novo" solicita a quantidade de espaço necessária do sistema. O construtor à direita da palavra-chave new, que cria o objeto, é chamado implicitamente pela palavra-chave new. O endereço do objeto criado (resultado do valor correto, que é uma expressão chamada "expressão de criação de instância de classe") é atribuído ao valor esquerdo (que é uma variável de referência com um nome e um tipo especificado) usando o operador de atribuição.

Embora a referência de um objeto seja passada por valor, um método ainda pode interagir com o objeto referenciado chamando seus métodos públicos usando a cópia da referência do objeto. Como a referência armazenada no parâmetro é uma cópia da referência que foi passada como argumento, o parâmetro no método chamado e o argumento no método de chamada se referem ao mesmo objeto na memória.

Passar referências para matrizes, em vez dos próprios objetos da matriz, faz sentido por razões de desempenho. Como tudo em Java é passado por valor, se objetos de matriz fossem passados, uma cópia de cada elemento seria passada. Para matrizes grandes, isso desperdiçaria tempo e consumiria armazenamento considerável para as cópias dos elementos.

Na imagem abaixo, você pode ver que temos duas variáveis ​​de referência (chamadas de ponteiros em C / C ++, e acho que esse termo facilita a compreensão desse recurso.) No método principal. As variáveis ​​primitivas e de referência são mantidas na memória da pilha (lado esquerdo nas imagens abaixo). variáveis ​​de referência array1 e array2 "point" (como os programadores C / C ++ o chamam) ou referência a matrizes aeb, respectivamente, que são objetos (valores que essas variáveis ​​de referência mantêm são endereços de objetos) na memória heap (lado direito nas imagens abaixo) .

Exemplo de passagem por valor 1

Se passarmos o valor da variável de referência array1 como argumento para o método reverseArray, uma variável de referência será criada no método e essa variável de referência começará a apontar para a mesma matriz (a).

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

Exemplo de passagem por valor 2

Então, se dissermos

array1[0] = 5;

no método reverseArray, ele fará uma alteração na matriz a.

Temos outra variável de referência no método reverseArray (array2) que aponta para um array c. Se disséssemos

array1 = array2;

no método reverseArray, a variável de referência array1 no método reverseArray para de apontar para a matriz a e começa a apontar para a matriz c (linha pontilhada na segunda imagem).

Se retornarmos o valor da variável de referência array2 como o valor de retorno do método reverseArray e atribuir esse valor à variável de referência array1 no método principal, o array1 no main começará a apontar para o array c.

Então, vamos escrever todas as coisas que fizemos ao mesmo tempo agora.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

insira a descrição da imagem aqui

E agora que o método reverseArray acabou, suas variáveis ​​de referência (array1 e array2) desapareceram. O que significa que agora temos apenas as duas variáveis ​​de referência no método principal array1 e array2, que apontam para arrays ce respectivamente. Nenhuma variável de referência está apontando para o objeto (matriz) a. Portanto, é elegível para coleta de lixo.

Você também pode atribuir o valor do array2 no main ao array1. array1 começaria a apontar para b.

Michael
fonte
27

Eu criei um tópico dedicado a esse tipo de pergunta para qualquer linguagem de programação aqui .

Java também é mencionado . Aqui está o breve resumo:

  • Java passa parâmetros por valor
  • "por valor" é a única maneira no java de passar um parâmetro para um método
  • o uso de métodos do objeto fornecido como parâmetro alterará o objeto conforme as referências apontam para os objetos originais. (se esse próprio método altera alguns valores)
sven
fonte
27

Para resumir uma longa história, os objetos Java têm propriedades muito peculiares.

Em geral, Java tem tipos de primitivas ( int, bool, char, double, etc.) que são passados directamente por valor. Então o Java tem objetos (tudo o que deriva dejava.lang.Object ). Na verdade, os objetos sempre são manipulados por uma referência (uma referência sendo um ponteiro que você não pode tocar). Isso significa que, na verdade, os objetos são passados ​​por referência, pois as referências normalmente não são interessantes. No entanto, isso significa que você não pode alterar para qual objeto está apontado, pois a própria referência é passada por valor.

Isso soa estranho e confuso? Vamos considerar como C implementa passar por referência e passar por valor. Em C, a convenção padrão é passar por valor. void foo(int x)passa um int por valor. void foo(int *x)é uma função que não quer um int a, mas um ponteiro para um int: foo(&a). Alguém poderia usar isso com o &operador para passar um endereço variável.

Leve isso para C ++, e temos referências. As referências são basicamente (neste contexto) açúcar sintático que oculta a parte indicadora da equação: void foo(int &x)é chamada por foo(a), onde o próprio compilador sabe que é uma referência e o endereço da não referência adeve ser passado. Em Java, todas as variáveis ​​referentes a objetos são, na verdade, do tipo referência, forçando a chamada por referência para a maioria das intenções e propósitos, sem o controle (e a complexidade) de baixa granularidade proporcionados, por exemplo, pelo C ++.

Paul de Vrieze
fonte
Eu acho que está muito próximo do meu entendimento do objeto Java e de sua referência. O objeto em Java é passado para um método por uma cópia de referência (ou alias).
MaxZoom