[Java] Hàm hoán vị trong Java – Swap in Java
Việc thực hiện hàm swap (đổi chỗ – hoán vị) trong java nó hơi khác so với C/C++ hay Pascal,… Muốn thay đổi giá trị của các biến khi ra khỏi hàm thì bạn phải sử dụng đến các đối tượng. Các bạn có thể tham khảo 2 VD dưới đây cho hàm swap sau đó nếu muốn hãy đọc chi tiết ở bên dưới hoặc nếu đọc code thấy khó hiểu thì hãy đọc phần giải thích bên dưới trước rồi hãy quay trở về code.
VD1: Sử dụng đối tượng
package VietSource.net.temp; import java.util.Scanner; class Number { // lop Number int num; public Number(int num) { // ham khoi tao this.num = num; } public int getNum() { // ham tra ve gia tri num return num; } public void setNum(int num) { // ham set gia tri num this.num = num; } } public class MySwap { private void swap(Number a, Number b) { // ham hoan vi int temp = a.getNum(); // gan num cua a cho temp a.setNum(b.getNum()); // gan num cua b cho a b.setNum(temp); // gan temp cho num cua b } public static void main(String[] args) { Number a, b; // hai doi tuong Number a va b MySwap sw = new MySwap(); // doi tuong sw thuoc lop MySwap Scanner scan = new Scanner(System.in); System.out.print("Nhap a = "); a = new Number(scan.nextInt()); // khoi tao a voi num la so nhap vao System.out.print("Nhap b = "); b = new Number(scan.nextInt()); // khoi tao b voi num la so nhap vao scan.close(); System.out.println("Befor swap: a = " + a.getNum() + " b = " + b.getNum()); sw.swap(a, b); // su dung ham swap System.out.println("After swap: a = " + a.getNum() + " b = " + b.getNum()); } }
VD 2 Sử dụng qua mảng (thực chất mảng cũng là đối tượng)
package VietSource.net.temp; import java.util.Scanner; public class SwapArray { private void swapArray(int arr[], int i, int j){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public static void main(String[] args) { SwapArray sw = new SwapArray(); Scanner scan = new Scanner(System.in); int arr[] = new int[2]; System.out.print("Nhap a = "); arr[0] = scan.nextInt(); System.out.print("Nhap b = "); arr[1] = scan.nextInt(); System.out.println("Befor swap: a = " + arr[0] + " b = " + arr[1]); sw.swapArray(arr, 0, 1); System.out.println("After swap: a = " + arr[0] + " b = " + arr[1]); scan.close(); } }
Một trong những điểm khó khăn (và gây tranh cãi) cho những lập trình viên C++ khi lập trình Java là việc pass-by-value hay
pass-by-reference. Ví dụ:
Object a = new Object("Object A"); //kỳ quặc với C++: Object* a = new ... Object b = a; b.setNewName("Object B");
Theo cách hiểu của C++ devs thì rõ ràng Object b thay đổi tên chẳng liên quan gì đến Object a cả. Thực tế theo Java thì khác, Object a sẽ có name là “Object B” chẳng khác gì Object b cả. Để Object b hoàn toàn “độc lập” với Object a thì: Object b = a.clone();
Thế nhưng hàm swap() trong Java lại chẳng hoạt động theo cách pass-by-reference.
private void swap(Type arg1, Type arg2) { Type temp = arg1; arg1 = arg2; arg2 = temp; }
Vậy đấy!!! Java thực sự đã làm như thế nào? Ẩn sau những cái lằng nhằng gây tranh cãi như vậy là gì?
Trước hết cần hiểu rằng pass-by-value là cách truyền bằng cách copy giá trị của tham số vào một “biến” được sử dụng trong nội hàm đó. Do đó khi hàm trả về thì sẽ hủy đi các tham số copied, và các tham số truyền vào thì không bị ảnh hưởng thay đổi gì. Trái ngược với nó là pass-by-reference thì thực sự tạo ra một alias tới các tham số thực sự. Khi trong hàm thay đổi các tham số đó thì nó cũng thay đổi trực tiếp các tham số truyền vào. Việc không phải tạo một bản copy ở trong C++ nhiều khi tiết kiệm được CPU với object lớn, có quá trình copy/khởi tạo phức tạp.
Tuy nhiên thì ở trong Java hàm swap không hoạt động vì nó hoạt động theo cách thức pass-by-value. Sau khi hàm swap trả về thì arg1 và arg2 không đổi chỗ cho nhau. Nhưng kỳ lạ hơn nếu trong thân hàm swap gọi một phương thức của Type làm thay đổi object, ví dụ như: arg1.setValue(“Change”);
Theo cách nghĩ pass-by-value thì sau khi thoát khỏi hàm swap thì arg1 chẳng bị thay đổi gì. Thực tế lại khác, arg1 đã gọi phương thức setValue() do đó khi thoát khỏi hàm thì value của arg1 đã thay đổi. Hãy nhớ lại rằng trong C cũng không có khái niệm reference (&). Thế nhưng C lại có pointer (con trỏ) và nó làm cho C mạnh mẽ uyển chuyển không cần có reference. Java có pointer không? Nhiều người cho rằng: Object thì là pass-by-reference còn các kiểu primitive thì là pass-by-value (In Java, Objects are passed by reference, and primitives are passed by value.) Mệnh đề này chỉ đúng một nửa. Object không phải được pass-by-reference mà là object reference được pass-by-value. Chúng ta sẽ hiểu rõ ở câu trả lời:
Câu trả lời cho tất cả các sự kỳ quặc trên ở Java đó chính là Java đã “làm sạch” cú pháp pointer trong C. Java có pointer nhưng pointer được khai báo và sử dụng một cách rất clean. Và tất nhiên nó tuy không mạnh bằng pointer trong C nhưng nó cũng “fix” tất cả những lỗi tiềm tàng và nguy hiểm (casting, releasing pointer hay pointer arithmetic) của pointer trong C.
Trong Java:
Object a;
giống như trong C/C++
Object* a;
Do đó khi
Object b = a;
có nghĩa trong C/C++
Object* b = a; // b và a đều là pointer
và
b.setName(“Object B”);
tương đương với
b->setName(“Object B”);
Thế nên b.setName(”Object B”) cũng đặt object “trỏ” bởi a có tên là “Object B” vì a và b trỏ cùng vào một object.
Và
private void swap(Object a, Object b) { Object temp = a; a = b; b = temp; }
tương ứng với
void swap(Object* a, Object* b) { Object* temp = a; a = b; b = temp; }
Rõ ràng hàm này trong C không hoạt động trừ phi thay đổi thế này:
void swap(Object* a, Object* b) { Object temp = *a; *a = *b; *b = temp; }
“Con trỏ” a và b được truyền theo kiểu pass-by-value (có thể hiểu rằng Java giống như C, tất cả các tham số được truyền theo kiểu pass-by-value) nên sau khi hàm swap trả về thì a, b không bị thay đổi hay phép gán a = b không có ý nghĩa. Thế nhưng a.setValue(”…”) thì đã thay đổi object trỏ bởi con trỏ.
Tham khảo tại:
http://javadude.com/articles/passbyvalue.htm
http://java.forumvi.net/t23-topic
Đây là vấn đề khá thú vị. Bài viết rất hay, nó sẽ giúp một số bạn hiểu sâu hơn về con trỏ. Phần con trỏ trong java bạn nói là chính xác. Tuy nhiên ở một mức độ sâu hơn nữa, có một số điểm bạn chưa hiểu rõ bản chất vấn đề. Mình có vài ý chắc bạn sẽ phản đối:
– Trong c, c++, obj-c hay java, cái mà các bạn truyền vào hàm, trong mọi trường hợp, tham chiếu hay tham trị thì cũng chỉ là giá trị của biến, hay nói là một bản copy cũng được. “pass-by-reference thì thực sự tạo ra một alias tới các tham số thực sự” Cái này ko đúng đâu bạn ạ. Đó cũng chỉ là một bản copy cái giá trị thôi. Bạn không bao giờ thay đổi được giá trị của đối số truyền vào.
– pass-by-value nghĩa là cái tham số bạn truyền vào chỉ là 1 hằng, bạn ko thể thay đổi bất cứ điều gì.
– pass-by-reference nghĩa là cái tham số bạn truyền vào là một con trỏ, từ con trỏ này bạn có thể thay thay đổi giá trị của các biến mà con trỏ này có thể tham chiếu đến. tuy nhiên bạn ko thể thay đổi giá trị của con trỏ này.
private void swap(Type arg1, Type arg2) {
Type temp = arg1;
arg1 = arg2;
arg2 = temp;
}
Ở ví dụ này, điểm # nhau giữa java và c++ là gì:
– với java: kiểu của tham số thực chất là Type *: bạn có thể thay đổi giá trị của thằng arg1.
– với java: kiểu của tham số là Type: bạn ko thể thay đổi giá trị của thằng arg1.
Còn nếu trong C++ bạn viết :
private void swap(Type * arg1, Type * arg2) {
}
Nếu bạn nghĩ bạn có thể thay đổi giá trị của đối số thì bạn đã ngộ nhận.
Thứ bạn thay đổi là biến kiểu Type, còn đối số bạn truyền vào có kiểu Type * cơ.
Xấu hổ thật, trước h mình chỉ dùng c và java nên ko biết rằng c++ có pass-by-reference. Tối qua mình lần đầu tìm hiểu c++. Và mình nhầm lẫn giữa Reference to obj với pass-by-reference. Sáng nay mình đọc lại và đã hiểu. Phải cố quên đi cái cmt ngu mà tỏ ra nguy hiểm kia mới đc. ^^.
Cảm ơn bạn đã ghé thăm blog. 😀
Nói chung thì phần này nó có chút phức tạp, chắc bạn cũng đã tìm hiểu và sửa lại được quan niệm của mình. Nếu có gì không đúng mong bạn có thể chia sẻ.
Cho mình hỏi tại sao hàm swap bạn ko để trong class Number luôn mà phải tạo thêm class MySwap chi vậy?
Thực ra để ở class nào cũng được bạn ah. Nhưng để thuận tiện thì mình viết vào đó, vì nếu để ở class Number thì sẽ dùng 1 đối tượng của clas Number là a, b hoặc bạn khai báo thêm 1 đối tượng khác để gọi hàm swap.
cảm ơn Bạn, giờ mới biết một object là object reference được pass by value. trước giờ mình cứ nghĩ 1 object như một pointer.
phức tạp quá bạn có thể lấy thêm ví dụ dễ hiểu hơn nữa được không ?
phần con trỏ trong c mình cũng chưa nắm chắc nữa.