CodeGym/Blog Java/Ngẫu nhiên/Các phương thức trong Java

Các phương thức trong Java

Xuất bản trong nhóm
Xin chào lần nữa! Trong bài học trước, chúng ta đã làm quen với các lớp và hàm tạo, đồng thời học cách tạo lớp của riêng mình. Hôm nay chúng ta sẽ làm quen tốt hơn với các Phương thức Java, một phần thiết yếu của các lớp. Phương thức trong Java là một tập hợp các lệnh cho phép bạn thực hiện một thao tác cụ thể trong một chương trình. Nói cách khác, một phương thức là một chức năng; một cái gì đó mà lớp học của bạn có thể làm. Trong các ngôn ngữ lập trình khác, các phương thức thường được gọi là "hàm", nhưng trong Java, từ "phương thức" phổ biến hơn. :) Nếu bạn còn nhớ, trong bài trước chúng ta đã tạo các phương thức đơn giản cho lớp Cat , để mèo của chúng ta có thể kêu meo meo và nhảy:
public class Cat {

    String name;
    int age;

    public void sayMeow() {
        System.out.println("Meow!");
    }

    public void jump() {
        System.out.println("Pounce!");
    }

    public static void main(String[] args) {
        Cat smudge = new Cat();
        smudge.age = 3;
        smudge.name = "Smudge";

        smudge.sayMeow();
        smudge.jump();
    }
}
sayMeow()jump() là các phương thức của lớp chúng ta. Và việc chạy các phương thức này dẫn đến kết quả đầu ra của bảng điều khiển sau:
Meow!
Pounce!
Các phương pháp của chúng tôi khá đơn giản: chúng chỉ xuất văn bản ra bàn điều khiển. Nhưng trong Java, các phương thức có một nhiệm vụ quan trọng: chúng thực hiện các hành động trên dữ liệu của một đối tượng. Chúng thay đổi dữ liệu của đối tượng, biến đổi nó, hiển thị nó và làm những việc khác với nó. Các phương thức hiện tại của chúng tôi không làm gì với dữ liệu của đối tượng Cat . Hãy xem xét một ví dụ minh họa hơn:
public class Truck {

    int length;
    int width;
    int height;
    int weight;

    public int getVolume() {
        int volume = length * width * height;
        return volume;
    }
}
Ví dụ, ở đây chúng ta có một lớp đại diện cho Xe tải . Xe bán tải có chiều dài, chiều rộng, chiều cao và trọng lượng (mà chúng ta sẽ cần sau). Trong phương thức getVolume() , chúng ta thực hiện các phép tính, chuyển đổi dữ liệu của đối tượng thành một số biểu thị âm lượng của nó (chúng ta nhân chiều dài, chiều rộng và chiều cao). Con số này sẽ là kết quả của phương pháp. Lưu ý rằng phần khai báo của phương thức được viết dưới dạng public int getVolume . Điều đó có nghĩa là phương thức này phải trả về một int . Chúng ta đã tính toán giá trị trả về của phương thức và bây giờ chúng ta phải trả lại giá trị đó cho chương trình đã gọi phương thức của chúng ta. Để trả về kết quả của một phương thức trong Java, chúng ta sử dụng từ khóa return. khối lượng trả về;

Tham số phương thức Java

Chúng ta có thể chuyển các giá trị được gọi là "đối số" cho một phương thức khi gọi nó. Khai báo của một phương thức bao gồm một danh sách các biến cho chúng ta biết loại và thứ tự của các biến mà phương thức có thể chấp nhận. Danh sách này được gọi là "tham số phương pháp". Phương thức getVolume() của lớp Xe tải của chúng ta hiện không xác định bất kỳ tham số nào, vì vậy, hãy thử mở rộng ví dụ về xe tải của chúng ta. Tạo một lớp mới gọi là BridgeOfficer . Đây là một sĩ quan cảnh sát đang làm nhiệm vụ tại một cây cầu, người kiểm tra tất cả các xe tải đi qua để xem liệu chúng có vượt quá trọng lượng cho phép hay không.
public class BridgeOfficer {

    int maxWeight;

    public BridgeOfficer(int normalWeight) {
        this.maxWeight = normalWeight;
    }

    public boolean checkTruck(Truck truck) {
        if (truck.weight > maxWeight) {
            return false;
        } else {
            return true;
        }
    }
}
Phương thức checkTruck chấp nhận một đối số, một đối tượng Xe tải và xác định xem cảnh sát có cho phép xe tải trên cầu hay không. Bên trong phương thức, logic đủ đơn giản: nếu trọng lượng của xe tải vượt quá mức tối đa cho phép, thì phương thức trả về false . Nó sẽ phải tìm một con đường khác :( Nếu trọng số nhỏ hơn hoặc bằng mức tối đa, nó có thể vượt qua và phương thức trả về true. Nếu bạn chưa hiểu đầy đủ các cụm từ "trả về" hoặc "phương thức trả về một giá trị", hãy tạm ngừng lập trình và xem xét chúng bằng một ví dụ đơn giản từ cuộc sống thực. :) Giả sử bạn bị ốm và phải nghỉ làm ở nhà vài ngày. Bạn đến phòng kế toán với ghi chú của bác sĩ, vì nghỉ ốm phải được trả lương. Nếu chúng ta so sánh tình huống này với các phương thức, thì kế toán có paySickLeave()phương pháp. Bạn chuyển ghi chú của bác sĩ làm đối số cho phương pháp này (không có nó, phương pháp sẽ không hoạt động và bạn sẽ không được trả tiền!). Sau đó, các tính toán cần thiết được thực hiện bên trong phương pháp bằng cách sử dụng ghi chú của bạn (kế toán sử dụng nó để tính toán số tiền công ty phải trả cho bạn) và kết quả công việc của bạn (một số tiền) được trả lại cho bạn. Chương trình của chúng tôi hoạt động theo cách tương tự. Nó gọi một phương thức, truyền dữ liệu cho nó và cuối cùng nhận được kết quả. Đây là phương thức main() của chương trình BridgeOfficer của chúng ta :
public static void main(String[] args) {
    Truck first = new Truck();
    first.weight = 10000;
    Truck second = new Truck();
    second.weight = 20000;

    BridgeOfficer officer = new BridgeOfficer(15000);
    System.out.println("Truck 1! Can I go, officer?");
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println("Truck 2! And can I?");
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canSecondTruckGo);
}
Ta tạo hai xe tải có tải trọng 10.000 và 20.000. Và cây cầu nơi sĩ quan làm việc có trọng lượng tối đa là 15.000. Chương trình gọi phương thức officer.checkTruck(first) . Phương thức này tính toán mọi thứ và sau đó trả về true , mà chương trình sau đó sẽ lưu vào biến boolean canFirstTruckGo . Bây giờ bạn có thể làm bất cứ điều gì bạn muốn với số tiền đó (giống như bạn có thể làm với số tiền mà kế toán đưa cho bạn). Vào cuối ngày, mã
boolean canFirstTruckGo = officer.checkTruck(first);
sôi xuống
boolean canFirstTruckGo =  true;
Đây là một điểm rất quan trọng: câu lệnh return không chỉ trả về giá trị trả về của phương thức, nó còn ngăn không cho phương thức chạy! Bất kỳ mã nào xuất hiện sau câu lệnh return sẽ không được thực thi!
public boolean checkTruck(Truck truck) {

    if (truck.weight > maxWeight) {
        return false;
        System.out.println("Turn around, you're overweight!");
    } else {
        return true;
        System.out.println("Everything looks good, go ahead!");
    }
}
Nhận xét của viên chức sẽ không được hiển thị, vì phương thức đã trả về kết quả và kết thúc! Chương trình quay trở lại nơi phương thức được gọi. Bạn không cần phải để ý điều này: trình biên dịch Java đủ thông minh để tạo ra lỗi khi bạn cố gắng viết mã sau câu lệnh return .

Avengers: Cuộc chiến thông số

Có những tình huống khi chúng ta muốn có nhiều cách gọi một phương thức. Tại sao không tạo ra trí tuệ nhân tạo của riêng chúng ta? Amazon có Alexa, Apple có Siri, vậy tại sao chúng ta không có? :) Trong phim Iron Man, Tony Stark tạo ra trí thông minh nhân tạo đáng kinh ngạc của riêng mình, Jarvis. Hãy vinh danh nhân vật tuyệt vời đó và đặt tên cho AI của chúng ta để vinh danh anh ấy. :) Điều đầu tiên chúng ta cần làm là dạy Jarvis chào những người bước vào phòng (sẽ thật kỳ lạ nếu một trí tuệ tuyệt vời như vậy lại trở nên bất lịch sự).
public class Jarvis {

    public void sayHi(String name) {
        System.out.println("Good evening, " + name + ". How are you?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
    }
}
Đầu ra bảng điều khiển:
Good evening, Tony Stark. How are you?
Rất tốt! Jarvis hiện đã có thể đón khách. Tất nhiên, thường thì đó sẽ là chủ nhân của anh ta, Tony Stark. Nhưng nếu anh ấy không đến một mình thì sao! Phương thức sayHi() của chúng ta chỉ chấp nhận một đối số. Và do đó, nó chỉ có thể chào một người bước vào phòng, và sẽ phớt lờ người kia. Không lịch sự lắm, bạn có đồng ý không? :/

Quá tải phương thức Java

Trong trường hợp này, chúng ta có thể giải quyết vấn đề bằng cách viết 2 phương thức có cùng tên nhưng khác tham số:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ". How are you?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + " and " + secondGuest + ". How are you?");
    }
}
Điều này được gọi là nạp chồng phương thức. Quá tải phương thức cho phép chương trình của chúng tôi linh hoạt hơn và phù hợp với nhiều cách làm việc khác nhau. Hãy xem lại cách nó hoạt động:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ". How are you?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + " and " + secondGuest + ". How are you?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
        jarvis.sayHi("Tony Stark", "Captain America");
    }
}
Đầu ra bảng điều khiển:
Good evening, Tony Stark. How are you?
Good evening, Tony Stark and Captain America. How are you?
Tuyệt vời, cả hai phiên bản đều hoạt động. :) Nhưng chúng tôi đã không giải quyết được vấn đề! Nếu có ba khách thì sao? Tất nhiên, chúng ta có thể nạp chồng phương thức sayHi() một lần nữa để nó chấp nhận ba tên khách. Nhưng có thể có 4 hoặc 5. Cho đến vô tận. Không có cách nào tốt hơn để dạy Jarvis xử lý bất kỳ số lượng tên nào mà không làm quá tải phương thức sayHi() hàng triệu lần sao? :/ Tất nhiên là có! Nếu không, bạn có nghĩ Java sẽ là ngôn ngữ lập trình phổ biến nhất trên thế giới không? ;)
public void sayHi(String...names) {

    for (String name: names) {
        System.out.println("Good evening, " + name + ". How are you?");
    }
}
Khi ( String... names ) được sử dụng làm tham số, nó chỉ ra rằng một tập hợp các Chuỗi sẽ được truyền cho phương thức. Chúng tôi không phải chỉ định trước sẽ có bao nhiêu, vì vậy bây giờ phương pháp của chúng tôi linh hoạt hơn nhiều:
public class Jarvis {

    public void sayHi(String...names) {
        for (String name: names) {
            System.out.println("Good evening, " + name + ". How are you?");
        }
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark", "Captain America", "Black Widow", "Hulk");
    }
}
Đầu ra bảng điều khiển:
Good evening, Tony Stark. How are you?
Good evening, Captain America. How are you?
Good evening, Black Widow. How are you?
Good evening, Hulk. How are you?
Một số mã ở đây sẽ không quen thuộc với bạn, nhưng đừng lo lắng về nó. Cốt lõi của nó rất đơn giản: phương pháp này lần lượt lấy từng tên và chào đón từng vị khách! Ngoài ra, nó sẽ hoạt động với bất kỳ số lượng chuỗi đã truyền nào! Hai, mười, thậm chí một nghìn—phương pháp này sẽ hoạt động hiệu quả với bất kỳ số lượng khách nào. Bạn có nghĩ rằng thuận tiện hơn là nạp chồng phương thức cho tất cả các khả năng không? :) Đây là một điểm quan trọng khác: thứ tự của các đối số rất quan trọng! Giả sử phương thức của chúng ta nhận một Chuỗi và một số:
public class Person {

    public static void sayYourAge(String greeting, int age) {
        System.out.println(greeting + " " + age);
    }

    public static void main(String[] args) {
        sayYourAge("My age is ", 33);
        sayYourAge(33, "My age is "); // Error!
    }
}
Nếu phương thức sayYourAge của lớp Person lấy một chuỗi và một số làm đầu vào, thì chương trình phải chuyển chúng theo thứ tự cụ thể đó! Nếu chúng ta chuyển chúng theo một thứ tự khác, trình biên dịch sẽ báo lỗi và người đó sẽ không thể nói tuổi của mình. Nhân tiện, hàm tạo mà chúng ta đã đề cập trong bài trước cũng là phương thức! Bạn cũng có thể quá tải chúng (tức là tạo một số hàm tạo với các bộ tham số khác nhau) và thứ tự của các đối số được truyền về cơ bản cũng rất quan trọng đối với chúng. Chúng là những phương pháp thực sự! :)

Một lần nữa liên quan đến các thông số

Vâng, xin lỗi, chúng tôi chưa hoàn thành với họ. :) Chủ đề mà chúng ta sẽ nghiên cứu bây giờ rất quan trọng. Có 90% khả năng bạn sẽ được hỏi về điều này trong mọi cuộc phỏng vấn trong tương lai! Hãy nói về việc truyền đối số cho các phương thức. Hãy xem xét một ví dụ đơn giản:
public class TimeMachine {

    public void goToFuture(int currentYear) {
        currentYear = currentYear+10;
    }

    public void goToPast(int currentYear) {
        currentYear = currentYear-10;
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        int currentYear = 2018;

        System.out.println("What year is it?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("How about now?");
        System.out.println(currentYear);
    }
}
Cỗ máy thời gian có hai phương pháp. Cả hai đều lấy số đại diện cho năm hiện tại làm đầu vào và tăng hoặc giảm giá trị của nó (tùy thuộc vào việc chúng ta muốn đi về quá khứ hay tương lai). Tuy nhiên, như bạn có thể thấy từ đầu ra của bàn điều khiển, phương pháp này không hoạt động! Đầu ra bảng điều khiển:
What year is it?
2018
How about now?
2018
Chúng ta đã chuyển biến currentYear sang phương thức goToPast() , nhưng giá trị của nó không thay đổi. Chúng tôi đã ở vào năm 2018 và chúng tôi đã ở đây. Nhưng tại sao? :/ Bởi vì các nguyên hàm trong Java được truyền cho các phương thức theo giá trị. Điều đó nghĩa là gì? Khi chúng ta gọi phương thức goToPast() và truyền biến int currentYear (=2018) cho nó, phương thức này không nhận chính biến currentYear mà là một bản sao của nó. Tất nhiên, giá trị của bản sao này cũng là 2018, nhưng mọi thay đổi đối với bản sao không ảnh hưởng đến biến currentYear ban đầu của chúng tôi theo bất kỳ cách nào! Hãy làm cho mã của chúng ta rõ ràng hơn và xem điều gì sẽ xảy ra với currentYear:
public class TimeMachine {

    public void goToFuture(int currentYear) {
        currentYear = currentYear+10;
    }

    public void goToPast(int currentYear) {
        System.out.println("The goToPast method has started running!");
        System.out.println("currentYear inside the goToPast method (at the beginning) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("currentYear inside the goToPast method (at the end) = " + currentYear);
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        int currentYear = 2018;

        System.out.println("What was the year when the program started?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("And what year is it now?");
        System.out.println(currentYear);
    }
}
Đầu ra bảng điều khiển:
What was the year when the program started?
2018
The goToPast method has started running!
currentYear inside the goToPast method (at the beginning) = 2018
currentYear inside the goToPast method (at the end) = 2008
And what year is it now?
2018
Điều này cho thấy rõ ràng rằng biến được truyền cho phương thức goToPast() chỉ là một bản sao của currentYear . Và việc thay đổi bản sao không ảnh hưởng đến giá trị "gốc". "Chuyển qua tham chiếu" có nghĩa hoàn toàn ngược lại. Hãy thực hành trên mèo! Ý tôi là, hãy xem việc chuyển qua tham chiếu trông như thế nào khi sử dụng một ví dụ về mèo. :)
public class Cat {

    int age;

    public Cat(int age) {
        this.age = age;
    }
}
Giờ đây, với sự trợ giúp của cỗ máy thời gian, chúng tôi sẽ gửi Smudge , chú mèo du hành thời gian đầu tiên trên thế giới, về quá khứ và tương lai! Hãy sửa đổi lớp TimeMachine để nó hoạt động với các đối tượng Cat ;
public class TimeMachine {

    public void goToFuture(Cat cat) {
        cat.age += 10;
    }

    public void goToPast(Cat cat) {
        cat.age -= 10;
    }
}
Giờ đây, các phương thức không chỉ thay đổi số đã truyền. Thay vào đó, họ thay đổi trường tuổi Cat cụ thể đó . Bạn sẽ nhớ rằng điều này không hiệu quả đối với chúng tôi với số nguyên thủy, bởi vì số ban đầu không thay đổi. Hãy xem điều gì sẽ xảy ra!
public static void main(String[] args) {

    TimeMachine timeMachine = new TimeMachine();
    Cat smudge = new Cat(5);

    System.out.println("How old was Smudge when the program started?");
    System.out.println(smudge.age);

    timeMachine.goToFuture(smudge);
    System.out.println("How about now?");
    System.out.println(smudge.age);

    System.out.println("Holy smokes! Smudge has aged 10 years! Back up quickly!");
    timeMachine.goToPast(smudge);
    System.out.println("Did it work? Have we returned the cat to its original age?");
    System.out.println(smudge.age);
}
Đầu ra bảng điều khiển:
How old was Smudge when the program started running?
5
How about now?
15
Holy smokes! Smudge has aged 10 years! Back up quickly!
Did it work? Have we returned the cat to its original age?
5
Ồ! Bây giờ phương pháp đã làm khác đi: con mèo của chúng tôi già đi đáng kể, nhưng sau đó nó lại trẻ ra! :) Hãy thử tìm hiểu tại sao. Không giống như ví dụ với các nguyên hàm, khi các đối tượng được truyền cho một phương thức, chúng được truyền theo tham chiếu. Một tham chiếu đến đối tượng smudge ban đầu được truyền cho phương thức changeAge() . Vì vậy, khi chúng ta thay đổi smudge.age bên trong phương thức, chúng ta đang tham chiếu đến cùng một vùng bộ nhớ nơi đối tượng của chúng ta được lưu trữ. Đó là một tham chiếu đến cùng một Vết nhòe mà chúng tôi đã tạo ban đầu. Điều này được gọi là "đi qua tham chiếu"! Tuy nhiên, không phải mọi thứ có tham chiếu đều dễ dàng như vậy. :) Hãy thử thay đổi ví dụ của chúng tôi:
public class TimeMachine {

    public void goToFuture(Cat cat) {
        cat = new Cat(cat.age);
        cat.age += 10;
    }

    public void goToPast(Cat cat) {
        cat = new Cat(cat.age);
        cat.age -= 10;
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        Cat smudge = new Cat(5);

        System.out.println("How old was Smudge when the program started?");
        System.out.println(smudge.age);

        timeMachine.goToFuture(smudge);
        System.out.println ("Smudge went to the future! Has his age changed?");
        System.out.println(smudge.age);

        System.out.println ("And if you try going back?");
        timeMachine.goToPast(smudge);
        System.out.println(smudge.age);
    }
}
Đầu ra bảng điều khiển:
How old was Smudge when the program started running?
5
Smudge went to the future! Has his age changed?
5
And if you try going back?
5
Nó không hoạt động trở lại! О_О Hãy tìm hiểu chuyện gì đã xảy ra. :) Nó có mọi thứ liên quan đến các phương thức goToPast / goToFuture và cách hoạt động của các tham chiếu. Bây giờ, chú ý của bạn, xin vui lòng! Đây là điều quan trọng nhất để hiểu về cách thức hoạt động của các tham chiếu và phương thức. Thực tế là, khi chúng ta gọi phương thức goToFuture(Cat cat) , đó là một bản sao của tham chiếu đến đối tượng mèo được truyền, chứ không phải chính tham chiếu đó. Do đó, khi chúng ta truyền một đối tượng cho một phương thức, có hai tham chiếu đến đối tượng. Điều này rất quan trọng để hiểu những gì đang xảy ra. Đây chính xác là lý do tại sao tuổi của con mèo không thay đổi trong ví dụ trước của chúng ta. Trong ví dụ trước, khi thay đổi độ tuổi, chúng ta chỉ cần lấy tham chiếu được truyền cho goToFuture()và sử dụng nó để tìm đối tượng trong bộ nhớ và thay đổi tuổi của nó ( cat.age += 10 ). Nhưng bây giờ, bên trong phương thức goToFuture() , chúng ta đang tạo một đối tượng mới ( cat = new Cat(cat.age) ) và đối tượng này được gán cùng một bản sao tham chiếu đã được truyền cho phương thức. Kết quả là:
  • Tham chiếu đầu tiên ( Cat smudge = new Cat (5) ) trỏ đến con mèo ban đầu (với 5 tuổi)
  • Sau đó, khi chúng ta chuyển biến cat phương thức goToPast() và gán cho nó một đối tượng mới, tham chiếu đã được sao chép.
Và điều này đưa chúng ta đến kết quả cuối cùng: hai tham chiếu trỏ đến hai đối tượng khác nhau. Nhưng chúng tôi chỉ thay đổi tuổi của một trong số chúng (cái được tạo bên trong phương thức).
cat.age += 10;
Và tất nhiên, trong phương thức main(), chúng ta có thể thấy trên bảng điều khiển rằng tuổi của con mèo, smudge.age , không thay đổi. Xét cho cùng, smudge là một biến tham chiếu vẫn trỏ đến đối tượng gốc, cũ với 5 tuổi và chúng tôi không làm gì với đối tượng đó. Tất cả các thay đổi về tuổi của chúng tôi đã được thực hiện trên đối tượng mới. Vì vậy, hóa ra các đối tượng được truyền cho các phương thức bằng cách tham chiếu. Bản sao của các đối tượng không bao giờ được tạo tự động. Nếu bạn truyền một đối tượng mèo vào một phương thức và thay đổi tuổi của nó, bạn sẽ thay đổi tuổi của nó. Nhưng các biến tham chiếu được sao chép khi gán giá trị và/hoặc gọi phương thức! Hãy lặp lại ở đây những gì chúng ta đã nói về việc truyền nguyên hàm: "Khi chúng ta gọi phương thức changeInt() và truyền intbiến x (=15) , thì phương thức này không nhận chính biến x mà là một bản sao của nó. Do đó, mọi thay đổi được thực hiện đối với bản sao không ảnh hưởng đến x gốc của chúng tôiCuối cùng, bạn vẫn sẽ tranh cãi nhiều lần về cách các đối số được truyền trong Java (ngay cả với những nhà phát triển có kinh nghiệm). Nhưng, bây giờ bạn biết chính xác làm thế nào nó hoạt động. Giữ nó lên! :) Để củng cố những gì bạn đã học, chúng tôi khuyên bạn nên xem một video bài học từ Khóa học Java của chúng tôi
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào