W3docs

Java: Passagem por Valor vs. Passagem por Referência

Por que Java é sempre passagem por valor, mesmo ao passar referências de objetos, e o que isso significa na prática.

Java é passagem por valor. Sempre. O que quer que você passe para um método — um int, uma String, um objeto personalizado, um array — o método recebe uma cópia do valor que você entregou. Esse valor pode ser um número, ou pode ser uma referência a um objeto, mas ainda é uma cópia.

Isso confunde muita gente porque um método pode alterar o conteúdo de um objeto que você passou, e essa alteração é visível para quem chamou. Então parece que o objeto foi passado por referência. Não foi. O que foi passado foi uma cópia da referência. Este capítulo mostra exatamente o que isso significa.

Esta página aborda como primitivos, objetos, arrays e strings se comportam quando entregues a um método, o modelo mental que explica cada caso e as consequências práticas para seus próprios parâmetros de método.

Primitivos são simples

Passar um primitivo copia seu valor para o parâmetro:

public static void doubleIt(int n) {
  n = n * 2;
}

int x = 5;
doubleIt(x);
System.out.println(x);    // 5

O método tem seu próprio n, separado de x. Reatribuir n não tem efeito sobre x. Todo mundo concorda que isso é passagem por valor.

Argumentos de objeto: a referência é copiada

Para tipos de objetos, a variável não contém o objeto — ela contém uma referência (um endereço que aponta para o objeto em algum lugar na memória). Quando você passa essa variável para um método, Java copia a referência, não o objeto:

Caller's variable        →   [ref to Dog A]
                                  |
                                  v
                              { Dog A: name="Rex" }
                                  ^
                                  |
Method's parameter       →   [ref to Dog A]

Ambas as variáveis agora apontam para o mesmo objeto Dog. É por isso que modificar o objeto por meio do parâmetro é visível no local da chamada:

public static void rename(Dog d) {
  d.setName("Buddy");           // mutates the shared object
}

Dog rex = new Dog("Rex");
rename(rex);
System.out.println(rex.getName());   // Buddy

Mas atribuir uma nova referência ao parâmetro não altera a variável do chamador:

public static void replace(Dog d) {
  d = new Dog("Buddy");         // parameter now points at a new Dog
}

Dog rex = new Dog("Rex");
replace(rex);
System.out.println(rex.getName());   // Rex — unchanged

O método atualizou sua própria cópia da referência. O rex do chamador ainda aponta para o Dog original.

O modelo mental dos dois ponteiros

Sempre que um método recebe um parâmetro de objeto, visualize duas setas no início: uma da variável do chamador, outra do parâmetro do método, ambas apontando para o mesmo objeto.

  • Modificar o objeto por qualquer uma das setas altera o que a outra seta enxerga.
  • Redirecionar uma seta para outro lugar não tem nenhum efeito na outra seta.

Essa única regra resolve todas as perguntas do tipo "Java é passagem por referência?".

Arrays seguem a mesma regra

Arrays são objetos, então passar um para um método copia a referência, não o conteúdo:

public static void zeroFirst(int[] xs) {
  xs[0] = 0;                    // mutates the shared array
}

int[] data = {1, 2, 3};
zeroFirst(data);
System.out.println(data[0]);    // 0

O método alterou um elemento por meio de sua cópia da referência, e o chamador vê a mudança porque ambas as referências apontam para o mesmo array.

Mas reatribuir o parâmetro a um array completamente novo não tem efeito sobre o chamador:

public static void resetArray(int[] xs) {
  xs = new int[]{0, 0, 0};      // parameter only
}

int[] data = {1, 2, 3};
resetArray(data);
System.out.println(data[0]);    // 1 — unchanged

Strings: a imutabilidade esconde o problema

Strings também são objetos, mas são imutáveis — não existe método que altere o conteúdo de uma String. Então o caso de mutação não pode acontecer:

public static void uppercase(String s) {
  s = s.toUpperCase();          // creates a new String
}

String name = "ada";
uppercase(name);
System.out.println(name);       // ada — unchanged

s.toUpperCase() retorna uma nova String; atribuí-la a s apenas atualiza o parâmetro. name ainda aponta para a "ada" original. Para "alterar" uma string, retorne a nova e deixe o chamador atribuí-la.

Por que isso importa

Três consequências práticas:

  1. Um método não pode "alterar para fora" um primitivo nem reatribuir a referência do chamador. Se você precisa desse efeito, retorne o novo valor: x = doubleIt(x); ou use um objeto wrapper que o chamador possa ler após a chamada.

  2. Um método pode modificar um objeto compartilhado — o que às vezes é desejado (preencher um array, popular uma lista) e às vezes é uma surpresa (os chamadores não esperam que sua lista mude).

  3. Cópia defensiva. Se um método não deve alterar o objeto do chamador, ou não mute o parâmetro, ou copie-o primeiro: Arrays.copyOf(xs, xs.length). Da mesma forma, se você retorna um array ou lista internos, os chamadores podem modificá-los pela referência, a menos que você retorne uma cópia.

Um exemplo prático

java— editable, runs on the server

O que vem a seguir

Você entende como argumentos individuais fluem para os métodos. Às vezes você não sabe de antemão quantos argumentos o chamador vai fornecer — pense em String.format, ou um max(...) que aceita qualquer número de valores. É para isso que servem os varargs.

Prática

Prática
Um método recebe um parâmetro de objeto e o reatribui: param = new Thing(). O que o chamador vê?
Um método recebe um parâmetro de objeto e o reatribui: param = new Thing(). O que o chamador vê?
Was this page helpful?