parameter Java é “pass-by-reference” ou “pass-by-value”?




ref argument java (24)

Não posso acreditar que ninguém tenha mencionado Barbara Liskov ainda. Quando desenhou o CLU em 1974, ela se deparou com 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 esse caso específico de "chamada por valor onde o valor é uma referência".

Eu sempre achei que Java fosse passar por referência .

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

Eu não acho que entendi a diferença que eles estão fazendo.

Qual é a explicação?


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 dado 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, isso não reflete as alterações externas.

  • Se é um tipo de dado de objeto como Foo foo = new Foo () então neste caso a cópia do endereço do objeto passa como atalho de arquivo, suponha que tenhamos um arquivo de texto abc.txt em C: \ desktop e suponha que nós façamos um atalho de o mesmo arquivo e coloque isso dentro de C: \ desktop \ abc-shortcut, então quando você acessar o arquivo a partir de C: \ desktop \ abc.txt e escrever '', feche o arquivo e abra novamente o arquivo a partir do atalho. write 'é a maior comunidade online para programadores aprenderem', então a mudança total de arquivo será '' é a maior comunidade online para programadores aprenderem 'o que significa que não importa de onde você abre o arquivo, cada vez que estávamos acessando o mesmo arquivo, aqui podemos assumir Foo como um arquivo e supor foo armazenado em 123hd7h (endereço original como C: \ desktop \ abc.txt ) endereço e 234jdid (endereço copiado como C: \ desktop \ abc-atalho que realmente contém o endereço original do arquivo dentro) .. Então, para uma melhor compreensão, faça o arquivo de atalho e sentir ...


Apenas para mostrar o contraste, compare os seguintes snippets 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.


Java sempre passa argumentos por valor NOT por referência.

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

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. Declarar uma referência chamada f do tipo Foo e atribuí-la a um novo objeto do tipo Foo com um atributo "f" .

    Foo f = new Foo("f");
    

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

    public static void changeReference(Foo a)
    

  3. Conforme você chama o método changeReference , a referência a será atribuída ao objeto que é passado como um argumento.

    changeReference(f);
    

  4. Declarar uma referência chamada b do tipo Foo e atribuí-la a um novo objeto do tipo Foo com um atributo "b" .

    Foo b = new Foo("b");
    

  5. a = b está reatribuindo a referência a NOT f ao objeto cujo atributo é "b" .

  6. Conforme você chama o modifyReference(Foo c) , uma referência c é criada e atribuída ao objeto com o atributo "f" .

  7. c.setAttribute("c"); mudará o atributo do objeto que a referência c aponta para ele, e é o mesmo objeto que a referência f aponta para ele.

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


Eu sinto que discutir sobre "pass-by-reference vs pass-by-value" não é super-útil.

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

Curso intensivo em pilha / heap antes de chegarmos à implementação Java: os valores entram e saem da pilha de uma maneira ordenada, como uma pilha de pratos em uma lanchonete. A memória no heap (também conhecida como memória dinâmica) é aleatória e desorganizada. A JVM apenas encontra espaço sempre que pode e a libera à medida que as variáveis ​​que a usam não são mais necessárias.

OK. Primeiramente, os primitivos locais vão para a pilha. Então esse código:

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

resulta nisso:

Quando você declara e instancia um objeto. O objeto real vai no heap. O que vai na pilha? O endereço do objeto no heap. Programadores C ++ chamariam isso de um ponteiro, mas alguns desenvolvedores Java estão contra a palavra "ponteiro". Tanto faz. Só sei que o endereço do objeto vai na pilha.

Igual a:

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

Um array é um objeto, então ele também faz parte do heap. E os objetos da matriz? Eles obtêm seu próprio espaço de heap e o endereço de cada objeto entra no array.

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

Então, o que é passado quando você chama um método? Se você passar um objeto, o que você está realmente passando é o endereço do objeto. Alguns podem dizer o "valor" do endereço, e alguns dizem que é apenas uma referência ao objeto. Esta é a gênese da guerra santa entre os proponentes da "referência" e do "valor". O que você chama de não é tão importante quanto você entende que o que está sendo passado é o endereço para o 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 no heap, e o endereço para a string é armazenado na pilha e recebe o identificador hisName , pois o endereço da segunda String é o mesmo da primeira, nenhuma nova String é criada e nenhum espaço de heap novo é alocado, mas um novo identificador é criado na pilha. Em seguida, chamamos shout() : um novo quadro de pilha é criado e um novo identificador, name é criado e atribuído o endereço da String já existente.

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


Algumas correções para alguns posts.

C NÃO suporta passagem por referência. É sempre passar por valor. O C ++ suporta pass por referência, mas não é o padrão e é bastante perigoso.

Não importa qual seja o valor em Java: primitivo ou endereço (aproximadamente) de objeto, é SEMPRE passado por valor.

Se um objeto Java "se comporta" como se estivesse sendo passado por referência, isso é uma propriedade de mutabilidade e não tem absolutamente nada a ver com mecanismos de passagem.

Não sei por que isso é tão confuso, talvez porque muitos "programadores" de Java não são formalmente treinados e, portanto, não entendem o que realmente está acontecendo na memória?


Eu pensei em contribuir com essa resposta para adicionar mais detalhes das Especificações.

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

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

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

Ou da wikipedia, sobre o tema da passagem por referência

Na avaliação de chamada por referência (também referida como 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 de 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 sobre o assunto do pass-by-value

Em call-by-value, a expressão do argumento é avaliada, e o valor resultante é ligado à variável correspondente na função [...]. Se a função ou procedimento é capaz de atribuir valores aos seus parâmetros, apenas a sua cópia local é atribuída [...].

Em segundo lugar, precisamos saber o que o Java usa em suas invocações de método. Os estados da especificação da linguagem Java

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

Por isso, atribui (ou liga) 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 especificação Java Virtual Machine

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 classes criadas dinamicamente, arrays ou instâncias de classes ou arrays que implementam interfaces, respectivamente.

A especificação da linguagem Java também

Os valores de referência (geralmente apenas referências) são ponteiros para esses objetos e uma referência nula especial, que 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 retorno de tipo de referência e uma expressão de criação de instância ( new ...) são todas 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 ligam o valor de uma referência a uma Stringinstância ao parâmetro recém-criado do método param. Isso é exatamente o que a definição de passagem por valor descreve. Como tal, Java é pass-by-value .

O fato de você poder seguir a referência para invocar 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ê reatribuir a variável dentro do método, ela passaria despercebida ao chamador. Modificar o objeto referenciado pela variável é um conceito totalmente diferente.

Valores primitivos também são definidos no Java Virtual Machine Specification, here . O valor do tipo é o valor da integral ou ponto flutuante correspondente, codificado apropriadamente (8, 16, 32, 64, etc. bits).


Em java tudo é referência, então 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 do objeto Point, você acessará esse objeto através da referência pnt1. Então podemos dizer que em Java você manipula o objeto através de sua referência.

Java não passa argumentos de método por referência; passa-os por 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.

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

Como saída esperada será:

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

Nesta linha 'pass-by-value' entra em jogo ...

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

Referências pnt1e pnt2são passadas por valor para o método complicado, o que significa que agora o seu faz referência pnt1e pnt2tem seus copiesnomes arg1e arg2.So pnt1e arg1 aponta para o mesmo objeto. (Mesmo para o pnt2e arg2)

No trickymétodo:

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

Próximo 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. Em seguida, você move a referência arg1para apontar para o mesmo local como arg2referência. Finalmente arg2vai apontar para o mesmo lugar como temp.

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

Então, depois de executar o método tricky, quando você retornar main, você tem essa situação:

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

Eu sempre penso nisso como "passar por cópia". É uma cópia do valor, seja primitivo ou de referência. Se for uma primitiva, é 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 de java PassByCopy:

nome = nome Maxx
= Fido

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


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

/ **

Passe por Valor

Em Java, todos os parâmetros são passados ​​por valor, ou seja, a atribuição de 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:

/ ** * * passar 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 'Pass By Value tem a sensação de' Pass By Reference '

Algumas pessoas dizem que os tipos primitivos e 'String' são 'pass by value' e os objetos são 'pass by reference'.

Mas, a partir desse exemplo, podemos entender que é apenas passagem por valor, tendo em mente que aqui estamos passando a referência como o valor. ie: referência é passada por valor. É por isso que são capazes de mudar e ainda é verdade depois do escopo local. Mas não podemos alterar a referência real fora do escopo original. o que isso significa é demonstrado pelo 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 que foi mencionado no Example3 (PassByValueObjectCase1.java), não podemos alterar a referência real fora do escopo original. "

Nota: Eu não estou colando o código para private class Student. A definição de classe Studenté igual a Example3.

* /

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]

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

Argumentos Java são todos 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, isso é o mesmo: Variáveis ​​de objeto são ponteiros (buckets) 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 de objeto copiada contém o mesmo endereço (para o mesmo Objeto), o conteúdo / membros do Objeto ainda podem ser modificados em um método e posteriormente acessados ​​externamente, dando a ilusão de que o objeto (contido) em si foi passado por referência.

"String" Objetos parecem ser um perfeito contra-exemplo para a lenda urbana dizendo que "Objetos são passados ​​por referência":

Com efeito, dentro de um método você nunca será capaz de atualizar o valor de uma String passada como argumento:

Um objeto String, contém caracteres por uma matriz declarada final que não pode ser modificada. Apenas o endereço do objeto pode ser substituído por outro usando "novo". Usando "novo" para atualizar a variável, não permitirá que o objeto seja acessado de fora, desde que a variável foi inicialmente passada por valor e copiada.


Basicamente, reatribuir os parâmetros do 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);
}

irá imprimir 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 para"Hah!" .Se fosse a referência propriamente dita, então footeria redefinido bazpara null.


Como muitas pessoas mencionaram antes, Java é sempre pass-by-value

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

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 locais que possuem o mesmo valor das referências passadas (elas apontam para a e b respectivamente). Portanto, tentar alterar as referências de iA ou iB só mudará no escopo local e não fora desse método.


Java passa parâmetros por VALUE e por valor SOMENTE .

Para encurtar a história:

Para aqueles que vêm de C #: não há parâmetro "out".

Para aqueles que vêm de PASCAL: NÃO HÁ parâmetro "var" .

Isso significa que você não pode alterar a referência do próprio objeto, mas sempre pode alterar as propriedades do objeto.

Uma solução alternativa é usar o StringBuilderparâmetro String. E você sempre pode usar matrizes!


Eu só notei que você fez referência ao meu artigo .

O Java Spec diz que tudo em Java é passado por valor. Não existe "pass-by-reference" em Java.

A chave para entender isso é que algo como

Dog myDog;

não é um cachorro; Na verdade, é um ponteiro para um cão.

O que isso significa é quando você tem

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

você está essencialmente passando o endereço do objeto Dog criado para o método foo .

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

Suponha que o objeto Dog 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 someDog está configurado para o valor 42
  • na linha "AAA"
    • someDog é seguido pelo Dog para o qual aponta (o objeto Dog no endereço 42)
    • aquele 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 someDog para 74
  • na linha "CCC"
    • someDog é seguido pelo Dog para o qual aponta (o objeto Dog no endereço 74)
    • aquele Dog (o do endereço 74) é solicitado a mudar seu nome para Rowlf
  • então voltamos

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

O myDog mudou?

Há a chave.

Tendo em mente que o myDog é um ponteiro , e não um Dog real, a resposta é NÃO. myDog ainda tem o valor 42; ainda está apontando para o Dog original (mas note que por causa da linha "AAA", seu nome é agora "Max" - ainda o mesmo Cão; o valor do myDog não mudou).

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

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 que foram apontados. No entanto, você não pode alterar onde esse ponteiro aponta.

Em C ++, Ada, Pascal e outras linguagens que suportam pass-by-reference, você pode realmente mudar a variável que foi passada.

Se Java tivesse semântica de passagem por referência, o método foo que definimos acima teria mudado para onde myDog estava apontando quando atribuiu someDog na linha BBB.

Pense nos parâmetros de referência como sendo aliases para a variável transmitida. Quando esse alias é atribuído, a variável que foi transmitida também é atribuída.


Uma referência é sempre um valor quando representada, não importa qual idioma você usa.

Obtendo uma visão externa da caixa, vamos ver 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 propósito ao mesmo tempo).

Os dados na memória possuem um local e nesse local existe um valor (byte, word, whatever). Em Assembly, temos uma solução conveniente para dar um nome a determinado local (também conhecido como variável), mas ao compilar o código, o montador simplesmente substitui o Nome pelo local designado, assim como seu navegador substitui nomes de domínio por endereços IP.

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

Vamos dizer que temos 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 223º byte na memória, e seu valor será 47. Esse Ref2Foo pode ser uma variável técnica , não explicitamente criado pelo programa. Se você apenas olhar para 5 e 47 sem qualquer outra informação, verá apenas dois valores . Se você usá-los como referências, então para chegar a 5nós temos que viajar:

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

É assim que funcionam as tabelas de salto.

Se quisermos chamar um método / função / procedimento com o valor de Foo, há algumas maneiras possíveis de passar a variável ao método, dependendo da linguagem e seus vários modos de invocação de método:

  1. 5 é copiado para um dos registradores da CPU (ie. EAX).
  2. 5 recebe PUSHd para a pilha.
  3. 47 é copiado para um dos registradores da CPU
  4. 47 PUSHd para a pilha.
  5. 223 é copiado para um dos registradores da CPU.
  6. 223 recebe PUSHd para a pilha.

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

Agora passamos Foo para o método:

  • no caso 1. e 2. se você alterar Foo ( Foo = 9), isso afetará apenas o escopo local, pois você tem uma cópia do Valor. De dentro do método, não podemos nem determinar onde na memória estava o Foo original.
  • no caso 3. e 4. Se você usa construções de linguagem padrão e mudar Foo ( Foo = 11), ele poderia mudar Foo globalmente (depende da linguagem, isto é. Java ou como a de Pascal procedure findMin(x, y, z: integer; var m : integer); ). No entanto, se a linguagem permitir que você contorne o processo de desreferenciamento, você poderá mudar 47, por exemplo 49. Nesse ponto, Foo parece ter sido alterado se você o leu, porque você mudou o ponteiro local para ele. E se você fosse modificar este Foo dentro do método ( Foo = 12) você provavelmente irá FUBAR a execução do programa (aka. Segfault) porque você vai escrever em uma memória diferente do esperado, você pode até modificar uma área que é destinada a manter executável programa e escrevendo para ele irá modificar o código de execução (Foo agora não está em 47). MAS o valor de Foo47não mudou globalmente, apenas aquele dentro do método, porque 47também era uma cópia para o método.
  • no caso 5. e 6. se você modificar 223dentro do método, ele cria o mesmo caos como em 3. ou 4. (um ponteiro, apontando para um valor agora ruim, que é novamente usado como um ponteiro), mas este ainda é um local problema, como 223 foi copiado . No entanto, se você for capaz de desreferenciar Ref2Foo(isto é 223), atingir e modificar o valor apontado 47, digamos, para 49, ele afetará Foo globalmente , porque nesse caso os métodos obtiveram uma cópia, 223mas o referenciado 47existe apenas uma vez e alterando esse valor. para 49levar a cada Ref2Fooduplo desreferência a um valor errado.

Limitando detalhes insignificantes, mesmo as linguagens que passam por referência passarão valores às funções, mas essas funções sabem que precisam usá-las para fins de desreferenciação. Essa passagem-a-referência-como-valor é escondida do programador porque é praticamente inútil e a terminologia é apenas passagem por referência .

Passar por valor estrito também é inútil, significaria que um array de 100 Mbyte deveria ser copiado toda vez que chamamos um método com o array como argumento, portanto Java não pode ser passivamente passado por valor. Cada idioma passaria por uma referência a essa enorme matriz (como um valor) e empregaria o mecanismo de cópia na gravação se essa matriz puder ser alterada localmente dentro do método ou permitir que o método (como o Java) modifique a matriz globalmente (de a visão do chamador) e algumas linguagens permitem modificar o Valor da própria referência.

Portanto, em suma e na própria terminologia do Java, Java é pass-by-value onde value pode ser: um valor real ou um valor que é uma representação de uma referência .


Java tem apenas o valor passado. 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();
}

Para encurtar a história, os objetos Java possuem algumas propriedades muito peculiares.

Em geral, Java tem tipos de primitivas ( int, bool, char, double, etc) que são passados directamente por valor. Então Java tem objetos (tudo o que deriva java.lang.Object). Na verdade, os objetos sempre são manipulados por meio de uma referência (uma referência é 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, significa que você não pode alterar o objeto que está sendo apontado, pois a própria referência é passada por valor.

Isso soa estranho e confuso? Vamos considerar como o 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). Um usaria 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 esconde a parte do ponteiro da equação: void foo(int &x)é chamado 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 de referência, forçando, na verdade, chamada por referência para a maioria das intenções e finalidades sem o controle (e complexidade) refinado fornecido, por exemplo, pelo C ++.


Tanto quanto eu sei, Java só conhece a chamada por valor. Isso significa que, para tipos de dados primitivos, você trabalhará com uma cópia e, para objetos, trabalhará com uma cópia da referência aos objetos. No entanto, penso 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 copys que não afetam as referências no main. 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 em String, ele produzirá apenas Hello 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ê poderia fazer um wrapper para String como este, o que tornaria 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);
}

Editar: Eu acredito que esta é também a razão para usar StringBuffer quando se trata de "adicionar" duas seqüências de caracteres porque você pode modificar o objeto original que você não pode com objetos imutáveis ​​como String é.


Java passa referências a objetos por valor.


A distinção, ou talvez apenas a maneira como me lembro como costumava ter a mesma impressão do cartaz original, é a seguinte: Java é sempre passar 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.


Java passa referências por valor.

Então você não pode mudar a referência que é passada.


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

Geralmente, em referência Java, significa uma referência a um objeto . Mas os termos técnicos que passam 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.


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

Java é passado por valor de acordo com a especificação da linguagem Java:

Quando o método ou construtor é invocado (§15.12), os valores das expressões de argumentos reais inicializam variáveis ​​de parâmetros 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 .





pass-by-value