Truyền đối số trong Java, có thể bạn chưa biết?

Tất nhiên, trong Java, các đối số luôn luôn được truyền bằng giá trị. Hãy cùng xem những gì đang thực sự hoạt động bên trong 😃

Trước khi mô tả các đối số được truyền qua Java, cần xác định cách các biến Java được phân bổ như thế nào trong bộ nhớ. Về cơ bản, chúng ta đang nói về hai loại biến primitive và object. Biến primitive luôn được lưu trữ bên trong stack memory. Còn object được lưu trữ hơi khác hơn một tí. Dữ liệu đối tượng thực được lưu trữ bên trong heap memory và một tham chiếu cho đối tượng được giữ bên trong stack memory, cái mà trỏ đến đối tượng thực.

1. By Value vs by Reference

  • By Value: Khi các đối số được truyền bằng giá trị cho một method, nó có nghĩa là một bản sao của biến ban đầu đang được truyền đến method và đó không phải là bản gốc, do đó bất kỳ thay đổi nào được áp dụng bên trong method thực ra là nó đang ảnh hưởng đến bản sao.
  • By Reference: Khi các đối số được truyền bằng tham chiếu, nó có nghĩa là một tham chiếu hoặc một con trỏ đến biến ban đầu đang được truyền đến method chứ không phải là dữ liệu biến ban đầu.

2. Đối số được truyền trong Java là như thế nào?

Trong Java, các đối số luôn luôn được truyền bằng giá trị bất kể loại biến ban đầu. Mỗi lần một method được gọi, một bản sao cho mỗi đối số được tạo ra trong stack memory và bản sao này được truyền đến method.

  • Nếu kiểu biến ban đầu là primitive, đơn giản thôi, một bản sao của biến được tạo ra bên trong stack memory và sau đó truyền vào method.
  • Nếu kiểu ban đầu không phải là primitive, thì một tham chiếu mới hoặc con trỏ được tạo ra bên trong stack memory, cái mà trỏ tới đối tượng thật và tham chiếu mới sau đó được truyền đến method (ở giai đoạn này, hai tham chiếu đều trỏ đến cùng một đối tượng).

3. Tìm hiểu về vài concern

Trong một vài ví dụ dưới đây, chúng ta cố gắng để xác nhận rằng "Java luôn luôn là truyền giá trị" , bằng cách truyền đối số với các type cơ bản như : primitive, wrappers, collections, business objects; đồng thời kiểm tra xem chúng có được sửa đổi sau khi gọi method.

Truyền đối số Primitive

Code

public static void main(String[] args) {
    int x = 69;
    int y = 96;
    System.out.print("Values of x & y before primitive modification: ");
    System.out.println(" x = " + x + " ; y = " + y );
    modifyPrimitiveTypes(x,y);
    System.out.print("Values of x & y after primitive modification: ");
    System.out.println(" x = " + x + " ; y = " + y );
}
private static void modifyPrimitiveTypes(int x, int y)
{
    x = 11;
    y = 111;
}

Output

Values of x & y before primitive modification:  x = 69 ; y = 96
Values of x & y after primitive modification:  x = 69 ; y = 96

Giải thích: Chúng ta có hai biến, x và y, thuộc kiểu primitive và do đó chúng được lưu trữ bên trong stack memory. Khi gọi modifyPrimitiveTypes (), hai bản sao được tạo ra bên trong bộ nhớ ngăn xếp (giả sử w và z) và sau đó chúng được truyền đến medthod. Do đó, các biến ban đầu không được gửi đến method và bất kỳ sửa đổi nào trong luồng xử lý của method chỉ ảnh hưởng đến các bản sao.

Truyền đối số Wrapper

Code

public static void main(String[] args) {
    Integer obj1 = new Integer(69);
    Integer obj2 = new Integer(96);
    System.out.print("Values of obj1 & obj2 before wrapper modification: ");
    System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue());
    modifyWrappers(obj1, obj2);
    System.out.print("Values of obj1 & obj2 after wrapper modification: ");
    System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue());
}
private static void modifyWrappers(Integer x, Integer y)
{
    x = new Integer(11);
    y = new Integer(111);
}

Output:

Values of obj1 & obj2 before wrapper modification: obj1 = 69 ; obj2 = 96
Values of obj1 & obj2 after wrapper modification: obj1 = 69 ; obj2 = 96

Giải thích: Wrappers được lưu trữ bên trong heap memory với một tham chiếu bên trong stack memory. Nên khi gọi hàm modifyWrappers (), một bản sao cho mỗi tham chiếu được tạo ra bên trong stack memory, và các bản sao được truyền vào medthod. Bất kỳ sự thay đổi nào đối với tham chiếu bên trong method thực sự thay đổi tham chiếu của các bản sao chứ không phải các tham chiếu ban đầu.

Truyền đối số Collection

Code:


public static void main(String[] args) {
    List<Integer> listNums = new ArrayList<Integer>();
    listNums.add(69);
    System.out.println("Size of list before List modification = " + listNums.size());
    modifyList(listNums);
    System.out.println("Size of list after List modification = " + listNums.size());
}
private static void modifyList(List<Integer> listParam)
{
    listParam.add(96);
}

Output:

Size of list before List modification = 1
Size of list after List modification = 2

Giải thích: Khi định nghĩa một ArrayList hay bất cứ collection nào trong Java, một tham chiếu được tạo ra bên trong stack để trỏ tới nhiều đối tượng bên trong heap memory. Khi gọi method modifyList(), một bản sao của đối tượng tham chiếu được tạo ra và truyền cho method. Dữ liệu đối tượng thực được tham chiếu bởi hai đối tượng tham chiếu, và bất kỳ thay đổi nào được thực hiện bởi một tham chiếu được phản ánh trong các tham chiếu còn lại.

Bên trong method, khi chúng ta gọi là listParam.add (2), là chúng ta đang cố gắng tạo một đối tượng Integer mới trong heap memory và liên kết nó với danh sách các đối tượng hiện có. Vì vậy, tham chiếu của list ban đầu có thể được sửa đổi, vì cả hai tham chiếu đang trỏ đến cùng một đối tượng trong bộ nhớ.

Truyền đối số là Business Object

Code:

public static void main(String[] args) {
    User user = new User();
    System.out.println("Value of userName before User modification = " + user.getUserName());
    modifyUser(user);
    System.out.println("Value of userName after User modification = " + user.getUserName());
}
private static void modifyUser(User user)
{
    user.setUserName("Shanks");
}

Output

Value of userName before User modification = null
Value of userName before User modification = Shanks

Giải thích Đối tượng User được tạo ra bên trong heap memory, và một tham chiếu cho nó được định nghĩa bên trong stack. Khi gọi modifyUser() , một bản sao của tham chiếu được tạo ra bên trong stack và truyền cho method. Bất kỳ sửa đổi đối với các thuộc tính của đối tượng bên trong method đều được phản ánh sang tham chiếu ban đầu.

Kết luận

Trong Java, các đối số luôn luôn được truyền bằng giá trị. Bản sao sẽ là một tham chiếu hoặc một biến phụ thuộc vào loại biến ban đầu. Từ bây giờ, bạn có thể sử dụng các mẹo dưới đây để hiểu cách sửa đổi các đối số bên trong các method của mình để nó ảnh hưởng đến biến ban đầu:

  1. Modify giá trị của một đối số primitive sẽ không bao giờ ảnh hưởng đến biến ban đầu.
  2. Thay đổi tham chiếu đến một đối số đối tượng bên trong method sẽ không bao giờ ảnh hưởng đến tham chiếu ban đầu. Tuy nhiên, nó tạo ra một đối tượng hoàn toàn mới trong heap memory.
  3. Modify thuộc tính của một đối số đối tượng bên trong một method sẽ được phản ánh ra đối tượng ban đầu.
  4. Modify collections and maps bên trong method sẽ được phản ánh ra đối tượng ban đầu của chúng.

All Rights Reserved