JavaRush /Blog Java /Random-VI /Đa hình trong Java

Đa hình trong Java

Xuất bản trong nhóm
Các câu hỏi về 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 trong những nguyên tắc của OOP - tính đ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 và cũng cung cấp các ví dụ nhỏ để làm rõ.

Đa hình là gì?

Tính đa hình là khả năng của một chương trình sử dụng giống hệt các đối tượng có cùng giao diện mà không cần thông tin về loại đối tượng cụ thể này. Nếu bạn trả lời câu hỏi đa hình theo cách này là gì, rất có thể bạn sẽ được yêu cầu giải thích ý của bạn. Một lần nữa, không hỏi thêm nhiều câu hỏi nữa, hãy sắp xếp mọi thứ theo trình tự cho người phỏng vấn.

Tính đa hình trong Java khi phỏng vấn - 1
Chúng ta có thể bắt đầu với thực tế là cách tiếp cận 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 của các đối tượng dựa trên các lớp. Các lớp là các bản vẽ (mẫu) viết sẵn theo đó các đối tượng trong chương trình sẽ được tạo. 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, nó sẽ “cho biết” mục đích của nó bằng tên của nó. Hơn nữa, có thể lưu ý rằng vì Java là ngôn ngữ có kiểu dữ liệu mạnh nên mã chương trình luôn cần chỉ ra loại đối tượng khi khai báo biến. Thêm vào đó, việc gõ nghiêm ngặt sẽ tăng độ an toàn của mã và độ tin cậy của chương trình, đồng thời cho phép bạn ngăn chặn các lỗi không tương thích về loại (ví dụ: cố gắng chia một chuỗi cho một số) ở giai đoạn biên dịch. Đương nhiên, trình biên dịch phải “biết” loại được khai báo - đây có thể là một lớp từ JDK hoặc do chúng tôi tự tạo. Xin lưu ý với người phỏng vấn rằng khi làm việc với mã chương trình, chúng ta không chỉ có thể sử dụng các đối tượng thuộc loại mà chúng ta đã gán khi khai báo mà còn cả các đối tượng con của nó. Đây là điểm quan trọng: chúng ta có thể coi nhiều kiểu như thể chúng chỉ là một (miễn là những kiểu đó có nguồn gốc từ một kiểu cơ sở). Điều này cũng có nghĩa là, sau khi khai báo một biến thuộc loại siêu lớp, chúng ta có thể gán cho nó giá trị của một trong các biến con của nó. Người phỏng vấn sẽ thích nếu bạn đưa ra một ví dụ. Chọn một số đối tượng có thể chung (cơ sở) cho một nhóm đối tượng và kế thừa một vài lớp từ 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 "Я " + name + ", to me " + age + " years. " ;
    }
}
Ở lớp con, ghi đè phương thức lớp cơ sở:
public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// overriding the base class method
    @Override
    public void dance() {
        System.out.println( toString() + "I dance electric boogie!");
    }
}

public class BreakDankDancer extends Dancer{

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

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

        Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);// upcast to base type
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20); // upcast to base type

        List<Dancer> discotheque = Arrays.asList(dancer, breakDanceDancer, electricBoogieDancer);
        for (Dancer d : discotheque) {
            d.dance();// polymorphic method call
        }
    }
}
mainHiển thị trong mã phương thức những gì có trong dòng:
Dancer breakDanceDancer = new BreakDankDancer("Alexei", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Igor", 20);
Chúng ta đã khai báo một biến kiểu siêu lớp và gán cho nó giá trị của một trong các lớp con. Rất có thể, bạn sẽ được hỏi tại sao trình biên dịch sẽ không phàn nàn về sự không khớp giữa các kiểu được khai báo ở bên trái và bên phải của dấu gán, vì Java có kiểu gõ nghiêm ngặt. Giải thích rằng chuyển đổi kiểu hướng lên hoạt động ở đây - tham chiếu đến một đối tượng được hiểu là tham chiếu đến lớp cơ sở. Hơn nữa, trình biên dịch, khi gặp phải cấu trúc như vậy trong mã, sẽ thực hiện việc này một cách tự động và ngầm định. Dựa vào mã ví dụ, có thể thấy rằng kiểu lớp được khai báo ở bên trái dấu gán Dancercó một số dạng (loại) được khai báo ở bên phải BreakDankDancer, ElectricBoogieDancer. Mỗi biểu mẫu có thể có hành vi riêng cho chức năng chung được xác định trong phương thức siêu lớp dance. Nghĩa là, một phương thức được khai báo trong siêu lớp có thể được triển khai khác nhau ở các 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 và đây chính xác là điều tạo ra nhiều hình thức (hành vi) khác nhau. Bạn có thể thấy điều này bằng cách chạy mã phương thức chính để thực thi: Đầu ra chương trình Tôi là Anton, tôi 18 tuổi. Tôi nhảy như mọi người khác. Tôi là Alexey, tôi 19 tuổi. Tôi nhảy breakdance! Tôi là Igor, tôi 20 tuổi. Tôi nhảy điệu boogie điện! Nếu chúng ta không sử dụng tính năng ghi đè ở con cháu thì chúng ta sẽ không có được hành vi khác. BreakDankDancerVí dụ: nếu chúng ta ElectricBoogieDancernhận xét phương thức cho các lớp của mình dance, thì kết quả của chương trình sẽ như thế này: Tôi là Anton, tôi 18 tuổi. Tôi nhảy như mọi người khác. Tôi là Alexey, tôi 19 tuổi. Tôi nhảy như mọi người khác. Tôi là Igor, 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 BreakDankDancercác lớp mới đơn giản là vô ích ElectricBoogieDancer. Chính xác thì nguyên tắc đa hình của Java là gì? Việc sử dụng một đối tượng trong chương trình mà không biết loại cụ thể của nó bị ẩn ở đâu? Trong ví dụ của chúng tôi, đây là lệnh gọi phương thức d.dance()trên một đối tượng dthuộc loại Dancer. Tính đa hình của Java có nghĩa là chương trình không cần biết đối tượng BreakDankDancerhoặc đối tượng sẽ thuộc loại nào ElectricBoogieDancer. Điều chính là nó là hậu duệ của lớp Dancer. Và nếu chúng ta nói về con cháu, cần lưu ý rằng tính kế thừa trong Java không chỉ là extends, mà còn là implements. Bây giờ là lúc cần nhớ rằng Java không hỗ trợ đa kế thừa - mỗi kiểu có thể có một lớp cha (lớp cha) và số lượng lớp con (lớp con) không giới hạn. Do đó, giao diện được sử dụng để thêm nhiều chức năng cho các lớp. Các giao diện làm giảm sự ghép nối của các đối tượng với cha mẹ so với kế thừa và được sử dụng rất rộng rãi. Trong Java, giao diện là một kiểu tham chiếu, do đó chương trình có thể khai báo kiểu đó là một biến của kiểu giao diện. Đây là thời điểm tốt để đưa ra một ví dụ. Hãy tạo giao diện:
public interface Swim {
    void swim();
}
Để rõ ràng, hãy lấy các đối tượng khác nhau và không liên quan và triển khai giao diện trong đó:
public class Human implements Swim {
    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 inflatable ring.");
    }

    @Override
    public String toString() {
        return "Я " + name + ", to me " + age + " years. ";
    }

}

public class Fish implements Swim{
    private String name;

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

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

    }

public class UBoat implements Swim {

    private int speed;

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

    @Override
    public void swim() {
        System.out.println("The submarine is sailing, rotating the propellers, at a speed" + speed + " knots.");
    }
}
Phương pháp main:
public class Main {

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

        List<Swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
Kết quả của việc thực thi một phương thức đa hình được xác định trong một giao diện cho phép chúng ta thấy sự khác biệt trong hành vi của các loại thực hiện giao diện đó. Chúng bao gồm các kết quả khác nhau của việc thực hiện phương thức swim. 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 khi thực thi mã từmain
for (Swim s : swimmers) {
            s.swim();
}
Các phương thức được định nghĩa trong các lớp này có được gọi cho các đối tượng của chúng ta không? Làm thế nào để bạn chọn cách triển khai mong muốn của một phương thức khi thực hiện một chương trình? Để trả lời những câu hỏi này, chúng ta cần nói về sự ràng buộc muộn (động). Bằng cách ràng buộc, chúng tôi muốn nói đến việc thiết lập kết nối giữa lệnh gọi phương thức và cách triển khai cụ thể của nó trong các lớp. Về cơ bản, mã xác định phương thức nào trong ba phương thức được xác định trong các lớp sẽ được thực thi. Theo mặc định, Java sử dụng liên kết muộn (trong thời gian chạy thay vì lúc biên dịch, như trường hợp liên kết sớm). Điều này có nghĩa là khi biên dịch mã
for (Swim s : swimmers) {
            s.swim();
}
trình biên dịch vẫn chưa biết mã đó thuộc lớp nào Humanhoặc Fishliệu Uboatnó có được thực thi trong tệp swim. Điều này sẽ chỉ được xác định khi chương trình được thực thi nhờ cơ chế điều phối động - kiểm tra loại đối tượng trong quá trình thực hiện chương trình và chọn cách triển khai phương thức mong muốn cho loại này. Nếu được hỏi cách thực hiện điều này, bạn có thể trả lời rằng khi tải và khởi tạo các đối tượng, JVM xây dựng các bảng trong bộ nhớ và trong đó liên kết các biến với giá trị của chúng và các đối tượng với các phương thức của chúng. Hơn nữa, nếu một đối tượng được kế thừa hoặc triển khai một giao diện, thì trước tiên, sự hiện diện của các phương thức được ghi đè trong lớp của nó sẽ được kiểm tra. Nếu có, chúng sẽ được gắn với loại này, nếu không, một phương thức sẽ được tìm kiếm được xác định trong lớp một cấp cao hơn (trong lớp cha), v.v. lên đến thư mục gốc trong hệ thống phân cấp nhiều cấp. Nói về tính đa hình trong OOP và cách triển khai nó trong mã chương trình, chúng tôi lưu ý rằng nên sử dụng các mô tả trừu tượng để xác định các lớp cơ sở bằng cách sử dụng các lớp trừu tượng cũng như các giao diện. Cách thực hành này dựa trên việc sử dụng tính trừu tượng - cô lập các hành vi và thuộc tính chung và đặt chúng trong một lớp trừu tượng hoặc chỉ cô lập hành vi chung - trong trường hợp đó chúng ta tạo một giao diện. Xây dựng và thiết kế hệ thống phân cấp các đối tượng dựa trên giao diện và kế thừa lớp là điều kiện tiên quyết để thực hiện nguyên tắc đa hình OOP. Liên quan đến vấn đề đa hình và đổi mới trong Java, chúng ta có thể đề cập rằng khi tạo các lớp và giao diện trừu tượng, bắt đầu với Java 8, có thể viết cách triển khai mặc định các phương thức trừu tượng trong các lớp cơ sở bằng cách sử dụng từ khóa default. Ví dụ:
public interface Swim {
    default void swim() {
        System.out.println("Just floating");
    }
}
Đôi khi họ có thể hỏi về yêu cầu khai báo các phương thức trong lớp cơ sở để không vi phạm nguyên tắc đa hình. Mọi thứ ở đây đều đơn giản: các phương thức này không được là static , PrivateFinal . Riêng tư làm cho phương thức này chỉ khả dụng trong lớp và bạn không thể ghi đè nó trong lớp con. Tĩnh làm cho phương thức trở thành thuộc tính của lớp chứ không phải đối tượng, vì vậy phương thức siêu lớp sẽ luôn được gọi. Final sẽ làm cho phương thức trở nên bất biến và ẩn khỏi những người thừa kế của nó.

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

Câu hỏi về việc sử dụng tính đa hình mang lại cho chúng ta những gì rất có thể cũng sẽ nảy sinh. Ở đây bạn có thể trả lời ngắn gọn mà không đi sâu vào vấn đề cỏ dại:
  1. Cho phép bạn thay thế việc triển khai đối tượng. Đây là những gì thử nghiệm dựa trên.
  2. Cung cấp khả năng mở rộng chương trình - việc tạo nền tảng cho tương lai trở nên dễ dàng hơn nhiều. Thêm các kiểu mới dựa trên các kiểu 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 được viết theo kiểu OOP.
  3. Cho phép bạn kết hợp các đối tượng có cùng loại hoặc hành vi chung vào một bộ sưu tập hoặc mảng và thao tác chúng một cách thống nhất (như trong ví dụ của chúng tôi, khiến mọi người nhảy - một phương pháp dancehoặc bơi - một phương pháp swim).
  4. Tính linh hoạt khi tạo loại mới: bạn có thể chọn triển khai một phương thức từ cấp độ gốc hoặc ghi đè lên cấp độ con.

Lời chia tay cho cuộc hành trình

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