Truyền Giá Trị Và Tham Chiếu
(Pass By Value And Pass By Reference)
Trong Java, truyền giá trị (pass by value) và truyền tham chiếu (pass by reference) là hai cách mà các đối tượng và giá trị được truyền vào các phương thức. Tuy nhiên, Java sử dụng một loại cơ chế truyền tham số duy nhất, được gọi là truyền giá trị (pass by value). Java có hai loại dữ liệu khác nhau mà bạn có thể truyền vào các phương thức:
↳ Kiểu dữ liệu nguyên thủy (Primitive types)
↳ Tham chiếu đến đối tượng (Reference types)

Ⅰ. Truyền giá trị của kiểu dữ liệu nguyên thủy (Primitive types) trong Java là gì?
Truyền giá trị của kiểu dữ liệu nguyên thủy (primitive types): Áp dụng cho các kiểu dữ liệu nguyên thủy (primitive types) như int, double, char,... Khi truyền một giá trị nguyên thủy vào một hàm, một bản sao của giá trị đó được tạo và truyền vào hàm. Bất kỳ thay đổi nào đối với bản sao này bên trong hàm sẽ không ảnh hưởng đến giá trị gốc bên ngoài hàm.
Dưới đây là ví dụ về truyền kiểu dữ liệu nguyên thủy không ảnh hưởng đến biến gốc:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
int a = 10;
System.out.println("Giá trị của a trước khi gọi phương thức: " + a);
thayDoiGiaTri(a);
System.out.println("Giá trị của a sau khi gọi phương thức: " + a);
}
public static void thayDoiGiaTri(int a) {
a = 20;
System.out.println("Giá trị của a trong phương thức: " + a);
}
}
Kết quả của chương trình là:
Giá trị của a trong phương thức: 20
Giá trị của a sau khi gọi phương thức: 10
Giải thích ví dụ trên:
Trước khi gọi phương thức thayDoiGiaTri:
↳ Biến a có giá trị là 10.
↳ Giá trị này được in ra màn hình: "Giá trị của a trước khi gọi phương thức: 10".
Trong phương thức thayDoiGiaTri:
↳ Một bản sao của giá trị a (là 10) được truyền vào tham số a.
↳ Trong phương thức, a được gán giá trị mới là 20.
↳ Giá trị của a được in ra màn hình: "Giá trị của a trong phương thức: 20".
Sau khi gọi phương thức thayDoiGiaTri:
↳ Giá trị của a không thay đổi và vẫn là 10.
↳ Giá trị này được in ra màn hình: "Giá trị của a sau khi gọi phương thức: 10".
Kết luận:
Trong ví dụ trên, bạn có thể thấy rằng việc thay đổi giá trị của a trong phương thức thayDoiGiaTri không ảnh hưởng đến giá trị của a trong phương thức main. Điều này chứng minh rằng Java truyền giá trị của biến nguyên thủy vào phương thức, không phải tham chiếu đến biến gốc.
Để làm rõ hơn về cơ chế truyền giá trị của kiểu dữ liệu nguyên thủy chúng ta hãy xem một ví dụ khác:
Ví dụ: Example.java
public class Example {
public static void main(String[] args) {
int a = 5;
int b = 10;
System.out.println("Trước khi hoán đổi: a = " + a + ", b = " + b);
swap(a, b);
System.out.println("Sau khi hoán đổi: a = " + a + ", b = " + b);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
}
Kết quả của chương trình là:
Sau khi hoán đổi: a = 5, b = 10
Phân tích ví dụ trên:
Hoán đổi trong phương thức swap:
↳ Trong phương thức swap, các giá trị của a và b được hoán đổi bằng cách sử dụng một biến tạm thời temp.
↳ Tuy nhiên, sự hoán đổi này chỉ ảnh hưởng đến các bản sao của a và b trong phương thức swap. Vì a và b là kiểu dữ liệu nguyên thủy, chúng được truyền bằng giá trị, không phải tham chiếu.
Kết quả:
↳ Trước khi gọi swap: a = 5 và b = 10.
↳ Trong phương thức swap, a và b được hoán đổi, nhưng chỉ trong phương thức đó.
↳ Sau khi phương thức swap kết thúc, các giá trị gốc của a và b trong phương thức main không thay đổi.
Kết luận:
Ví dụ này cho thấy rằng trong Java, khi làm việc với kiểu dữ liệu nguyên thủy, các tham số được truyền bằng giá trị. Do đó, bất kỳ sự thay đổi nào trong phương thức swap không ảnh hưởng đến giá trị của các biến gốc trong phương thức main. Kết quả là các giá trị của a và b không bị thay đổi sau khi gọi phương thức swap.
Ⅱ. Truyền giá trị của tham chiếu đến đối tượng (Reference types) trong Java là gì?
Truyền giá trị của tham chiếu đến đối tượng (reference types): Áp dụng cho các đối tượng. Khi truyền một đối tượng vào một hàm, thực chất là truyền một tham chiếu (địa chỉ) đến đối tượng đó. Bất kỳ thay đổi nào đối với đối tượng thông qua tham chiếu này bên trong hàm sẽ ảnh hưởng trực tiếp đến đối tượng gốc bên ngoài hàm.
Ví dụ này là một minh họa rõ ràng về cách truyền giá trị của tham chiếu đối tượng trong Java và cách nó ảnh hưởng đến đối tượng gốc.
Ví dụ: Example.java
class Person {
String name;
Person(String name) {
this.name = name;
}
}
public class Example {
public static void main(String[] args) {
Person person = new Person("Dương");
System.out.println("Trước khi thay đổi: person.name = " + person.name);
changeName(person);
System.out.println("Sau khi thay đổi: person.name = " + person.name);
}
public static void changeName(Person person) {
person.name = "Trường";
System.out.println("Giá trị trong phương thức changeName: " + person.name);
}
}
Kết quả của chương trình là:
Giá trị trong phương thức changeName: Trường
Sau khi thay đổi: person.name = Trường
Ví dụ này chứng minh rằng trong Java, khi làm việc với đối tượng, tham số được truyền bằng giá trị của tham chiếu (reference value). Điều này có nghĩa là khi bạn thay đổi thuộc tính của đối tượng thông qua tham số trong phương thức, thay đổi đó sẽ được phản ánh trên đối tượng gốc. Vì vậy, person.name đã được cập nhật từ "Dương" thành "Trường" như mong đợi.
Hãy xem xét một ví dụ về truyền kiểu dữ liệu đối tượng để làm rõ cách Java thực hiện việc truyền giá trị của tham chiếu đối tượng.
Ví dụ: Example.java
class Person {
String name;
Person(String name) {
this.name = name;
}
}
public class Example {
public static void main(String[] args) {
Person person1 = new Person("Dương");
Person person2 = new Person("Trường");
System.out.println("Trước khi hoán đổi: person1.name = " + person1.name + ", person2.name = " + person2.name);
swapNames(person1, person2);
System.out.println("Sau khi hoán đổi: person1.name = " + person1.name + ", person2.name = " + person2.name);
}
public static void swapNames(Person person1, Person person2) {
String temp = person1.name;
person1.name = person2.name;
person2.name = temp;
}
}
Kết quả của chương trình là:
Sau khi hoán đổi: person1.name = Trường, person2.name = Dương
Phân tích ví dụ trên:
Hoán đổi tên trong phương thức swapNames:
↳ Trong phương thức swapNames, bạn hoán đổi giá trị của thuộc tính name giữa hai đối tượng person1 và person2 bằng cách sử dụng biến tạm temp.
↳ person1.name sẽ nhận giá trị từ person2.name, và person2.name sẽ nhận giá trị từ temp.
Kết quả:
↳ Trước khi gọi swapNames, giá trị của person1.name là "Dương" và person2.name là "Trường".
↳ Trong phương thức swapNames, giá trị của person1.name sẽ được thay đổi thành "Trường" và person2.name sẽ được thay đổi thành "Dương".
↳ Sau khi phương thức swapNames kết thúc, kết quả là person1.name sẽ là "Trường" và person2.name sẽ là "Dương".
Kết luận:
Ví dụ này chứng minh rằng khi truyền đối tượng vào phương thức, bạn có thể thay đổi thuộc tính của đối tượng gốc thông qua tham số. Vì person1 và person2 trong phương thức swapNames là các tham chiếu đến các đối tượng gốc, bất kỳ sự thay đổi nào trong phương thức swapNames đều ảnh hưởng đến các đối tượng gốc. Do đó, tên của person1 và person2 được hoán đổi thành công.
Ⅲ. Truyền tham chiếu (Pass by Reference) trong Java: Một khái niệm nhầm lẫn thường gặp.
Java không hỗ trợ trực tiếp cơ chế truyền tham chiếu (pass by reference) như một số ngôn ngữ lập trình khác. Tuy nhiên, nhiều người thường nhầm lẫn về khái niệm này khi làm việc với các đối tượng trong Java.
Tại sao lại có sự nhầm lẫn này?
↳ Sự tương đồng về cú pháp: Trong một số ngôn ngữ lập trình khác như C++ hay Pascal, truyền tham chiếu (pass by reference) là khái niệm quen thuộc, và cú pháp có thể rất giống với Java. Điều này khiến nhiều lập trình viên có kinh nghiệm từ các ngôn ngữ đó dễ dàng hiểu sai cách Java hoạt động.
↳ Truyền tham chiếu đến đối tượng: Khi bạn truyền một đối tượng vào một phương thức trong Java, thực chất bạn đang truyền một bản sao của tham chiếu đến đối tượng đó. Tham chiếu này chỉ ra vị trí của đối tượng trong bộ nhớ.
↳ Thay đổi trạng thái của đối tượng: Nếu bạn thay đổi trạng thái của đối tượng thông qua tham chiếu này bên trong phương thức, thì thay đổi đó sẽ được phản ánh ở đối tượng gốc bên ngoài phương thức, vì cả hai tham chiếu đều trỏ đến cùng một đối tượng trong bộ nhớ.
↳ Không thể thay đổi tham chiếu gốc: Trong Java, nếu bạn cố gắng thay đổi tham chiếu bên trong phương thức để trỏ đến một đối tượng mới, tham chiếu gốc ở bên ngoài phương thức vẫn không thay đổi. Điều này minh chứng rõ ràng rằng Java không phải truyền tham chiếu trực tiếp.
Dưới đây là ví dụ minh họa sự nhầm lẫn về truyền tham chiếu trong Java:
Ví dụ: Example.java
class Box {
int value;
Box(int value) {
this.value = value;
}
}
public class Example {
public static void main(String[] args) {
Box box1 = new Box(5);
Box box2 = new Box(10);
System.out.println("Trước khi hoán đổi: box1.value = " + box1.value + ", box2.value = " + box2.value);
swap(box1, box2);
System.out.println("Sau khi hoán đổi: box1.value = " + box1.value + ", box2.value = " + box2.value);
}
public static void swap(Box box1, Box box2) {
Box temp = box1;
box1 = box2;
box2 = temp;
}
}
Kết quả của chương trình là:
Sau khi hoán đổi: box1.value = 5, box2.value = 10
Giải thích ví dụ trên:
↳ Trước khi hoán đổi: box1.value là 5 và box2.value là 10.
↳ Phương thức swap: Cố gắng hoán đổi hai tham chiếu b1 và b2, nhưng điều này chỉ thay đổi tham chiếu cục bộ bên trong phương thức swap. Tham chiếu gốc box1 và box2 ở ngoài phương thức main vẫn không thay đổi.
↳ Sau khi hoán đổi: Giá trị của box1.value và box2.value vẫn giữ nguyên như ban đầu, chứng minh rằng Java không truyền tham chiếu trực tiếp.
Kết luận:
Sự nhầm lẫn về truyền tham chiếu (pass by reference) trong Java thường bắt nguồn từ việc Java truyền tham trị (pass by value) của tham chiếu đối tượng. Hiểu rõ cơ chế này sẽ giúp bạn tránh những hiểu lầm và lỗi logic trong quá trình phát triển ứng dụng Java.
Để bạn hình dung rõ hơn về sự khác biệt giữa truyền giá trị của kiểu dữ liệu nguyên thủy và truyền giá trị của tham chiếu đến đối tượng trong Java, mình xin phép lập một bảng so sánh chi tiết như sau:
Tính năng | Truyền giá trị của kiểu dữ liệu nguyên thủy | Truyền giá trị của tham chiếu đến đối tượng |
---|---|---|
Bản chất | Truyền một bản sao của giá trị | Truyền một bản sao của tham chiếu (địa chỉ bộ nhớ) đến đối tượng |
Ảnh hưởng đến giá trị gốc | Không ảnh hưởng | Có thể ảnh hưởng nếu thay đổi trạng thái của đối tượng thông qua tham chiếu |
Thay đổi tham chiếu | Không thể thay đổi tham chiếu bên trong hàm | Không thể thay đổi tham chiếu bên trong hàm để nó trỏ đến một đối tượng khác |
Ví dụ | int, double, char, boolean | Các đối tượng (Object), mảng (Array), ... |
Minh họa | int x = 10; changeValue(x); (x vẫn là 10) | Person person = new Person("Dương"); changeName(person); (person.name trở thành "Trường") |
Lưu ý | Giá trị được sao chép và truyền vào hàm | Tham chiếu được sao chép nhưng vẫn trỏ đến cùng một đối tượng |