วิธีการเทียบเท่า pass by reference สำหรับ primitives ใน 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ตัวแปรเป็น pass by reference เพื่อหลีกเลี่ยงการเกิดเงาเช่นการสร้างสำเนาของตัวแปรเดียวกันดังต่อไปนี้:

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 จึงเป็น pass-by-value ทั้งหมด
DarenW

1
@DarenW ฉันเห็นด้วยอย่างยิ่ง - ได้โหวตให้เปิดอีกครั้ง โอ้และคุณมีชื่อเสียงมากพอที่จะทำเช่นเดียวกัน :-)
Duncan Jones

การอภิปรายเกี่ยวกับสิ่งดั้งเดิมนั้นค่อนข้างทำให้เข้าใจผิดเนื่องจากคำถามนี้ใช้กับค่าอ้างอิงอย่างเท่าเทียมกัน
shmosel

"ใน C ++ ฉันสามารถส่งตัวแปร toyNumber เป็น pass by reference เพื่อหลีกเลี่ยงการเกิดเงา" - นี่ไม่ใช่การทำเงาเนื่องจาก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
}

ตัวเลือกนี้จะต้องมีการเปลี่ยนแปลงเล็กน้อยกับ callite ใน main เพื่อให้อ่าน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 จะทำงานในขอบเขตเดียวกันกับการเพิ่มขึ้นซึ่งไม่ได้พิสูจน์ประเด็นของ COURSE สำเนาในเครื่องที่พิมพ์หลังจากการเพิ่มจะมีค่าที่แตกต่างกันอย่างไรก็ตามนั่นไม่ได้หมายความว่าการอ้างอิงที่ผ่านจะ อีกครั้งตัวอย่างของคุณใช้ได้ผล แต่ไม่ได้พิสูจน์ประเด็น
zero298

ไม่ฉันคิดว่ามันถูกต้องในแบบที่เป็นอยู่ แต่ละฟังก์ชั่น "play ()" ในคำตอบของฉันด้านบนมีจุดมุ่งหมายเพื่อแทนที่ดร็อปอินสำหรับฟังก์ชัน "play ()" ดั้งเดิมซึ่งจะพิมพ์ค่าก่อนและหลังการเพิ่มขึ้นด้วย คำถามเดิมมี println () เป็นหลักซึ่งพิสูจน์ได้ว่าผ่านการอ้างอิง
laslowh

ตัวเลือกที่ 2 ต้องการคำอธิบายเพิ่มเติม: ต้องเปลี่ยนไซต์การโทรใน main เพื่อtoyNumber = temp.play(toyNumber);ให้ทำงานได้ตามต้องการ
ToolmakerSteve

1
นี่เป็นเรื่องที่น่าเกลียดมากและมีความรู้สึกแฮ็ก ... ทำไมบางสิ่งที่เรียบง่ายจึงไม่เป็นส่วนหนึ่งของภาษา?
shinzou

30

Java ไม่ได้ถูกเรียกโดยการอ้างอิงแต่เป็นการเรียกตามค่าเท่านั้น

แต่ตัวแปรทั้งหมดของประเภทออบเจ็กต์เป็นตัวชี้

ดังนั้นหากคุณใช้ Mutable Object คุณจะเห็นพฤติกรรมที่คุณต้องการ

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;. ตามที่ Ernest Friedman-Hill แนะนำ
ToolmakerSteve

ด้วย non-primitives. ค่าของวัตถุไม่ใช่ข้อมูลอ้างอิง บางทีคุณอาจต้องการบอกว่า: "ค่าพารามิเตอร์" คือ "ข้อมูลอ้างอิง" การอ้างอิงถูกส่งต่อด้วยค่า
vlakov

ฉันกำลังพยายามทำสิ่งที่คล้ายกัน แต่ในโค้ดของคุณวิธีการprivate static void play(StringBuilder toyNumber)- คุณจะเรียกมันได้อย่างไรว่ามันเป็น int แบบคงที่สาธารณะซึ่งส่งคืนจำนวนเต็ม เพราะฉันมีเมธอดที่ส่งคืนตัวเลข แต่ถ้าฉันไม่โทรไปที่ไหนก็ไม่ได้ใช้งาน
frank17

18

ทำ

class PassMeByRef { public int theValue; }

จากนั้นส่งการอ้างอิงไปยังอินสแตนซ์ของมัน โปรดทราบว่าวิธีที่ทำให้สถานะกลายพันธุ์ผ่านอาร์กิวเมนต์นั้นดีที่สุดโดยเฉพาะอย่างยิ่งในโค้ดคู่ขนาน


3
โหวตโดยฉัน สิ่งนี้ผิดในหลายระดับ Java ส่งผ่านค่า - เสมอ ไม่มีข้อยกเว้น.
duffymo

14
@duffymo - แน่นอนคุณสามารถลงคะแนนได้ตามต้องการ - แต่คุณได้พิจารณาสิ่งที่ OP ถามหรือยัง? เขาต้องการส่งผ่านและ int โดยการอ้างอิง - และแค่นี้ก็สำเร็จแล้วถ้าฉันส่งต่อค่าอ้างอิงไปยังอินสแตนซ์ด้านบน
Ingo

7
@duffymo: หือ? คุณเข้าใจผิดหรือเข้าใจผิดในคำถาม นี่เป็นวิธีดั้งเดิมอย่างหนึ่งใน Java ในการทำสิ่งที่ OP ร้องขอ
ToolmakerSteve

5
@duffymo: ความคิดเห็นของคุณผิดในหลายระดับ SO เป็นเรื่องเกี่ยวกับการให้คำตอบและความคิดเห็นที่เป็นประโยชน์และคุณเพียงแค่ลงคะแนนคำตอบที่ถูกต้องเพียงเพราะ (ฉันเดาว่า) มันไม่เหมาะกับสไตล์การเขียนโปรแกรมและปรัชญาของคุณ อย่างน้อยคุณช่วยให้คำอธิบายที่ดีกว่านี้หรือแม้แต่คำตอบที่ดีกว่าสำหรับคำถามเดิมได้ไหม
paercebal

2
@duffymo นั่นคือสิ่งที่ฉันพูด: ส่งการอ้างอิงตามค่า คุณคิดว่า pass by ref ทำได้อย่างไรในภาษาที่ทำให้มันช้า?
Ingo

11

คุณไม่สามารถส่งผ่าน primitives โดยการอ้างอิงใน Java แน่นอนว่าตัวแปรทั้งหมดของประเภทออบเจ็กต์เป็นตัวชี้ แต่เราเรียกมันว่า "การอ้างอิง" และตัวแปรเหล่านี้จะถูกส่งผ่านด้วยค่าเสมอ

ในสถานการณ์ที่คุณจำเป็นต้องส่งผ่านแบบดั้งเดิมโดยการอ้างอิงสิ่งที่ผู้คนจะทำบางครั้งคือการประกาศพารามิเตอร์เป็นอาร์เรย์ของประเภทดั้งเดิมจากนั้นส่งอาร์เรย์องค์ประกอบเดียวเป็นอาร์กิวเมนต์ ดังนั้นคุณจึงส่งการอ้างอิง int [1] และในวิธีนี้คุณสามารถเปลี่ยนเนื้อหาของอาร์เรย์ได้


1
ไม่ - คลาส Wrapper ทั้งหมดไม่เปลี่ยนรูป - แสดงถึงค่าคงที่ซึ่งไม่สามารถเปลี่ยนแปลงได้เมื่อสร้างออบเจ็กต์แล้ว
Ernest Friedman-Hill

1
"คุณไม่สามารถส่งผ่าน primitives โดยการอ้างอิงใน Java": ดูเหมือนว่า OP จะเข้าใจสิ่งนี้เนื่องจาก C ++ (ที่คุณทำได้) ตัดกันกับ Java
Raedwald

ควรสังเกตว่า "คลาส Wrapper ทั้งหมดไม่สามารถเปลี่ยนรูปได้" กล่าวโดย Ernest ใช้กับจำนวนเต็ม build-in, Double, Long และอื่น ๆ ของ JDK คุณสามารถข้ามข้อ จำกัด นี้ได้ด้วยคลาส Wrapper ที่กำหนดเองเช่นเดียวกับคำตอบของ Ingo
user3207158

10

สำหรับวิธีการแก้ปัญหาอย่างรวดเร็วคุณสามารถใช้ AtomicInteger หรือตัวแปรอะตอมใด ๆ ซึ่งจะช่วยให้คุณเปลี่ยนค่าภายในวิธีการโดยใช้วิธีการ inbuilt นี่คือโค้ดตัวอย่าง:

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]);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.