Como fazer o equivalente à passagem por referência para primitivas em Java

116

Este código Java:

public class XYZ {   
    public static void main(){  
        int toyNumber = 5;   
        XYZ temp = new XYZ();  
        temp.play(toyNumber);  
        System.out.println("Toy number in main " + toyNumber);  
    }

    void play(int toyNumber){  
        System.out.println("Toy number in play " + toyNumber);   
        toyNumber++;  
        System.out.println("Toy number in play after increement " + toyNumber);   
    }   
}  

irá gerar este:

 
Número do brinquedo em jogo 5  
Número do brinquedo em jogo após o incremento 6  
Número do brinquedo no 5 principal  

Em C ++, posso passar a toyNumbervariável como passagem por referência para evitar sombras, ou seja, criar uma cópia da mesma variável como abaixo:

void main(){  
    int toyNumber = 5;  
    play(toyNumber);  
    cout << "Toy number in main " << toyNumber << endl;  
}

void play(int &toyNumber){  
    cout << "Toy number in play " << toyNumber << endl;   
    toyNumber++;  
    cout << "Toy number in play after increement " << toyNumber << endl;   
} 

e a saída C ++ será esta:

Número do brinquedo em jogo 5  
Número do brinquedo em jogo após o incremento 6  
Número do brinquedo no 6 principal  

Minha pergunta é - Qual é o código equivalente em Java para obter a mesma saída que o código C ++, visto que Java é passado por valor em vez de por referência ?

Aluna
fonte
22
Isso não parece uma duplicata para mim - a pergunta supostamente duplicada trata de como java funciona e o que os termos significam, que é educação, enquanto esta pergunta pergunta especificamente como obter algo semelhante ao comportamento de passagem por referência, algo muito C Os programadores de / C ++ / D / Ada podem estar se perguntando a fim de realizar o trabalho prático, sem se importar por que o java é passado por valor.
DarenW,
1
@DarenW Concordo plenamente - votei para reabrir. Ah, e você tem reputação suficiente para fazer o mesmo :-)
Duncan Jones
A discussão em torno dos primitivos é um tanto enganosa, pois a questão se aplica igualmente aos valores de referência.
shmosel
"Em C ++, posso passar a variável toyNumber como passagem por referência para evitar sombreamento" - isso não é sombreamento porque a toyNumbervariável declarada no mainmétodo não está no escopo do playmétodo. O sombreamento em C ++ e Java só acontece quando há aninhamento de escopos. Consulte en.wikipedia.org/wiki/Variable_shadowing .
Stephen C

Respostas:

171

Você tem várias opções. O que faz mais sentido realmente depende do que você está tentando fazer.

Escolha 1: torne toyNumber uma variável de membro público em uma classe

class MyToy {
  public int toyNumber;
}

em seguida, passe uma referência a um MyToy para o seu método.

void play(MyToy toy){  
    System.out.println("Toy number in play " + toy.toyNumber);   
    toy.toyNumber++;  
    System.out.println("Toy number in play after increement " + toy.toyNumber);   
}

Opção 2: retornar o valor em vez de passar por referência

int play(int toyNumber){  
    System.out.println("Toy number in play " + toyNumber);   
    toyNumber++;  
    System.out.println("Toy number in play after increement " + toyNumber);   
    return toyNumber
}

Essa escolha exigiria uma pequena alteração no call center em principal para que fosse toyNumber = temp.play(toyNumber);,.

Escolha 3: torne-o uma classe ou variável estática

Se as duas funções forem métodos na mesma classe ou instância de classe, você pode converter toyNumber em uma variável de membro de classe.

Opção 4: crie uma única matriz de elemento do tipo int e passe-a

Isso é considerado um hack, mas às vezes é empregado para retornar valores de invocações de classes embutidas.

void play(int [] toyNumber){  
    System.out.println("Toy number in play " + toyNumber[0]);   
    toyNumber[0]++;  
    System.out.println("Toy number in play after increement " + toyNumber[0]);   
}
Laslowh
fonte
2
Explicação clara e especialmente boa para fornecer várias opções sobre como codificar para o efeito desejado. Ajudam diretamente em algo em que estou trabalhando agora! É uma loucura que esta questão foi encerrada.
DarenW
1
Embora sua escolha 1 faça o que você está tentando transmitir, eu realmente tive que voltar e verificar novamente. A questão pergunta se você pode passar por referência; então realmente você deveria ter feito as printlns no mesmo escopo em que o brinquedo passado para a função existe. Do jeito que você tem, as printlns são operadas no mesmo escopo que o incremento que realmente não prova o ponto, CLARO uma cópia local impressa após um incremento terá um valor diferente, porém isso não significa que a referência passada terá. Novamente, porém, seu exemplo funciona, mas não prova o ponto.
zero298
Não, acho que está correto do jeito que está. Cada função "play ()" em minha resposta acima tem o objetivo de substituir a função "play ()" original, que também imprimiu o valor antes e depois do incremento. A questão original tem o println () principal que prova passagem por referência.
Laslowh,
A opção 2 requer mais explicações: o site da chamada em principal deve ser alterado para toyNumber = temp.play(toyNumber);para que funcione como desejado.
ToolmakerSteve
1
Isso é extremamente feio e tem um toque hackish ... por que algo tão básico não faz parte da linguagem?
shinzou de
30

Java não é chamada por referência , é chamada apenas por valor

Mas todas as variáveis ​​do tipo de objeto são, na verdade, ponteiros.

Então, se você usar um objeto mutável, você verá o comportamento que deseja

public class XYZ {

    public static void main(String[] arg) {
        StringBuilder toyNumber = new StringBuilder("5");
        play(toyNumber);
        System.out.println("Toy number in main " + toyNumber);
    }

    private static void play(StringBuilder toyNumber) {
        System.out.println("Toy number in play " + toyNumber);
        toyNumber.append(" + 1");
        System.out.println("Toy number in play after increement " + toyNumber);
    }
}

Saída deste código:

run:
Toy number in play 5
Toy number in play after increement 5 + 1
Toy number in main 5 + 1
BUILD SUCCESSFUL (total time: 0 seconds)

Você pode ver esse comportamento nas bibliotecas padrão também. Por exemplo Collections.sort (); Coleções.shuffle (); Esses métodos não retornam uma nova lista, mas modificam seu objeto de argumento.

    List<Integer> mutableList = new ArrayList<Integer>();

    mutableList.add(1);
    mutableList.add(2);
    mutableList.add(3);
    mutableList.add(4);
    mutableList.add(5);

    System.out.println(mutableList);

    Collections.shuffle(mutableList);

    System.out.println(mutableList);

    Collections.sort(mutableList);

    System.out.println(mutableList);

Saída deste código:

run:
[1, 2, 3, 4, 5]
[3, 4, 1, 5, 2]
[1, 2, 3, 4, 5]
BUILD SUCCESSFUL (total time: 0 seconds)
Kerem Baydoğan
fonte
2
Esta não é uma resposta à pergunta. Ele teria respondido à pergunta se sugerisse fazer um array de inteiros que contivesse um único elemento e, em seguida, modificar esse elemento dentro do método play; por exemplo mutableList[0] = mutableList[0] + 1;. Como sugere Ernest Friedman-Hill.
Toolmaker Steve
Com não primitivos. O valor do objeto não é uma referência. Talvez você queira dizer: "valor do parâmetro" é "uma referência". As referências são passadas por valor.
vlakov de
Estou tentando fazer algo semelhante, mas em seu código, o método private static void play(StringBuilder toyNumber)- como você o chama se for, por exemplo, público estático int que retorna um inteiro? Porque tenho um método que retorna um número, mas se eu não ligar para ele em algum lugar, ele não está em uso.
frank17
18

Faça um

class PassMeByRef { public int theValue; }

em seguida, passe uma referência a uma instância dele. Observe que é melhor evitar um método que altera o estado por meio de seus argumentos, especialmente em código paralelo.

Ingo
fonte
3
Votos negados por mim. Isso está errado em muitos níveis. Java é passado por valor - sempre. Sem exceções.
duffymo de
14
@duffymo - Claro que você pode votar negativamente como quiser - mas você considerou o que o OP pediu? Ele quer passar e int por referência - e isso só é conseguido se eu passar por valor uma referência a uma instância do acima.
Ingo,
7
@duffymo: Hã? Você está enganado, ou então entendeu mal a pergunta. Esta é uma das formas tradicionais em Java de fazer o que o OP solicita.
Toolmaker Steve
5
@duffymo: Seu comentário está errado em muitos níveis. SO é sobre fornecer respostas e comentários úteis, e você simplesmente rejeita uma resposta correta apenas porque (eu acho) ela não se ajusta ao seu estilo e filosofia de programação. Você poderia pelo menos oferecer uma explicação melhor, ou mesmo uma resposta melhor para a pergunta original?
paercebal
2
@duffymo foi exatamente o que eu falei: passe uma ref por valor. Como você acha que passar por ref é realizado em linguagens que o tornam lento?
Ingo
11

Você não pode passar primitivos por referência em Java. Todas as variáveis ​​do tipo de objeto são, na verdade, ponteiros, é claro, mas as chamamos de "referências" e também são sempre passadas por valor.

Em uma situação em que você realmente precisa passar um primitivo por referência, o que as pessoas às vezes farão é declarar o parâmetro como um array do tipo primitivo e, em seguida, passar um array de um único elemento como argumento. Então você passa uma referência int [1], e no método, você pode mudar o conteúdo do array.

Ernest Friedman-Hill
fonte
1
Não - todas as classes de wrapper são imutáveis - elas representam um valor fixo que não pode ser alterado depois que o objeto é criado.
Ernest Friedman-Hill,
1
"Você não pode passar primitivos por referência em Java": o OP parece entender isso, pois eles estão contrastando C ++ (onde você pode) com Java.
Raedwald
Deve-se notar que "todas as classes de wrapper são imutáveis" dito por Ernest se aplica a Integer, Double, Long etc. do JDK. Você pode ignorar essa restrição com sua classe de wrapper personalizada, como o que fez a Resposta de Ingo.
user3207158
10

Para uma solução rápida, você pode usar AtomicInteger ou qualquer uma das variáveis ​​atômicas que permitem alterar o valor dentro do método usando os métodos embutidos. Aqui está o código de amostra:

import java.util.concurrent.atomic.AtomicInteger;


public class PrimitivePassByReferenceSample {

    /**
     * @param args
     */
    public static void main(String[] args) {

        AtomicInteger myNumber = new AtomicInteger(0);
        System.out.println("MyNumber before method Call:" + myNumber.get());
        PrimitivePassByReferenceSample temp = new PrimitivePassByReferenceSample() ;
        temp.changeMyNumber(myNumber);
        System.out.println("MyNumber After method Call:" + myNumber.get());


    }

     void changeMyNumber(AtomicInteger myNumber) {
        myNumber.getAndSet(100);

    }

}

Resultado:

MyNumber before method Call:0

MyNumber After method Call:100
premSiva
fonte
Eu uso e gosto dessa solução porque ela oferece boas ações compostas e é mais fácil de integrar em programas concorrentes que já usam ou podem querer usar essas classes atômicas no futuro.
RAnders00
2
public static void main(String[] args) {
    int[] toyNumber = new int[] {5};
    NewClass temp = new NewClass();
    temp.play(toyNumber);
    System.out.println("Toy number in main " + toyNumber[0]);
}

void play(int[] toyNumber){
    System.out.println("Toy number in play " + toyNumber[0]);
    toyNumber[0]++;
    System.out.println("Toy number in play after increement " + toyNumber[0]);
}
itun
fonte