Muitas linguagens de programação permitem passar objetos por referência ou por valor. Em Java, só podemos passar parâmetros de objetos por valor. Isto impõe limites e também levanta questões. Por exemplo, se o valor do parâmetro for alterado no método, o que acontece com o valor após a execução do método? Você também pode estar se perguntando como o Java gerencia valores de objetos no heap de memória. Este artigo ajuda você a resolver essas e outras questões comuns sobre referências de objetos em Java.
Passando referências de objetos em Java
Neste artigo você aprenderá a diferença entre passar por referência e passar por valor em Java e como passar referências de objetos Java:
- 'Passagem por referência' e 'passagem por valor' definidas
- Referências de objetos Java são passadas por valor
- Passando tipos primitivos em Java
- Passando referências de objetos imutáveis em Java
- Passando referências de objetos mutáveis em Java
- O que evitar ao passar referências de objetos
- O que lembrar sobre a passagem de referências de objetos
'Passagem por referência' e 'passagem por valor' definidas
Passe por referência significa que passamos o local na memória onde o valor da variável está armazenado e passar por valor significa que passamos uma cópia do valor real da variável. É um pouco mais complicado do que isso, claro, mas esta definição é um bom ponto de partida.
- Passando por valor significa que passamos uma cópia do valor.
- Passando por referência significa que passamos a referência real para a variável na memória.
Referências de objetos Java são passadas por valor
Todas as referências de objetos em Java são passadas por valor. Isso significa que uma cópia do valor será passada para um método. A parte complicada é que passar uma cópia do valor altera o valor real do objeto. Este exemplo deve ajudá-lo a entender o porquê:
public class ObjectReferenceExample {
public static void main(String... doYourBest) {
Simpson simpson = new Simpson();
transformIntoHomer(simpson);
System.out.println(simpson.name);
}
static void transformIntoHomer(Simpson simpson) {
simpson.name = "Homer";
}
}
class Simpson {
String name;
}
O que você acha que simpson.name
será depois do transformIntoHomer
método é executado?
Neste caso, será Homero! A razão é que as variáveis de objeto Java são simplesmente referências que apontam para objetos reais na pilha de memória. Portanto, mesmo que Java passe parâmetros para métodos por valor, se a variável apontar para uma referência de objeto, o objeto real também será alterado.
Se você ainda não tem certeza de como isso funciona, considere o seguinte diagrama:
Passando tipos primitivos em Java
Assim como os tipos de objetos, os tipos primitivos também são passados por valor. Você consegue adivinhar o que acontecerá com os tipos primitivos no exemplo de código a seguir?
public class PrimitiveByValueExample {
public static void main(String... primitiveByValue) {
int homerAge = 30;
changeHomerAge(homerAge);
System.out.println(homerAge);
}
static void changeHomerAge(int homerAge) {
homerAge = 35;
}
}
Se você determinou que o valor mudaria para 30, você está correto. São 30 porque (novamente) Java passa parâmetros de objeto por valor. O número 30 é apenas uma cópia do valor, não o valor real. Os tipos primitivos são alocados na memória da pilha, portanto apenas o valor local será alterado. Neste caso, não há referência de objeto.
Passando referências de objetos imutáveis em Java
E se fizéssemos o mesmo teste com um valor imutável String
objeto? Observe o que acontece quando alteramos o valor de a String
:
public class StringValueChange {
public static void main(String... doYourBest) {
String name = "";
changeToHomer(name);
System.out.println(name);
}
static void changeToHomer(String name) {
name = "Homer";
}
}
Qual você acha que será o resultado? Se você adivinhou “” então parabéns! Isso acontece porque um String
objeto é imutávelo que significa que os campos dentro do String
são finais e não podem ser alterados.
Fazendo o String
class immutable nos dá melhor controle sobre um dos objetos mais usados em Java. Se o valor de um String
pudesse ser alterado, criaria muitos bugs. Observe, também, que não estamos alterando um atributo do String
aula; em vez disso, estamos simplesmente atribuindo um novo String
valor para isso. Neste caso, o valor “Homer” será passado para name
no changeToHomer
método. O String
“Homer” poderá ser coletado como lixo assim que o changeToHomer
método conclui a execução. Mesmo que o objeto não possa ser alterado, a variável local será.
Passando referências de objetos mutáveis em Java
Diferente String
a maioria dos objetos no JDK são mutáveis, como o StringBuilder
aula. O exemplo abaixo é semelhante ao anterior, mas apresenta StringBuilder
em vez de String
:
static class MutableObjectReference {
public static void main(String... mutableObjectExample) {
StringBuilder name = new StringBuilder("Homer ");
addSureName(name);
System.out.println(name);
}
static void addSureName(StringBuilder name) {
name.append("Simpson");
}
}
Você consegue adivinhar a saída deste exemplo? Neste caso, por estarmos trabalhando com um objeto mutável, a saída será “Homer Simpson”. Você poderia esperar o mesmo comportamento de qualquer outro objeto mutável em Java.
Resumo do que você aprendeu
Você aprendeu que objetos Java são passados por valor, o que significa que uma cópia do valor é passada. Basta lembrar que o valor copiado aponta para um objeto real na pilha de memória Java. Além disso, lembre-se de que passar por valor altera o valor do objeto real.
O que evitar ao passar referências de objetos Java
- Não tente alterar um valor imutável por referência.
- Não tente alterar uma variável primitiva por referência.
- Não espere que o objeto real não mude quando você alterar um parâmetro de objeto mutável em um método (ele mudará).
O que lembrar sobre a passagem de referências de objetos Java
- Java sempre passa variáveis de parâmetro por valor.
- Variáveis de objeto em Java sempre apontam para o objeto real na pilha de memória.
- O valor de um objeto mutável pode ser alterado quando ele é passado para um método.
- O valor de um objeto imutável não pode ser alterado, mesmo que seja passado um novo valor.
Teste o que você aprendeu sobre referências de objetos
Agora, vamos testar o que você aprendeu sobre referências a objetos. No exemplo de código abaixo, você vê o imutável String
e o mutável StringBuilder
aula. Cada um está sendo passado como parâmetro para um método. Sabendo que Java só passa por valor, qual você acredita que será a saída quando o método principal desta classe for executado?
public class DragonWarriorReferenceChallenger {
public static void main(String... doYourBest) {
StringBuilder warriorProfession = new StringBuilder("Dragon ");
String warriorWeapon = "Sword ";
changeWarriorClass(warriorProfession, warriorWeapon);
System.out.println("Warrior=" + warriorProfession + " Weapon=" + warriorWeapon);
}
static void changeWarriorClass(StringBuilder warriorProfession, String weapon) {
warriorProfession.append("Knight");
weapon = "Dragon " + weapon;
weapon = null;
warriorProfession = null;
}
}
Aqui estão as opções, verifique a resposta no final do artigo.
A: Warrior=null Weapon=null
B: Warrior=Dragon Weapon=Dragon
C: Warrior=Dragon Knight Weapon=Dragon Sword
D: Warrior=Dragon Knight Weapon=Sword
Resolvendo o desafio
O primeiro parâmetro no exemplo acima é o warriorProfession
variável, que é um objeto mutável. O segundo parâmetro, arma, é um valor imutável String
:
static void changeWarriorClass(StringBuilder warriorProfession, String weapon) {
...
}
Na primeira linha deste método, anexamos o Knight
valor para o warriorProfession
variável. Lembre-se disso warriorProfession
é um objeto mutável; portanto, o objeto real será alterado e o valor dele será “Dragon Knight”.
warriorProfession.append("Knight");
Na segunda instrução, o local imutável String
variável será alterada para “Dragon Sword”. O objeto real nunca será alterado, entretanto, uma vez que String
é imutável e seus atributos são finais:
weapon = "Dragon " + weapon;
Finalmente, passamos null
às variáveis aqui, mas não aos objetos. Os objetos permanecerão os mesmos enquanto ainda estiverem acessíveis externamente – neste caso através do método main. E, embora as variáveis locais sejam nulas, nada acontecerá com os objetos:
weapon = null;
warriorProfession = null;
Disto podemos concluir que os valores finais do nosso mutável StringBuilder
e imutável String
vai ser:
System.out.println("Warrior=" + warriorProfession + " Weapon=" + warriorWeapon);
O único valor que mudou no changeWarriorClass
método era warriorProfession
porque é um mutável StringBuilder
objeto. Observe que warriorWeapon
não mudou porque é imutável String
objeto.
A saída correta do nosso código Challenger seria:
D: Warrior=Dragon Knight Weapon=Sword
.
Desafio de vídeo! Depurando referências de objetos em Java
A depuração é uma das maneiras mais fáceis de absorver totalmente os conceitos de programação e, ao mesmo tempo, melhorar seu código. Neste vídeo, você pode acompanhar enquanto eu depuro e explico referências de objetos em Java.