Jak zrobić odpowiednik przekazywania przez referencję dla prymitywów w Javie


116

Ten kod 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);   
    }   
}  

wyświetli to:

 
Numer zabawki w grze 5  
Numer zabawki w grze po zwiększeniu 6  
Numer zabawki w głównym 5  

W C ++ mogę przekazać toyNumberzmienną jako przekaz przez odniesienie, aby uniknąć cieniowania, tj. Tworzenia kopii tej samej zmiennej, co poniżej:

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;   
} 

a wynik C ++ będzie taki:

Numer zabawki w grze 5  
Numer zabawki w grze po zwiększeniu 6  
Numer zabawki w głównym 6  

Moje pytanie brzmi: Jaki jest równoważny kod w Javie, który daje takie same dane wyjściowe jak kod C ++, biorąc pod uwagę, że Java jest przekazywana przez wartość, a nie przez odniesienie ?


22
To nie wydaje mi się duplikatem - rzekomo zduplikowane pytanie dotyczy tego, jak działa java i co oznaczają terminy, czyli edukacja, podczas gdy to pytanie dotyczy konkretnie, jak uzyskać coś przypominającego zachowanie przekazywania przez odniesienie, coś, co wiele C Programiści / C ++ / D / Ada mogą się zastanawiać, aby wykonać praktyczną pracę, nie przejmując się tym, dlaczego java jest w całości przekazywana.
DarenW,

1
@DarenW W pełni się zgadzam - głosowałem za ponownym otwarciem. Aha, i masz wystarczającą reputację, aby zrobić to samo :-)
Duncan Jones

Dyskusja wokół prymitywów jest raczej myląca, ponieważ pytanie dotyczy w równym stopniu wartości referencyjnych.
shmosel

„W C ++ mogę przekazać zmienną toyNumber jako przekazanie przez referencję, aby uniknąć cieniowania” - to nie jest shadowing, ponieważ toyNumberzmienna zadeklarowana w mainmetodzie nie znajduje się w zakresie w playmetodzie. Cieniowanie w C ++ i Javie ma miejsce tylko wtedy, gdy występuje zagnieżdżenie zakresów. Zobacz en.wikipedia.org/wiki/Variable_shadowing .
Stephen C

Odpowiedzi:


172

Masz kilka możliwości. Ten, który ma największy sens, zależy tak naprawdę od tego, co próbujesz zrobić.

Wybór 1: uczyń toyNumber publiczną zmienną składową w klasie

class MyToy {
  public int toyNumber;
}

następnie przekaż odwołanie do MyToy do swojej metody.

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);   
}

Wybór 2: zwraca wartość zamiast przekazywania przez odwołanie

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
}

Wybór ten będzie wymagać małej zmiany w callsite w głównej tak, że czyta, toyNumber = temp.play(toyNumber);.

Wybór 3: uczyń z niej zmienną klasową lub statyczną

Jeśli te dwie funkcje są metodami tej samej klasy lub instancji klasy, można przekonwertować toyNumber na zmienną składową klasy.

Wybór 4: Utwórz pojedynczą tablicę elementów typu int i przekaż ją

Jest to uważane za włamanie, ale czasami jest wykorzystywane do zwracania wartości z wywołań klas wbudowanych.

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]);   
}

2
Jasne wyjaśnienie i szczególnie dobre, ponieważ zapewnia wiele możliwości wyboru sposobu kodowania w celu uzyskania pożądanego efektu. Bezpośrednio przydatne w przypadku czegoś, nad czym teraz pracuję! To szaleństwo, że to pytanie zostało zamknięte.
DarenW,

1
Chociaż twój wybór 1 robi to, co próbujesz przekazać, w rzeczywistości musiałem wrócić i dwukrotnie to sprawdzić. Pytanie brzmi, czy możesz przekazać referencję; więc naprawdę powinieneś zrobić printlns w tym samym zakresie, w którym istnieje zabawka przekazana do funkcji. Tak jak to masz, printlns działają w tym samym zakresie, co przyrost, który tak naprawdę nie dowodzi celu, KURS kopia lokalna drukowana po inkrementacji będzie miała inną wartość, jednak nie oznacza to, że przekazana referencja będzie. Ponownie jednak, twój przykład działa, ale nie dowodzi tego sensu.
zero298

Nie, myślę, że to prawda, tak jest. Każda funkcja „play ()” w mojej odpowiedzi powyżej ma na celu zastąpienie oryginalnej funkcji „play ()”, która również wypisuje wartość przed i po zwiększaniu. Oryginalne pytanie ma w głównym println (), co dowodzi, że jest przekazywane przez odniesienie.
laslowh

Opcja 2 wymaga dalszych wyjaśnień: aby strona z zaproszeniem toyNumber = temp.play(toyNumber);działała zgodnie z oczekiwaniami, musi zostać zmieniona na adres.
ToolmakerSteve

1
To jest wyjątkowo brzydkie i ma charakter hackerski ... dlaczego coś tak podstawowego nie jest częścią języka?
shinzou,

30

Java nie jest wywoływana przez odniesienie , jest wywoływana tylko przez wartość

Ale wszystkie zmienne typu obiektowego są w rzeczywistości wskaźnikami.

Więc jeśli używasz mutowalnego obiektu, zobaczysz pożądane zachowanie

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);
    }
}

Wyjście tego kodu:

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)

Możesz zobaczyć to zachowanie również w bibliotekach standardowych. Na przykład Kolekcje.sort (); Collections.shuffle (); Te metody nie zwracają nowej listy, ale modyfikują jej obiekt argumentu.

    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);

Wyjście tego kodu:

run:
[1, 2, 3, 4, 5]
[3, 4, 1, 5, 2]
[1, 2, 3, 4, 5]
BUILD SUCCESSFUL (total time: 0 seconds)

2
To nie jest odpowiedź na pytanie. Odpowiedziałby na to pytanie, gdyby zasugerował utworzenie tablicy liczb całkowitych zawierającej pojedynczy element, a następnie zmodyfikowanie tego elementu w metodzie play; np mutableList[0] = mutableList[0] + 1;. Jak sugeruje Ernest Friedman-Hill.
ToolmakerSteve

Z nieprymitywami. Wartość obiektu nie jest odniesieniem. Może chciałbyś powiedzieć: „wartość parametru” to „odniesienie”. Odwołania są przekazywane według wartości.
vlakov

Próbuję zrobić coś podobnego, ale w twoim kodzie metoda private static void play(StringBuilder toyNumber)- jak to nazwać, jeśli jest to na przykład publiczny statyczny int, który zwraca liczbę całkowitą? Ponieważ mam metodę, która zwraca liczbę, ale jeśli gdzieś jej nie wywołam, nie jest używana.
frank17

18

Zrobić

class PassMeByRef { public int theValue; }

następnie przekaż referencję do jej instancji. Zauważ, że najlepiej unikać metody, która mutuje stan poprzez swoje argumenty, szczególnie w kodzie równoległym.


3
Głosowany przeze mnie. To jest złe na tak wielu poziomach. Java jest przekazywana przez wartość - zawsze. Bez wyjątków.
duffymo,

14
@duffymo - Oczywiście możesz głosować przeciw, jak chcesz - ale czy rozważałeś, o co prosił OP? Chce przekazać i int przez odniesienie - i po prostu to zostanie osiągnięte, jeśli przekażę wartość odniesienia do wystąpienia powyższego.
Ingo

7
@duffymo: Hę? Mylisz się, albo źle rozumiesz pytanie. To JEST jednym z tradycyjnych sposobów w Java robi co żądań op.
ToolmakerSteve

5
@duffymo: Twój komentarz jest błędny na wielu poziomach. SO polega na udzielaniu przydatnych odpowiedzi i komentarzy, a Ty po prostu odrzucasz poprawną odpowiedź tylko dlatego, że (jak sądzę) nie pasuje ona do Twojego stylu programowania i filozofii. Czy mógłbyś przynajmniej zaoferować lepsze wyjaśnienie lub nawet lepszą odpowiedź na pierwotne pytanie?
paercebal

2
@duffymo to jest dokładnie to, co powiedziałem: przekaż referencję według wartości. Jak myślisz, jak można przejść obok ref w językach, które go spowalniają?
Ingo

11

W Javie nie można przekazywać prymitywów przez odwołanie. Wszystkie zmienne typu obiektowego są oczywiście wskaźnikami, ale nazywamy je „referencjami” i zawsze są one również przekazywane przez wartość.

W sytuacji, gdy naprawdę potrzebujesz przekazać prymityw przez referencję, czasami ludzie będą zadeklarować parametr jako tablicę typu pierwotnego, a następnie przekazać jako argument tablicę jednoelementową. Więc przekazujesz referencję int [1], a w metodzie możesz zmienić zawartość tablicy.


1
Nie - wszystkie klasy opakowania są niezmienne - reprezentują stałą wartość, której nie można zmienić po utworzeniu obiektu.
Ernest Friedman-Hill

1
„W Javie nie można przekazywać prymitywów przez odwołanie”: OP wydaje się to rozumieć, ponieważ kontrastują one C ++ (gdzie można) z Javą.
Raedwald

Należy zauważyć, że "wszystkie klasy opakowujące są niezmienne", powiedziane przez Ernesta, odnosi się do wbudowanych w JDK wartości Integer, Double, Long itp. Możesz ominąć to ograniczenie za pomocą własnej niestandardowej klasy opakowania, tak jak to zrobiła Ingo's Answer.
user3207158

10

Aby szybko rozwiązać problem, możesz użyć AtomicInteger lub dowolnej zmiennej atomowej, która pozwoli ci zmienić wartość wewnątrz metody za pomocą wbudowanych metod. Oto przykładowy kod:

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);

    }

}

Wynik:

MyNumber before method Call:0

MyNumber After method Call:100

Używam i lubię to rozwiązanie, ponieważ oferuje przyjemne złożone akcje i jest łatwiejsze do zintegrowania z programami współbieżnymi, które już lub mogą chcieć używać tych klas atomowych w przyszłości.
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]);
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.