CodeGym /Blog Java /Ngẫu nhiên /Đa hình Java

Đa hình Java

Xuất bản trong nhóm
Các câu hỏi liên quan đến OOP là một phần không thể thiếu trong cuộc phỏng vấn kỹ thuật cho vị trí nhà phát triển Java trong một công ty CNTT. Trong bài viết này, chúng ta sẽ nói về một nguyên tắc của OOP – đa hình. Chúng tôi sẽ tập trung vào các khía cạnh thường được hỏi trong các cuộc phỏng vấn, đồng thời đưa ra một vài ví dụ để làm rõ.

Tính đa hình trong Java là gì?

Tính đa hình là khả năng của chương trình xử lý các đối tượng có cùng giao diện theo cùng một cách mà không cần thông tin về loại cụ thể của đối tượng. Nếu bạn trả lời câu hỏi về tính đa hình là gì, rất có thể bạn sẽ được yêu cầu giải thích ý của mình. Không khơi mào cho hàng loạt câu hỏi bổ sung, hãy trình bày lại tất cả cho người phỏng vấn. Thời gian phỏng vấn: đa hình trong Java - 1Bạn có thể bắt đầu với thực tế là phương pháp OOP liên quan đến việc xây dựng một chương trình Java dựa trên sự tương tác giữa các đối tượng, dựa trên các lớp. Các lớp là các bản thiết kế (mẫu) được viết trước đây được sử dụng để tạo các đối tượng trong chương trình. Hơn nữa, một lớp luôn có một loại cụ thể, với phong cách lập trình tốt, có một cái tên gợi ý mục đích của nó. Hơn nữa, có thể lưu ý rằng vì Java được gõ mạnh, mã chương trình phải luôn chỉ định một loại đối tượng khi các biến được khai báo. Thêm vào đó là thực tế là việc gõ nghiêm ngặt cải thiện tính bảo mật và độ tin cậy của mã, đồng thời giúp ngăn ngừa lỗi do các loại không tương thích (ví dụ: cố gắng chia một chuỗi cho một số) ngay cả khi biên dịch. Đương nhiên, trình biên dịch phải "biết" loại được khai báo - nó có thể là một lớp từ JDK hoặc một lớp do chúng tôi tự tạo. Chỉ ra cho người phỏng vấn biết rằng mã của chúng ta không chỉ có thể sử dụng các đối tượng thuộc loại được chỉ định trong khai báo mà còn cả các đối tượng con của nó.Đây là một điểm quan trọng: chúng ta có thể làm việc với nhiều kiểu khác nhau dưới dạng một kiểu duy nhất (với điều kiện là các kiểu này được dẫn xuất từ ​​một kiểu cơ sở). Điều này cũng có nghĩa là nếu chúng ta khai báo một biến có kiểu là siêu lớp, thì chúng ta có thể gán một thể hiện của một trong các biến con của nó cho biến đó. Người phỏng vấn sẽ thích nếu bạn đưa ra ví dụ. Chọn một số lớp có thể được chia sẻ bởi (một lớp cơ sở cho) một số lớp và làm cho một vài trong số chúng kế thừa nó. Lớp cơ sở:

public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + " I dance like everyone else.");
    }

    @Override
    public String toString() {
        Return "I'm " + name + ". I'm " + age + " years old.";
    }
}
Trong các lớp con, ghi đè phương thức của lớp cơ sở:

public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
Một ví dụ về tính đa hình và cách các đối tượng này có thể được sử dụng trong một chương trình:

public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Fred", 18);

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type 
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
Trong phương pháp chính , chỉ ra rằng các dòng

Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
khai báo một biến của một siêu lớp và gán cho nó một đối tượng là một thể hiện của một trong những hậu duệ của nó. Rất có thể bạn sẽ được hỏi tại sao trình biên dịch không hiển thị khi các kiểu được khai báo ở bên trái và bên phải của toán tử gán không nhất quán — xét cho cùng, Java được gõ mạnh. Giải thích rằng một chuyển đổi kiểu mở rộng đang hoạt động ở đây — một tham chiếu đến một đối tượng được coi như một tham chiếu đến lớp cơ sở của nó. Hơn nữa, khi gặp một cấu trúc như vậy trong mã, trình biên dịch sẽ thực hiện chuyển đổi một cách tự động và hoàn toàn. Mã mẫu cho thấy loại được khai báo ở phía bên trái của toán tử gán ( Dancer ) có nhiều dạng (loại) được khai báo ở phía bên phải ( Breakdancer , ElectricBoogieDancer). Mỗi biểu mẫu có thể có hành vi độc đáo của riêng nó đối với chức năng chung được xác định trong lớp cha ( phương thức nhảy ). Nghĩa là, một phương thức được khai báo trong lớp cha có thể được triển khai khác trong lớp con của nó. Trong trường hợp này, chúng ta đang xử lý việc ghi đè phương thức, đây chính xác là thứ tạo ra nhiều biểu mẫu (hành vi). Điều này có thể được nhìn thấy bằng cách chạy mã trong phương thức chính: Đầu ra chương trình: Tôi là Fred. Tôi 18 tuổi. Tôi nhảy như mọi người khác. Tôi là Jay. Toi 19 tuoi. Tôi nhảy! Tôi là Marcia. Tôi 20 tuổi. Tôi nhảy boogie điện! Nếu chúng ta không ghi đè phương thức trong các lớp con, thì chúng ta sẽ không có hành vi khác. Ví dụ,ElectricBoogieDancer , thì đầu ra của chương trình sẽ là: Tôi là Fred. Tôi 18 tuổi. Tôi nhảy như mọi người khác. Tôi là Jay. Toi 19 tuoi. Tôi nhảy như mọi người khác. Tôi là Marcia. Tôi 20 tuổi. Tôi nhảy như mọi người khác. Và điều này có nghĩa là việc tạo các lớp BreakdancerElectricBoogieDancer đơn giản là không hợp lý . Nguyên tắc đa hình thể hiện cụ thể ở đâu? Trường hợp một đối tượng được sử dụng trong chương trình mà không biết loại cụ thể của nó? Trong ví dụ của chúng ta, nó xảy ra khi phương thức dance() được gọi trên đối tượng Dancer d . Trong Java, tính đa hình có nghĩa là chương trình không cần biết liệu đối tượng có phải là mộtBreakdancer hoặc ElectricBoogie Dancer . Điều quan trọng là nó là hậu duệ của class Dancer . Và nếu bạn đề cập đến hậu duệ, bạn nên lưu ý rằng tính kế thừa trong Java không chỉ là các phần mở rộng mà còn là các phần thực hiện.. Bây giờ là lúc để đề cập rằng Java không hỗ trợ đa kế thừa — mỗi loại có thể có một cha (lớp cha) và số lượng con cháu (lớp con) không giới hạn. Theo đó, các giao diện được sử dụng để thêm nhiều bộ chức năng vào các lớp. So với các lớp con (kế thừa), các giao diện ít kết hợp với lớp cha hơn. Chúng được sử dụng rất rộng rãi. Trong Java, một giao diện là một kiểu tham chiếu, vì vậy chương trình có thể khai báo một biến kiểu giao diện. Bây giờ là lúc để đưa ra một ví dụ. Tạo một giao diện:

public interface CanSwim {
    void swim();
}
Để rõ ràng, chúng tôi sẽ lấy các lớp không liên quan khác nhau và làm cho chúng triển khai giao diện:

public class Human implements CanSwim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+" I swim with an inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}
 
public class Fish implements CanSwim {
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");

    }

public class UBoat implements CanSwim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
phương pháp chính :

public class Main {

    public static void main(String[] args) {
        CanSwim human = new Human("John", 6);
        CanSwim fish = new Fish("Whale");
        CanSwim boat = new UBoat(25);

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
Các kết quả gọi một phương thức đa hình được định nghĩa trong một giao diện cho chúng ta thấy sự khác biệt trong hành vi của các kiểu triển khai giao diện này. Trong trường hợp của chúng tôi, đây là các chuỗi khác nhau được hiển thị bằng phương thức bơi . Sau khi nghiên cứu ví dụ của chúng tôi, người phỏng vấn có thể hỏi tại sao chạy mã này trong phương thức chính

for (Swim s : swimmers) {
            s.swim();        
}
làm cho các phương thức ghi đè được định nghĩa trong các lớp con của chúng ta được gọi? Cách triển khai mong muốn của phương thức được chọn trong khi chương trình đang chạy? Để trả lời những câu hỏi này, bạn cần giải thích ràng buộc muộn (động). Liên kết có nghĩa là thiết lập ánh xạ giữa lời gọi phương thức và cách triển khai lớp cụ thể của nó. Về bản chất, mã xác định phương thức nào trong ba phương thức được định nghĩa trong các lớp sẽ được thực thi. Java sử dụng liên kết muộn theo mặc định, nghĩa là liên kết xảy ra trong thời gian chạy chứ không phải tại thời điểm biên dịch như trường hợp liên kết sớm. Điều này có nghĩa là khi trình biên dịch biên dịch mã này

for (Swim s : swimmers) {
            s.swim();        
}
nó không biết lớp nào ( Human , Fish , hoặc Uboat ) có mã sẽ được thực thi khi bơiphương thức được gọi. Điều này chỉ được xác định khi chương trình được thực thi, nhờ cơ chế liên kết động (kiểm tra loại đối tượng trong thời gian chạy và chọn cách triển khai chính xác cho loại này). Nếu bạn được hỏi làm thế nào điều này được thực hiện, bạn có thể trả lời rằng khi tải và khởi tạo các đối tượng, JVM sẽ xây dựng các bảng trong bộ nhớ và liên kết các biến với các giá trị của chúng và các đối tượng với các phương thức của chúng. Khi làm như vậy, nếu một lớp được kế thừa hoặc triển khai một giao diện, thứ tự đầu tiên của công việc là kiểm tra sự hiện diện của các phương thức bị ghi đè. Nếu có, chúng bị ràng buộc vào loại này. Nếu không, việc tìm kiếm một phương thức phù hợp sẽ chuyển sang lớp cao hơn một bước (cha) và cứ thế cho đến gốc trong hệ thống phân cấp nhiều cấp. Khi nói đến tính đa hình trong OOP và việc triển khai nó trong mã, chúng tôi lưu ý rằng nên sử dụng các lớp trừu tượng và giao diện để cung cấp các định nghĩa trừu tượng của các lớp cơ sở. Thực hành này tuân theo nguyên tắc trừu tượng - xác định hành vi và thuộc tính chung và đặt chúng vào một lớp trừu tượng hoặc chỉ xác định hành vi chung và đặt nó vào một giao diện. Cần phải thiết kế và tạo một hệ thống phân cấp đối tượng dựa trên các giao diện và kế thừa lớp để thực hiện tính đa hình. Về tính đa hình và những đổi mới trong Java, chúng tôi lưu ý rằng bắt đầu từ Java 8, khi tạo các lớp và giao diện trừu tượng, có thể sử dụng hoặc chỉ xác định hành vi phổ biến và đưa nó vào một giao diện. Cần phải thiết kế và tạo một hệ thống phân cấp đối tượng dựa trên các giao diện và kế thừa lớp để thực hiện tính đa hình. Về tính đa hình và những đổi mới trong Java, chúng tôi lưu ý rằng bắt đầu từ Java 8, khi tạo các lớp và giao diện trừu tượng, có thể sử dụng hoặc chỉ xác định hành vi phổ biến và đưa nó vào một giao diện. Cần phải thiết kế và tạo một hệ thống phân cấp đối tượng dựa trên các giao diện và kế thừa lớp để thực hiện tính đa hình. Về tính đa hình và những đổi mới trong Java, chúng tôi lưu ý rằng bắt đầu từ Java 8, khi tạo các lớp và giao diện trừu tượng, có thể sử dụngtừ khóa mặc định để viết triển khai mặc định cho các phương thức trừu tượng trong các lớp cơ sở. Ví dụ:

public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
Đôi khi những người phỏng vấn hỏi về cách các phương thức trong các lớp cơ sở phải được khai báo để nguyên tắc đa hình không bị vi phạm. Câu trả lời rất đơn giản: các phương thức này không được là tĩnh , riêng tư hay cuối cùng . Riêng tư làm cho một phương thức chỉ khả dụng trong một lớp, vì vậy bạn sẽ không thể ghi đè lên phương thức đó trong một lớp con. Tĩnh liên kết một phương thức với lớp chứ không phải bất kỳ đối tượng nào, vì vậy phương thức của lớp cha sẽ luôn được gọi. Và final làm cho một phương thức trở nên bất biến và ẩn khỏi các lớp con.

Tính đa hình mang lại cho chúng ta điều gì?

Rất có thể bạn cũng sẽ được hỏi về việc tính đa hình có lợi cho chúng ta như thế nào. Bạn có thể trả lời câu hỏi này một cách ngắn gọn mà không bị sa lầy vào các chi tiết lông lá:
  1. Nó làm cho nó có thể thay thế việc triển khai lớp. Thử nghiệm được xây dựng trên đó.
  2. Nó tạo điều kiện cho khả năng mở rộng, giúp dễ dàng tạo ra một nền tảng có thể được xây dựng trong tương lai. Thêm các loại mới dựa trên các loại hiện có là cách phổ biến nhất để mở rộng chức năng của các chương trình OOP.
  3. Nó cho phép bạn kết hợp các đối tượng có chung kiểu hoặc hành vi vào một tập hợp hoặc mảng và xử lý chúng một cách thống nhất (như trong ví dụ của chúng tôi, nơi chúng tôi buộc mọi người phải nhảy() hoặc bơi() :)
  4. Tính linh hoạt trong việc tạo các loại mới: bạn có thể chọn cách triển khai phương thức của lớp cha hoặc ghi đè lên lớp con.

Vài lời chia tay

Đa hình là một chủ đề rất quan trọng và rộng lớn. Nó là chủ đề của gần một nửa bài viết này về OOP trong Java và là một phần tốt trong nền tảng của ngôn ngữ này. Bạn sẽ không thể tránh được việc xác định nguyên tắc này trong một cuộc phỏng vấn. Nếu bạn không biết hoặc không hiểu nó, cuộc phỏng vấn có thể sẽ đi đến hồi kết. Vì vậy, đừng là kẻ lười biếng — hãy đánh giá kiến ​​thức của bạn trước cuộc phỏng vấn và làm mới nó nếu cần.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION