Passing Mechanism trong Java: Hiểu Rõ Tham Trị (Pass by Value) và Tham Chiếu (Pass by Reference)

Passing Mechanism trong Java: Hiểu Rõ Tham Trị (Pass by Value) và Tham Chiếu (Pass by Reference)

Vấn đề

Khi lập trình Java, có một vấn đề mà nhiều người gặp phải: Cơ chế truyền tham số vào hàm.

Cụ thể là: truyền theo giá trị hay truyền theo tham chiếu? Nghe có vẻ phức tạp, nhưng đừng lo, tôi sẽ giải thích dễ hiểu, kèm ví dụ trực quan luôn nhé.

Truyền Tham Trị (Pass by Value)

Khi nói đến truyền tham trị, có nghĩa là khi bạn truyền một tham số vào hàm, Java sẽ tạo ra một bản sao của giá trị đó và gửi vào hàm. Kết quả là, mọi thay đổi trong hàm chỉ ảnh hưởng đến bản sao, không ảnh hưởng đến giá trị gốc bên ngoài.

Dùng khi nào?

  • Thường thì truyền tham trị áp dụng cho kiểu dữ liệu nguyên thủy (primitive types) trong Java như int, double, boolean, v.v.

Ví dụ:

public class 2TechyExample {
    public static void main(String[] args) {
        int num = 10;
        modifyPrimitive(num);
        System.out.println("Giá trị sau khi gọi hàm: " + num);
    }

    public static void modifyPrimitive(int x) {
        x = 20;  // Thay đổi giá trị của x trong hàm
    }
}

Kết quả:

Giá trị sau khi gọi hàm: 10

Giải thích: Ở đây, khi gọi modifyPrimitive(num), Java tạo ra một bản sao của giá trị num (là 10) và truyền vào hàm. Vậy nên, việc thay đổi trong hàm (gán x = 20) không ảnh hưởng đến giá trị gốc của num.

Truyền Tham Chiếu (Pass by Reference)

Khi bạn truyền tham chiếu vào một hàm, Java sẽ sao chép địa chỉ bộ nhớ của đối tượng và truyền vào phương thức. Điều này có nghĩa là bạn có thể thay đổi nội dung của đối tượng mà tham chiếu đó trỏ đến.

Dùng khi nào?

  • Truyền tham chiếu được dùng với kiểu đối tượng (reference types), như String, Array, hay các đối tượng bạn tự định nghĩa.

Ví dụ:

class Product {
    String name;
    double price;
}

public class 2TechyExample {
    public static void main(String[] args) {
        Product product = new Product();
        product.name = "Laptop";
        product.price = 1000.0;
        modifyProduct(product);
        System.out.println("Tên sản phẩm sau khi gọi hàm: " + product.name);
        System.out.println("Giá sản phẩm sau khi gọi hàm: " + product.price);
    }

    public static void modifyProduct(Product p) {
        p.name = "Smartphone";
        p.price = 500.0;  // Thay đổi thuộc tính của đối tượng p
    }
}

Kết quả:

Tên sản phẩm sau khi gọi hàm: Smartphone
Giá sản phẩm sau khi gọi hàm: 500.0

Giải thích: Khi gọi modifyProduct(product), tham số product là một bản sao của tham chiếu đến đối tượng Product. Vì vậy, việc thay đổi nội dung trong hàm (thay tên sản phẩm và giá) sẽ ảnh hưởng đến đối tượng gốc ngoài phương thức.

Tại sao Java luôn truyền tham trị?

Dù bạn nghĩ Java có thể truyền tham chiếu với đối tượng, nhưng thực chất Java luôn truyền tham trị. Vậy sự khác biệt là gì?

  1. Kiểu Nguyên Thủy (Primitive Types): Java sao chép giá trị của biến và truyền vào hàm. Thay đổi trong hàm không làm thay đổi biến gốc.
  2. Kiểu Đối Tượng (Reference Types): Java sao chép tham chiếu (địa chỉ bộ nhớ) và truyền vào hàm. Bạn có thể thay đổi nội dung của đối tượng, nhưng không thể thay đổi tham chiếu gốc.

Một số ví dụ thú vị

1. Thay đổi tham chiếu trong hàm

class Product {
    String name;
    double price;
}

public class 2TechyExample {
    public static void main(String[] args) {
        Product product = new Product();
        product.name = "Laptop";
        product.price = 1000.0;
        changeReference(product);
        System.out.println("Tên sản phẩm sau khi gọi hàm: " + product.name);
    }

    public static void changeReference(Product p) {
        p = new Product();  // Tạo đối tượng mới và thay đổi tham chiếu
        p.name = "Tablet";  // Thay đổi thuộc tính của đối tượng mới
    }
}

Kết quả:

Tên sản phẩm sau khi gọi hàm: Laptop

Giải thích: Trong hàm changeReference, bạn thay đổi tham chiếu p để trỏ đến một đối tượng mới. Tuy nhiên, tham chiếu product trong phương thức main không thay đổi vì Java sao chép bản sao của tham chiếu.

2. Sử dụng từ khóa final

class Product {
    String name;
}

public class 2TechyExample {
    public static void main(String[] args) {
        final Product product = new Product("Laptop");
        product.setName("Tablet");
        System.out.println("Tên sản phẩm sau khi gọi hàm: " + product.name);
    }
}

Kết quả:

Tên sản phẩm sau khi gọi hàm: Tablet

Giải thích: Bạn đang nghĩ tại sao đã dùng final mà vẫn thay đổi được giá trị? Vậy cần phải hiểu rằng, product đang lưu cái gì và final đang bảo vệ cái gì. Như đã nói, biến tham chiếu sẽ lưu địa chỉ bộ nhớ của đối tượng, không phải nội dung của đối tượng. Khi bạn dùng final, nó chỉ bảo vệ địa chỉ bộ nhớ của đối tượng, không phải nội dung của đối tượng. Vì vậy, bạn vẫn có thể thay đổi nội dung của đối tượng mà tham chiếu đó trỏ tới.

Tổng kết

  • Truyền tham trị: Java sao chép giá trị tham số và truyền vào hàm. Thay đổi trong hàm không làm ảnh hưởng đến giá trị gốc (áp dụng cho kiểu nguyên thủy).
  • Truyền tham chiếu: Java sao chép tham chiếu (địa chỉ bộ nhớ) và truyền vào hàm. Bạn có thể thay đổi nội dung đối tượng mà tham chiếu trỏ tới, nhưng không thể thay đổi tham chiếu gốc (áp dụng cho kiểu đối tượng).
  • Java luôn truyền tham trị: Mọi tham số đều được sao chép giá trị khi truyền vào hàm. Đối với kiểu đối tượng, bạn có thể thay đổi nội dung đối tượng, nhưng không thể thay đổi tham chiếu gốc.
  • Hiểu rõ cơ chế này giúp lập trình viên tránh được những lỗi không đáng có và viết mã hiệu quả hơn.

Hy vọng bài viết này giúp bạn hiểu rõ hơn về cơ chế truyền tham số trong Java. Chúc bạn lập trình vui vẻ và không còn loay hoay với những lỗi truyền tham số nữa! 😊