Как сделать эквивалент передачи по ссылке для примитивов в Java

116

Этот код 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);   
    }   
}  

выведет это:

 
Номер игрушки в игре 5  
Номер игрушки в игре после прибавления 6  
Номер игрушки в главном 5  

В C ++ я могу передать toyNumberпеременную как переданную по ссылке, чтобы избежать затенения, т.е. создать копию той же переменной, что и ниже:

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

и вывод C ++ будет таким:

Номер игрушки в игре 5  
Номер игрушки в игре после прибавления 6  
Номер игрушки в главной 6  

Мой вопрос: каков эквивалентный код в Java, чтобы получить тот же результат, что и код C ++, учитывая, что Java передается по значению, а не по ссылке ?

Ученик
источник
22
Мне это не кажется дубликатом - предположительно повторяющийся вопрос касается того, как работает java и что означают термины, то есть образование, в то время как в этом вопросе конкретно спрашивается, как получить что-то похожее на поведение передачи по ссылке, что-то много C Программисты / C ++ / D / Ada могут задаться вопросом, чтобы выполнить практическую работу, не заботясь о том, почему java все передается по значению.
DarenW
1
@DarenW Полностью согласен - проголосовали за повторное открытие. О, и у вас достаточно репутации, чтобы делать то же самое :-)
Дункан Джонс
Обсуждение примитивов вводит в заблуждение, поскольку вопрос в равной степени относится и к эталонным значениям.
shmosel
«В C ++ я могу передать переменную toyNumber в качестве передачи по ссылке, чтобы избежать затенения» - это не затенение, поскольку toyNumberпеременная, объявленная в mainметоде, не входит в область видимости playметода. Затенение в C ++ и Java происходит только при вложении областей видимости. См. En.wikipedia.org/wiki/Variable_shadowing .
Stephen C

Ответы:

172

У вас есть несколько вариантов. Тот, который имеет наибольший смысл, на самом деле зависит от того, что вы пытаетесь сделать.

Вариант 1: сделать toyNumber общедоступной переменной-членом класса

class MyToy {
  public int toyNumber;
}

затем передайте ссылку на MyToy вашему методу.

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

Вариант 2: вернуть значение вместо передачи по ссылке

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
}

Этот выбор требует небольшого изменения в callsite в главном , так что он читает, toyNumber = temp.play(toyNumber);.

Вариант 3: сделать класс или статическую переменную

Если две функции являются методами одного класса или экземпляра класса, вы можете преобразовать toyNumber в переменную-член класса.

Вариант 4: создайте массив из одного элемента типа int и передайте его

Это считается хакерским приемом, но иногда используется для возврата значений из встроенных вызовов классов.

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
источник
2
Четкое объяснение и особенно хорошо для предоставления нескольких вариантов кодирования для достижения желаемого эффекта. Непосредственно полезно для того, над чем я работаю прямо сейчас! Безумие, что этот вопрос был закрыт.
DarenW
1
Хотя ваш вариант 1 действительно делает то, что вы пытаетесь передать, мне на самом деле пришлось вернуться и перепроверить его. Вопрос спрашивает, можно ли пройти по ссылке; так что на самом деле вы должны были делать printlns в той же области, в которой существует игрушка, переданная в функцию. Как и у вас, printlns работают в той же области, что и приращение, которое на самом деле не доказывает суть, конечно локальная копия, напечатанная после приращения, будет иметь другое значение, однако это не означает, что переданная ссылка будет. Опять же, ваш пример работает, но не доказывает сути.
zero298
Нет, думаю, это правильно. Каждая функция play () в моем ответе выше предназначена для замены исходной функции play (), которая также выводит значение до и после увеличения. В исходном вопросе есть println () в основном, который доказывает передачу по ссылке.
laslowh
Вариант 2 требует дальнейшего объяснения: основной сайт вызова должен быть изменен на, toyNumber = temp.play(toyNumber);чтобы он работал должным образом .
ToolmakerSteve
1
Это крайне уродливо и имеет какое-то хакерское чувство ... почему что-то столь простое не является частью языка?
shinzou
30

Java не вызывается по ссылке, это только по значению

Но все переменные объектного типа на самом деле являются указателями.

Итак, если вы используете изменяемый объект, вы увидите желаемое поведение

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

Вывод этого кода:

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)

Вы также можете увидеть это поведение в стандартных библиотеках. Например, Collections.sort (); Collections.shuffle (); Эти методы не возвращают новый список, но изменяют его объект аргумента.

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

Вывод этого кода:

run:
[1, 2, 3, 4, 5]
[3, 4, 1, 5, 2]
[1, 2, 3, 4, 5]
BUILD SUCCESSFUL (total time: 0 seconds)
Керем Байдоган
источник
2
Это не ответ на вопрос. Он бы ответил на вопрос, если бы предложил создать целочисленный массив, содержащий единственный элемент, а затем изменить этот элемент внутри метода play; напр mutableList[0] = mutableList[0] + 1;. Как предполагает Эрнест Фридман-Хилл.
ToolmakerSteve
С непримитивными. Значение объекта не является ссылкой. Возможно, вы хотели сказать: «значение параметра» - это «ссылка». Ссылки передаются по значению.
vlakov
Я пытаюсь сделать что-то подобное, но в вашем коде метод private static void play(StringBuilder toyNumber)- как вы его называете, если это, например, public static int, который возвращает целое число? Потому что у меня есть метод, который возвращает число, но если я его где-то не назову, он не используется.
frank17
18

Делать

class PassMeByRef { public int theValue; }

затем передайте ссылку на его экземпляр. Обратите внимание, что лучше избегать метода, который изменяет состояние с помощью своих аргументов, особенно в параллельном коде.

Инго
источник
3
Проголосовано мной против. Это не правильно на многих уровнях. Java передается по значению - всегда. Без исключений.
duffymo
14
@duffymo - Конечно, вы можете голосовать против, как хотите, но учли ли вы то, что спросил ОП? Он хочет передать int по ссылке - и только это будет выполнено, если я передам по значению ссылку на приведенный выше экземпляр.
Ingo
7
@duffymo: А? Вы ошибаетесь или неправильно понимаете вопрос. Это IS один из традиционных способов в Java делать то , что запросы OP.
ToolmakerSteve
5
@duffymo: Ваш комментарий неверен на многих уровнях. SO - это предоставление полезных ответов и комментариев, и вы просто понижаете правильный ответ только потому, что (я полагаю) он не соответствует вашему стилю программирования и философии. Не могли бы вы хотя бы предложить лучшее объяснение или даже лучший ответ на исходный вопрос?
paercebal
2
@duffymo это именно то, что я сказал: передайте ссылку по значению. Как вы думаете, как происходит переход по ссылке в языках, которые его замедляют?
Инго
11

В Java нельзя передавать примитивы по ссылке. Все переменные объектного типа, конечно, на самом деле являются указателями, но мы называем их «ссылками», и они также всегда передаются по значению.

В ситуации, когда вам действительно нужно передать примитив по ссылке, люди иногда будут объявлять параметр как массив примитивного типа, а затем передавать одноэлементный массив в качестве аргумента. Таким образом, вы передаете ссылку на int [1] и в методе можете изменять содержимое массива.

Эрнест Фридман-Хилл
источник
1
Нет - все классы-оболочки неизменяемы - они представляют фиксированное значение, которое нельзя изменить после создания объекта.
Эрнест Фридман-Хилл
1
«Вы не можете передавать примитивы по ссылке в Java»: OP, кажется, понимает это, поскольку они противопоставляют C ++ (где можно) и Java.
Raedwald
Следует отметить, что «все классы-оболочки неизменяемы», сказанное Эрнестом, применимо к встроенным в JDK Integer, Double, Long и т. Д. Вы можете обойти это ограничение с помощью своего настраиваемого класса-оболочки, как это сделал Ingo's Answer.
user3207158
10

Для быстрого решения вы можете использовать AtomicInteger или любую из атомарных переменных, которые позволят вам изменить значение внутри метода с помощью встроенных методов. Вот пример кода:

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

    }

}

Вывод:

MyNumber before method Call:0

MyNumber After method Call:100
premSiva
источник
Мне нравится это решение, потому что оно предлагает приятные составные действия и его легче интегрировать в параллельные программы, которые уже или могут захотеть использовать эти атомарные классы в будущем.
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
источник