Як зробити еквівалент пропуску за посиланням для примітивів на 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 .
Стівен C

Відповіді:


171

У вас є кілька варіантів. Той, який має найбільше сенсу, насправді залежить від того, що ви намагаєтеся зробити.

Вибір 1: зробіть іграшкуNumber змінною публічного члена в класі

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

2
Чітке пояснення і особливо добре для надання декількох варіантів того, як кодувати бажаний ефект. Безпосередньо корисно для того, над чим я зараз працюю! Гарно, що це питання було закрито.
DarenW

1
Хоча ваш вибір 1 робить те, що ви намагаєтеся передати, я насправді повинен був повернутися і перевірити це ще раз. Питання задає питання, чи можна пройти посилання; так що вам справді слід було б виконати printlns в тому ж обсязі, що іграшка, передана функції, існує в. Як ви це маєте, printlns управляється в тому ж обсязі, що і прирост, який насправді не доводить суть, КУРС локальна копія, надрукована після збільшення, матиме інше значення, однак це не означає, що передана посилання буде. Знову ж таки, ваш приклад справді працює, але не доводить суть.
нуль298

Ні, я думаю, що це правильно саме так. Кожна функція "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)- як ви називаєте це, якщо його, наприклад, публічний статичний int, який повертає ціле число? Оскільки у мене є метод, який повертає число, але якщо я його не кудись називаю, він не використовується.
frank17

18

Зробити

class PassMeByRef { public int theValue; }

потім передайте посилання на його примірник. Зауважте, що методу, який мутує стан через свої аргументи, найкраще уникати, особливо в паралельному коді.


3
Захищений мною. Це не так на багатьох рівнях. Java передається за значенням - завжди. Немає винятків.
duffymo

14
@duffymo - Звичайно, ти можеш подати заявку як завгодно - але чи ти продумав те, що просив ОП? Він хоче пройти і int за посиланням - і якраз це робиться, якщо я передаю за значенням посилання на екземпляр з вищевказаного.
Інго

7
@duffymo: А? Ви помиляєтесь, інакше неправильно розумієте питання. Це ЯК є одним із традиційних способів в Java робити те, що вимагає ОП.
ToolmakerSteve

5
@duffymo: Ваш коментар неправильний на багатьох рівнях. Тож про надання корисних відповідей та коментарів, і ви просто просто спростуєте правильну відповідь лише тому, що (я думаю) це не відповідає вашому стилю та філософії програмування. Чи можете ви принаймні запропонувати краще пояснення чи навіть кращу відповідь на початкове запитання?
paercebal

2
@duffymo - це саме те, що я сказав: передайте посилання за значенням. Як ви думаєте, як передача посилань здійснюється мовами, які її сповільнюють?
Інго

11

Ви не можете передавати примітиви за посиланням на Java. Усі змінні типу об'єкта є, звичайно, покажчиками, звичайно, але ми називаємо їх "посиланнями", і вони також завжди передаються за значенням.

У ситуації, коли вам дійсно потрібно передати примітив за посиланням, те, що люди роблять іноді, це оголосити параметр як масив примітивного типу, а потім передати одноелементний масив як аргумент. Так ви передаєте посилання int [1], і в методі ви можете змінити вміст масиву.


1
Ні - всі класи обгортки незмінні - вони представляють фіксовану величину, яку неможливо змінити, коли об’єкт створений.
Ернест Фрідман-Хілл

1
"Ви не можете передавати примітиви за посиланням на Java": ОП, здається, це розуміє, оскільки вони контрастують на C ++ (де можна) з Java.
Raedwald

Слід зазначити, що "всі класи обгортки незмінні", зазначає Ернест, застосовується до вбудованих Integer, Double, Long тощо. JDK. Ви можете обійти це обмеження у вашому користувальницькому класі обгортки, як, наприклад, у відповіді Інго.
користувач3207158

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

Я використовую і подобається це рішення, тому що воно пропонує приємні складені дії і простіше інтегруватися в паралельні програми, які вже або можуть захотіти використовувати ці атомні класи в майбутньому.
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]);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.