Cách thực hiện tương đương với pass bằng tham chiếu cho các nguyên hàm trong Java


115

Mã Java này:

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

sẽ xuất ra điều này:

 
Số đồ chơi trong trò chơi 5  
Số đồ chơi trong khi chơi 6  
Số đồ chơi trong chính 5  

Trong C ++, tôi có thể truyền toyNumberbiến dưới dạng pass bằng tham chiếu để tránh đổ bóng tức là tạo một bản sao của cùng một biến như dưới đây:

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

và đầu ra C ++ sẽ là thế này:

Số đồ chơi trong trò chơi 5  
Số đồ chơi trong khi chơi 6  
Số đồ chơi trong chính 6  

Câu hỏi của tôi là - Mã tương đương trong Java có được đầu ra giống như mã C ++, với điều kiện Java được truyền theo giá trị chứ không phải chuyển qua tham chiếu ?


22
Điều này dường như không phải là một bản sao đối với tôi - câu hỏi được cho là trùng lặp liên quan đến cách java hoạt động và các thuật ngữ có nghĩa là gì, trong khi câu hỏi này hỏi cụ thể làm thế nào để có được một cái gì đó tương tự như hành vi tham chiếu, nhiều thứ C Các lập trình viên / C ++ / D / Ada có thể tự hỏi để hoàn thành công việc thực tế, không quan tâm tại sao java là tất cả các giá trị truyền qua.
DarenW

1
@DarenW Tôi hoàn toàn đồng ý - đã bỏ phiếu để mở lại. Ồ, và bạn có đủ danh tiếng để làm điều tương tự :-)
Duncan Jones

Các cuộc thảo luận xung quanh nguyên thủy là khá sai lệch, vì câu hỏi áp dụng như nhau cho các giá trị tham khảo.
shmosel

"Trong C ++, tôi có thể truyền biến toyNumber dưới dạng tham chiếu để tránh đổ bóng" - đây không phải là bóng vì toyNumberbiến được khai báo trong mainphương thức không nằm trong phạm vi trong playphương thức. Shadowing trong C ++ và Java chỉ xảy ra khi có các phạm vi lồng nhau. Xem en.wikipedia.org/wiki/Variable_shadowing .
Stephen C

Câu trả lời:


171

Bạn có nhiều lựa chọn. Điều có ý nghĩa nhất thực sự phụ thuộc vào những gì bạn đang cố gắng làm.

Lựa chọn 1: biến toyNumber thành một biến thành viên công khai trong một lớp

class MyToy {
  public int toyNumber;
}

sau đó chuyển tham chiếu đến MyToy cho phương thức của bạn.

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

Lựa chọn 2: trả về giá trị thay vì chuyển qua tham chiếu

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
}

Lựa chọn này sẽ yêu cầu một thay đổi nhỏ đối với các Callite trong chính để nó đọc , toyNumber = temp.play(toyNumber);.

Lựa chọn 3: biến nó thành một lớp hoặc biến tĩnh

Nếu hai hàm là các phương thức trên cùng một lớp hoặc thể hiện của lớp, bạn có thể chuyển đổi toyNumber thành một biến thành viên của lớp.

Lựa chọn 4: Tạo một mảng phần tử duy nhất của kiểu int và truyền vào đó

Đây được coi là một hack, nhưng đôi khi được sử dụng để trả về các giá trị từ các yêu cầu lớp nội tuyến.

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
Giải thích rõ ràng và đặc biệt tốt cho việc cung cấp nhiều lựa chọn về cách viết mã cho hiệu ứng mong muốn. Trực tiếp hữu ích cho một cái gì đó tôi đang làm việc ngay bây giờ! Đó là hạt mà câu hỏi này đã được đóng lại.
DarenW

1
Mặc dù lựa chọn 1 của bạn thực hiện những gì bạn đang cố gắng truyền đạt, tôi thực sự phải quay lại và kiểm tra lại nó. Câu hỏi hỏi nếu bạn có thể vượt qua bằng cách tham khảo; Vì vậy, thực sự bạn nên thực hiện các bản in trong cùng một phạm vi mà đồ chơi được truyền cho chức năng tồn tại. Cách bạn có nó, các bản in được vận hành trong cùng phạm vi với mức tăng không thực sự chứng minh điểm của KHÓA HỌC một bản sao cục bộ được in sau khi tăng sẽ có giá trị khác, tuy nhiên điều đó không có nghĩa là tham chiếu được thông qua sẽ. Một lần nữa, ví dụ của bạn không hoạt động, nhưng không chứng minh được quan điểm.
zero298

Không, tôi nghĩ rằng nó đúng theo cách của nó. Mỗi hàm "play ()" trong câu trả lời của tôi ở trên được dùng như một sự thay thế thả xuống cho hàm "play ()" ban đầu, cũng in giá trị trước và sau khi tăng. Câu hỏi ban đầu có println () trong chính chứng minh vượt qua bằng tham chiếu.
laslowh

Lựa chọn 2 yêu cầu giải thích thêm: trang web cuộc gọi chính phải được thay đổi để toyNumber = temp.play(toyNumber);nó hoạt động như mong muốn.
ToolmakerSteve

1
Điều này là vô cùng xấu xí và có một cảm giác khó chịu với nó ... tại sao một cái gì đó rất cơ bản không phải là một phần của ngôn ngữ?
shinzou

30

Java không được gọi bằng tham chiếu, nó chỉ gọi theo giá trị

Nhưng tất cả các biến của loại đối tượng thực sự là con trỏ.

Vì vậy, nếu bạn sử dụng Đối tượng có thể thay đổi, bạn sẽ thấy hành vi bạn muốn

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

Đầu ra của mã này:

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)

Bạn cũng có thể thấy hành vi này trong các thư viện tiêu chuẩn. Ví dụ: Collections.sort (); Bộ sưu tập.shuffle (); Các phương thức này không trả về một danh sách mới nhưng sửa đổi đối tượng đối số của nó.

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

Đầu ra của mã này:

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

2
Đây không phải là một câu trả lời cho câu hỏi. Nó sẽ trả lời câu hỏi, nếu nó gợi ý tạo một mảng nguyên chứa một phần tử, và sau đó sửa đổi phần tử đó trong phương thức play; ví dụ mutableList[0] = mutableList[0] + 1;. Như Ernest Friedman-Hill gợi ý.
ToolmakerSteve

Với người không nguyên thủy. Giá trị của đối tượng không phải là tài liệu tham khảo. Có thể bạn muốn nói: "giá trị tham số" là "tham chiếu". Tài liệu tham khảo được thông qua bởi giá trị.
vlakov

Tôi đang cố gắng làm một cái gì đó tương tự nhưng trong mã của bạn, phương thức private static void play(StringBuilder toyNumber)- làm thế nào để bạn gọi nó nếu ví dụ int static int đang trả về một số nguyên? Bởi vì tôi có một phương thức trả về một số nhưng nếu tôi không gọi nó ở đâu đó thì nó không được sử dụng.
thẳng thắn 17

18

Làm một

class PassMeByRef { public int theValue; }

sau đó chuyển một tham chiếu đến một thể hiện của nó. Lưu ý rằng một phương thức làm thay đổi trạng thái thông qua các đối số của nó là tốt nhất nên tránh, đặc biệt là trong mã song song.


3
Bị hạ bệ bởi tôi. Điều này là sai trên nhiều cấp độ. Java được truyền theo giá trị - luôn luôn. Không có ngoại lệ.
duffymo

14
@duffymo - Tất nhiên bạn có thể downvote như bạn muốn - nhưng bạn đã xem xét những gì OP yêu cầu chưa? Anh ta muốn vượt qua và int bằng cách tham chiếu - và chỉ điều này được thực hiện nếu tôi vượt qua giá trị một tham chiếu đến một thể hiện ở trên.
Ingo

7
@duffymo: Hả? Bạn đang nhầm, hoặc người khác hiểu sai câu hỏi. Đây một trong những cách truyền thống trong Java để thực hiện những gì OP yêu cầu.
ToolmakerSteve

5
@duffymo: Nhận xét của bạn sai ở rất nhiều cấp độ. SO là về việc cung cấp các câu trả lời và nhận xét hữu ích, và bạn chỉ đơn giản là đưa ra một câu trả lời đúng chỉ vì (tôi đoán) nó không phù hợp với phong cách và triết lý lập trình của bạn. Ít nhất bạn có thể đưa ra một lời giải thích tốt hơn, hoặc thậm chí là một câu trả lời tốt hơn cho câu hỏi ban đầu?
paercebal

2
@duffymo đó chính xác là những gì tôi đã nói: vượt qua một ref theo giá trị. Làm thế nào để bạn nghĩ rằng pass by ref được thực hiện trong các ngôn ngữ làm chậm nó?
Ingo

11

Bạn không thể vượt qua các nguyên hàm bằng cách tham chiếu trong Java. Tất cả các biến của loại đối tượng thực sự là con trỏ, tất nhiên, nhưng chúng tôi gọi chúng là "tham chiếu" và chúng cũng luôn được truyền theo giá trị.

Trong một tình huống mà bạn thực sự cần truyền một nguyên thủy bằng tham chiếu, đôi khi mọi người sẽ làm là khai báo tham số là một mảng của kiểu nguyên thủy, và sau đó truyền một mảng phần tử đơn làm đối số. Vì vậy, bạn truyền một tham chiếu int [1] và trong phương thức, bạn có thể thay đổi nội dung của mảng.


1
Không - tất cả các lớp trình bao bọc là bất biến - chúng biểu thị một giá trị cố định không thể thay đổi sau khi đối tượng được tạo.
Ernest Friedman-Hill

1
"Bạn không thể vượt qua các nguyên hàm bằng cách tham chiếu trong Java": OP dường như hiểu điều này, vì chúng tương phản với C ++ (nơi bạn có thể) với Java.
Raedwald

Cần lưu ý rằng "tất cả các lớp trình bao bọc là bất biến" được nói bởi Ernest áp dụng cho Integer, Double, Long, v.v. của JDK, bạn có thể bỏ qua hạn chế này với lớp trình bao bọc tùy chỉnh của mình, giống như những gì Câu trả lời của Ingo đã làm.
dùng3207158

10

Để có giải pháp nhanh, bạn có thể sử dụng AtomicInteger hoặc bất kỳ biến số nguyên tử nào sẽ cho phép bạn thay đổi giá trị bên trong phương thức bằng các phương thức sẵn có. Đây là mã mẫu:

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

    }

}

Đầu ra:

MyNumber before method Call:0

MyNumber After method Call:100

Tôi sử dụng và thích giải pháp này vì nó cung cấp các hành động kết hợp tốt đẹp và dễ dàng tích hợp vào các chương trình đồng thời đã hoặc có thể muốn sử dụng các lớp nguyên tử này trong tương lai.
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]);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.