Phần này chúng ta sẽ làm rõ ba vấn đề sau: truyền tham trị là gì(pass by value), truyền tham chiếu(pass by reference) là gì, trong java sử dụng kiểu nào ?

1 – Truyền tham trị (Pass by value)

Truyền tham trị là việc ta clone ra một bản sao (tạo ra một giá trị mới bằng cách copy giá trị gốc) và thao tác với giá trị của bản sao đó và không làm thay đổi giá trị của bản gốc. Hiểu đơn giản là khi truyền dữ liệu vào hàm và tương tác thì dữ liệu chỉ thay đổi trong hàm đó, mà không làm thay đổi giá trị gốc ban đầu ngoài hàm.

package com.cafeincode.java;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Example {
    public static void main(String[] args) {
        int counter = 7;
        log.info("Before increase:{}", counter);
        increaseValue(counter);
        log.info("After increase:{}", counter);
    }

    private static void increaseValue(int counter) {
        counter += 1;
        log.info("Increase counter:{}", counter);
    }
}

chương trình thực hiện như sau:

  • Tại dòng 7 trong hàm main(), ta khởi tạo biến counter có giá trị là 7, trước khi gọi hàm increaseValue() thì giá trị của nó giữ nguyên là 7
  • Tại dòng 9, khi gọi hàm increaseValue() ta truyền biến counter có giá trị là 7 vào hàm, tuy nhiên dữ liệu của biến này đã được clone ra thành một biến mới, tại đây ta thực hiện cộng thêm 1 đơn vị vào biến counter mới, giá trị của biến counter mới lúc này bằng 8 là giá trị cục bộ trong hàm increaseValue().
  • Tại dòng 10 trong hàm main(), ta thực hiện ghi ra giá trị biến counter, lúc này biến counter vẫn giữ nguyên giá trị của nó ban đầu do hàm increaseValue() không tác động được vào giá trị của biến gốc mà chỉ tác động vào biến mà nó clone ra.

2 – Truyền tham chiếu (Pass by reference)

Truyền tham chiếu là kiểu khi truyền giá trị vào hàm và thực hiện tương tác, sửa đổi thì dữ liệu ở trong hay ngoài hàm đều thay đổi, tuy nhiên trong java không có truyền tham chiếu, mà chỉ có truyền tham trị.

Một số website hướng dẫn hiện nay đang hướng dẫn theo kiểu : truyền biến nguyên thủy là tham trị, truyền biến đối tượng là tham chiếu, điều này là sai hoàn toàn.

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CounterObj {
    private String value;
}

Ví dụ 1: truyền đối tượng vào hàm, và khởi tạo một instance mới

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Example {

    public static void main(String[] args) {
        CounterObj counterObj = new CounterObj("JJJJJ");
        log.info("Step 1: Address: {} Value: {}", counterObj, counterObj.getValue());
        changeValueObj(counterObj);
        log.info("Step 3: Address: {} Value: {}", counterObj, counterObj.getValue());
    }

    private static void changeValueObj(CounterObj counterObj) {
        counterObj = new CounterObj("FFFFF");
        log.info("Step 2: Address: {} Value: {}", counterObj, counterObj.getValue());
    }
}
Kết quả việc thay đổi giá trị đối tượng bằng cách khởi tạo đối tượng mới

Luồng thực hiện đoạn code trên như sau:

  • Tại dòng 7, ta thực hiện khởi tạo đối tượng CounterObj, một đối tượng được lưu trong bộ nhớ Heap (giả sử nó có địa chỉ là 6996db8, giá trị của nó lúc này là JJJJJ), đồng thời biến counterObj được lưu trong một khối ô nhớ của hàm main() trong Stack.
  • Tại dòng 9, hàm changeValueObj() được gọi, lúc này một biến counterObj được lưu trong khối ô nhớ của hàm changeValueObj() của Stack.
  • Tại dòng 14, một đối tượng counterObj mới được tạo, được lưu trong Heap (có địa chỉ là 6504e3b2, giá trị là FFFFF), đồng thời biến counterObj được lưu trong khối ô nhớ của hàm changeValueObj() trong Stack.
  • Tại dòng 10, ta in ra địa chỉ và giá trị của đối tượng ban đầu, thì thấy nó không bị thay đổi giá trị, giá trị truyền vào hàm không bị tác động.

Ví dụ 2: truyền đối tượng vào hàm, và thay đổi giá trị trong hàm bằng setter

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Example {

    public static void main(String[] args) {
        CounterObj counterObj = new CounterObj("JJJJJ");
        log.info("Step 1: Address: {} Value: {}", counterObj, counterObj.getValue());
        changeValueObj(counterObj);
        log.info("Step 3: Address: {} Value: {}", counterObj, counterObj.getValue());
    }

    private static void changeValueObj(CounterObj counterObj) {
        counterObj.setValue("FFFFF");
        log.info("Step 2: Address: {} Value: {}", counterObj, counterObj.getValue());
    }
}
Kết quả khi thực hiện thay đổi giá trị của đối tượng bằng setter

Luồng thực hiện đoạn code trên như sau:

  • Tại dòng 7, ta thực hiện khởi tạo đối tượng CounterObj, một đối tượng được lưu trong bộ nhớ Heap (giả sử nó có địa chỉ là 6996db8, giá trị của nó lúc này là JJJJJ), đồng thời biến counterObj được lưu trong một khối ô nhớ của hàm main() trong Stack.
  • Tại dòng 9, hàm changeValueObj() được gọi, lúc này một biến counterObj được lưu trong khối ô nhớ của hàm changeValueObj() của Stack.
  • Tại dòng 13, ta truyền biến counterObj vào hàm, lúc này biến counterObj được clone ra thành một bản sao (có địa chỉ là 6996db8, giá trị của nó là JJJJJ)
  • Tại dòng 14, ta thực hiện thay đổi giá trị của thuộc tính value, lúc này biến counterObj tham chiếu đến đối tượng có địa chỉ 6996db8 trong Heap, nên giá trị của biến counterObj lúc này đã thay đổi ở cả trong hàm và ngoài hàm, tuy nhiên đây vẫn truyền kiểu tham trị, không phải truyền tham chiếu.

3 – Trong java sử dụng truyền tham chiếu hay truyền tham trị?

Từ các lập luận ở trên thì chúng ta rút ra được rằng, trong java chỉ hỗ trợ truyền tham trị, không hỗ trợ truyền tham chiếu, không phải truyền kiểu nguyên thủy là tham trị còn truyền kiểu đối tượng là tham chiếu đâu nhé.

Xem thêm các bài viết liên quan bên dưới: