Nếu một lớp chứa một phương thức trừu tượng ________.

Các lớp trong Java tồn tại trong một hệ thống phân cấp. Một lớp trong Java có thể được khai báo như một lớp con của một lớp khác bằng cách sử dụng từ khóa

    Cat simon = new Cat();
    Animal creature = simon;
0. Một lớp con kế thừa các biến và phương thức từ lớp cha của nó và có thể sử dụng chúng như thể chúng được khai báo trong chính lớp con đó

    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }

Trong ví dụ này, một đối tượng kiểu

    Cat simon = new Cat();
    Animal creature = simon;
1 có cả biến thể hiện
    Cat simon = new Cat();
    Animal creature = simon;
2 và phương thức
    Cat simon = new Cat();
    Animal creature = simon;
3. Họ được thừa hưởng từ
    Cat simon = new Cat();
    Animal creature = simon;
4

Một lớp chỉ có thể mở rộng một lớp khác. Để sử dụng thuật ngữ thích hợp, Java cho phép kế thừa duy nhất việc triển khai lớp. Ở phần sau của chương này, chúng ta sẽ nói về các giao diện, thay thế cho đa kế thừa vì nó chủ yếu được sử dụng trong các ngôn ngữ khác

Một phân lớp có thể được phân lớp tiếp theo. Thông thường, phân lớp chuyên biệt hóa hoặc tinh chỉnh một lớp bằng cách thêm các biến và phương thức (bạn không thể loại bỏ hoặc ẩn các biến hoặc phương thức bằng cách phân lớp). Ví dụ

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }

Lớp

    Cat simon = new Cat();
    Animal creature = simon;
5 là một loại
    Cat simon = new Cat();
    Animal creature = simon;
1 cuối cùng là một loại
    Cat simon = new Cat();
    Animal creature = simon;
4. Đối tượng
    Cat simon = new Cat();
    Animal creature = simon;
5 kế thừa tất cả các đặc điểm của đối tượng
    Cat simon = new Cat();
    Animal creature = simon;
1 và ngược lại, đối tượng
    Cat simon = new Cat();
    Animal creature = simon;
4.
    Cat simon = new Cat();
    Animal creature = simon;
5 cũng cung cấp hành vi bổ sung dưới dạng phương thức
    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
42 và biến
    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
43. Chúng ta có thể biểu thị mối quan hệ lớp trong sơ đồ, như trong Hình 6-1

Một lớp con kế thừa tất cả các thành viên của lớp cha của nó không được chỉ định là

    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
44. Như chúng ta sẽ thảo luận ngay sau đây, các mức hiển thị khác ảnh hưởng đến việc các thành viên kế thừa của lớp có thể được nhìn thấy từ bên ngoài lớp và các lớp con của nó, nhưng ở mức tối thiểu, một lớp con luôn có cùng một tập hợp các thành viên hiển thị như lớp cha của nó. Vì lý do này, loại của một lớp con có thể được coi là một kiểu con của lớp cha của nó và các thể hiện của kiểu con có thể được sử dụng ở bất kỳ nơi nào cho phép các thể hiện của siêu kiểu. Xem xét ví dụ sau

    Cat simon = new Cat();
    Animal creature = simon;

Nếu một lớp chứa một phương thức trừu tượng ________.

Hình 6-1. Một hệ thống phân cấp lớp

Ví dụ

    Cat simon = new Cat();
    Animal creature = simon;
5
    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
46 trong ví dụ này có thể được gán cho biến loại
    Cat simon = new Cat();
    Animal creature = simon;
4
    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
48 vì
    Cat simon = new Cat();
    Animal creature = simon;
5 là một kiểu con của
    Cat simon = new Cat();
    Animal creature = simon;
4. Tương tự, bất kỳ phương thức nào chấp nhận một đối tượng
    Cat simon = new Cat();
    Animal creature = simon;
4 cũng sẽ chấp nhận một thể hiện của một loại
    Cat simon = new Cat();
    Animal creature = simon;
5 hoặc bất kỳ loại
    Cat simon = new Cat();
    Animal creature = simon;
1 nào. Đây là một khía cạnh quan trọng của tính đa hình trong một ngôn ngữ hướng đối tượng như Java. Chúng ta sẽ xem nó có thể được sử dụng như thế nào để tinh chỉnh hành vi của một lớp, cũng như thêm các khả năng mới cho lớp đó

Trong Chương 5, chúng ta đã thấy rằng một biến cục bộ cùng tên với một biến thể hiện sẽ che (ẩn) biến thể hiện. Tương tự, một biến thể hiện trong một lớp con có thể che khuất một biến thể hiện cùng tên trong lớp cha của nó, như minh họa trong Hình 6-2. Chúng tôi sẽ đề cập chi tiết về ẩn biến này ngay bây giờ để hoàn thiện và chuẩn bị cho các chủ đề nâng cao hơn, nhưng trong thực tế, bạn hầu như không bao giờ nên làm điều này. Trên thực tế, sẽ tốt hơn nhiều nếu cấu trúc mã của bạn để phân biệt rõ ràng các biến bằng cách sử dụng các tên hoặc quy ước đặt tên khác nhau

Trong Hình 6-2, biến

    Cat simon = new Cat();
    Animal creature = simon;
2 được khai báo ở ba vị trí. dưới dạng biến cục bộ trong phương thức
    Cat simon = new Cat();
    Animal creature = simon;
85 của lớp
    Cat simon = new Cat();
    Animal creature = simon;
1, dưới dạng biến thể hiện của lớp
    Cat simon = new Cat();
    Animal creature = simon;
1 và dưới dạng biến thể hiện của lớp
    Cat simon = new Cat();
    Animal creature = simon;
4. Biến thực tế được chọn khi bạn tham chiếu nó trong mã sẽ phụ thuộc vào phạm vi mà chúng tôi đang làm việc và cách bạn đủ điều kiện tham chiếu đến nó

Nếu một lớp chứa một phương thức trừu tượng ________.

Hình 6-2. Phạm vi của các biến bóng mờ

Trong ví dụ trước, tất cả các biến đều cùng loại. Việc sử dụng các biến bóng mờ hợp lý hơn một chút sẽ liên quan đến việc thay đổi loại của chúng. Ví dụ, chúng ta có thể tạo bóng một biến

    Cat simon = new Cat();
    Animal creature = simon;
89 với một biến
    Cat simon = new Cat();
    Animal creature = simon;
30 trong một lớp con cần giá trị thập phân thay vì giá trị số nguyên. Chúng tôi có thể làm điều này mà không cần thay đổi mã hiện có bởi vì, như tên gọi của nó, khi chúng tôi ẩn các biến, chúng tôi không thay thế chúng mà thay vào đó che giấu chúng. Cả hai biến vẫn tồn tại; . Việc xác định các biến mà các phương thức khác nhau nhìn thấy xảy ra tại thời điểm biên dịch

Đây là một ví dụ đơn giản

    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
4

Trong ví dụ này, chúng tôi tạo bóng cho biến thể hiện

    Cat simon = new Cat();
    Animal creature = simon;
31 để thay đổi loại của nó từ
    Cat simon = new Cat();
    Animal creature = simon;
89 thành
    Cat simon = new Cat();
    Animal creature = simon;
30. [] Các phương thức được định nghĩa trong lớp
    Cat simon = new Cat();
    Animal creature = simon;
34 xem biến số nguyên
    Cat simon = new Cat();
    Animal creature = simon;
31, trong khi các phương thức được định nghĩa trong lớp
    Cat simon = new Cat();
    Animal creature = simon;
36 xem biến dấu phẩy động
    Cat simon = new Cat();
    Animal creature = simon;
31. Tuy nhiên, cả hai biến thực sự tồn tại trong một phiên bản nhất định của
    Cat simon = new Cat();
    Animal creature = simon;
36 và chúng có thể có các giá trị độc lập. Trên thực tế, bất kỳ phương thức nào mà
    Cat simon = new Cat();
    Animal creature = simon;
36 kế thừa từ
    Cat simon = new Cat();
    Animal creature = simon;
34 đều thực sự nhìn thấy biến số nguyên
    Cat simon = new Cat();
    Animal creature = simon;
31

Bởi vì cả hai biến đều tồn tại trong

    Cat simon = new Cat();
    Animal creature = simon;
36, nên chúng ta cần một cách để tham chiếu biến kế thừa từ
    Cat simon = new Cat();
    Animal creature = simon;
34. Chúng tôi làm điều đó bằng cách sử dụng từ khóa
    Cat simon = new Cat();
    Animal creature = simon;
84 làm từ hạn định trên tài liệu tham khảo

    Cat simon = new Cat();
    Animal creature = simon;
8

Bên trong

    Cat simon = new Cat();
    Animal creature = simon;
36, từ khóa
    Cat simon = new Cat();
    Animal creature = simon;
84 được sử dụng theo cách này sẽ chọn biến
    Cat simon = new Cat();
    Animal creature = simon;
31 được xác định trong siêu lớp. Chúng tôi sẽ giải thích việc sử dụng
    Cat simon = new Cat();
    Animal creature = simon;
84 đầy đủ hơn một chút

Một điểm quan trọng khác về các biến bóng mờ liên quan đến cách chúng hoạt động khi chúng ta tham chiếu đến một đối tượng bằng một kiểu ít dẫn xuất hơn (kiểu cha). Ví dụ, chúng ta có thể gọi một đối tượng

    Cat simon = new Cat();
    Animal creature = simon;
36 là một
    Cat simon = new Cat();
    Animal creature = simon;
34 bằng cách sử dụng nó thông qua một biến kiểu
    Cat simon = new Cat();
    Animal creature = simon;
34. Nếu chúng ta làm như vậy và sau đó truy cập vào biến
    Cat simon = new Cat();
    Animal creature = simon;
31, chúng ta sẽ nhận được biến số nguyên chứ không phải số thập phân

    Cat simon = new Cat();
    Animal creature = simon;
3

Điều này cũng đúng nếu chúng ta truy cập đối tượng bằng cách truyền rõ ràng sang kiểu

    Cat simon = new Cat();
    Animal creature = simon;
34 hoặc khi chuyển một thể hiện vào một phương thức chấp nhận kiểu cha đó

Để nhắc lại, tính hữu ích của các biến bóng tối bị hạn chế. Tốt hơn hết là trừu tượng hóa việc sử dụng các biến như thế này theo những cách khác hơn là sử dụng các quy tắc phạm vi phức tạp. Tuy nhiên, điều quan trọng là phải hiểu các khái niệm ở đây trước khi chúng ta nói về việc làm điều tương tự với các phương thức. Chúng ta sẽ thấy một loại hành vi khác và năng động hơn khi các phương thức che khuất các phương thức khác hoặc sử dụng thuật ngữ chính xác, ghi đè các phương thức khác

Trong Chương 5, chúng ta đã thấy rằng chúng ta có thể khai báo các phương thức quá tải (i. e. , các phương thức có cùng tên nhưng khác số lượng hoặc kiểu đối số) trong một lớp. Lựa chọn phương thức quá tải hoạt động theo cách chúng tôi đã mô tả trên tất cả các phương thức có sẵn cho một lớp, bao gồm cả những phương thức kế thừa. Điều này có nghĩa là một lớp con có thể định nghĩa các phương thức quá tải bổ sung thêm vào các phương thức quá tải được cung cấp bởi một lớp cha

Một lớp con có thể làm nhiều hơn thế; . Trong trường hợp đó, phương thức trong lớp con sẽ ghi đè phương thức trong lớp cha và thay thế hiệu quả việc triển khai của nó, như minh họa trong Hình 6-3. Ghi đè các phương thức để thay đổi hành vi của các đối tượng được gọi là đa hình kiểu con. Đó là cách sử dụng mà hầu hết mọi người nghĩ đến khi họ nói về sức mạnh của ngôn ngữ hướng đối tượng

Nếu một lớp chứa một phương thức trừu tượng ________.

Hình 6-3. Ghi đè phương thức

Trong Hình 6-3,

    Cat simon = new Cat();
    Animal creature = simon;
1 thay thế phương pháp
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
15 của
    Cat simon = new Cat();
    Animal creature = simon;
4, có lẽ để chuyên biệt hóa phương pháp cho hành vi của động vật có vú sinh con. [] Hành vi ngủ của đối tượng
    Cat simon = new Cat();
    Animal creature = simon;
5 cũng được ghi đè để khác với hành vi của đối tượng
    Cat simon = new Cat();
    Animal creature = simon;
4 chung, có lẽ để phù hợp với giấc ngủ ngắn của mèo. Lớp
    Cat simon = new Cat();
    Animal creature = simon;
5 cũng bổ sung các hành vi rừ rừ và săn chuột độc đáo hơn

Từ những gì bạn đã thấy cho đến nay, các phương thức bị ghi đè có thể trông giống như các phương thức ẩn trong các lớp cha, giống như các biến vậy. Nhưng các phương thức bị ghi đè thực sự mạnh hơn thế. Khi có nhiều triển khai của một phương thức trong hệ thống phân cấp kế thừa của một đối tượng, thì phương thức trong lớp “có nguồn gốc cao nhất” (xa nhất trong hệ thống phân cấp) luôn ghi đè lên các phương thức khác, ngay cả khi chúng ta tham chiếu đến đối tượng thông qua tham chiếu của một trong các . []

Ví dụ: nếu chúng ta có một thể hiện

    Cat simon = new Cat();
    Animal creature = simon;
5 được gán cho một biến có kiểu tổng quát hơn là
    Cat simon = new Cat();
    Animal creature = simon;
4 và chúng ta gọi phương thức
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
82 của nó, thì chúng ta vẫn nhận được phương thức
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
82 được triển khai trong lớp
    Cat simon = new Cat();
    Animal creature = simon;
5, không phải phương thức trong lớp
    Cat simon = new Cat();
    Animal creature = simon;
4

    Cat simon = new Cat();
    Animal creature = simon;
8

Nói cách khác, đối với các mục đích của hành vi (các phương thức gọi), một

    Cat simon = new Cat();
    Animal creature = simon;
5 hoạt động giống như một
    Cat simon = new Cat();
    Animal creature = simon;
5, bất kể bạn có gọi nó như vậy hay không. Ở khía cạnh khác, biến
    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
48 ở đây có thể hoạt động giống như một tham chiếu
    Cat simon = new Cat();
    Animal creature = simon;
4. Như chúng tôi đã giải thích trước đó, việc truy cập vào một biến bị che khuất thông qua tham chiếu
    Cat simon = new Cat();
    Animal creature = simon;
4 sẽ tìm thấy một triển khai trong lớp
    Cat simon = new Cat();
    Animal creature = simon;
4, không phải lớp
    Cat simon = new Cat();
    Animal creature = simon;
5. Tuy nhiên, vì các phương thức được định vị một cách linh hoạt, tìm kiếm các lớp con trước tiên, nên phương thức thích hợp trong lớp
    Cat simon = new Cat();
    Animal creature = simon;
5 được gọi, mặc dù chúng ta đang xử lý nó một cách tổng quát hơn như một đối tượng
    Cat simon = new Cat();
    Animal creature = simon;
4. Điều này có nghĩa là hành vi của các đối tượng là động. Chúng ta có thể xử lý các đối tượng chuyên biệt như thể chúng là các loại tổng quát hơn và vẫn tận dụng lợi thế của việc triển khai hành vi chuyên biệt của chúng

Một lỗi lập trình phổ biến trong Java là vô tình làm quá tải một phương thức khi cố gắng ghi đè lên nó. Bất kỳ sự khác biệt nào về số lượng hoặc loại đối số (chữ ký của phương thức) sẽ tạo ra hai phương thức bị quá tải thay vì một phương thức bị ghi đè. Cú pháp chú thích mới trong Java 5. 0 cung cấp một cách để trình biên dịch trợ giúp vấn đề này. Chú thích, như chúng tôi sẽ mô tả trong Chương 7, cho phép chúng tôi thêm các điểm đánh dấu hoặc siêu dữ liệu đặc biệt vào mã nguồn mà trình biên dịch hoặc công cụ thời gian chạy có thể đọc được. Một trong những chú thích tiêu chuẩn mà Java định nghĩa được gọi là

    Cat simon = new Cat();
    Animal creature = simon;
25 và nó cho trình biên dịch biết rằng phương thức mà nó đánh dấu nhằm ghi đè lên một phương thức trong lớp cha. Sau đó, trình biên dịch sẽ cảnh báo nếu phương thức không khớp. Ví dụ: chúng ta có thể chỉ định rằng phương thức
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
82 của lớp
    Cat simon = new Cat();
    Animal creature = simon;
5 của chúng ta sẽ ghi đè một phương thức trong lớp cha như vậy

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
1

Các phương thức bị ghi đè và liên kết động

Trong phần trước, chúng tôi đã đề cập rằng các phương thức quá tải được trình biên dịch chọn tại thời điểm biên dịch. Mặt khác, các phương thức bị ghi đè được chọn động khi chạy. Ngay cả khi chúng ta tạo một thể hiện của một phân lớp mà mã của chúng ta chưa từng thấy trước đây (có lẽ là một lớp mới được tải qua mạng), thì bất kỳ phương thức ghi đè nào chứa trong đó đều được định vị và sử dụng trong thời gian chạy, thay thế những phương thức đã tồn tại khi chúng ta biên dịch mã lần cuối

Ngược lại, nếu chúng ta tạo một lớp mới triển khai một phương thức quá tải bổ sung, cụ thể hơn và thay thế lớp đã biên dịch trong đường dẫn lớp của chúng ta bằng phương thức đó, thì mã của chúng ta sẽ tiếp tục sử dụng cách triển khai mà nó đã khám phá ban đầu. Tình trạng này sẽ tiếp tục cho đến khi chúng tôi biên dịch lại mã của mình cùng với lớp mới. Một hiệu ứng khác của điều này là việc truyền (i. e. , thông báo rõ ràng cho trình biên dịch coi một đối tượng là một trong các kiểu có thể gán của nó) ảnh hưởng đến việc lựa chọn các phương thức quá tải tại thời điểm biên dịch nhưng không ảnh hưởng đến các phương thức bị ghi đè

Trong thực tế, những gì chúng tôi vừa mô tả không phải là điều bạn cần phải lo lắng thường xuyên, nhưng điều quan trọng là phải hiểu máy ảo làm gì và không làm gì trong thời gian chạy.

Các phương thức tĩnh không thuộc về bất kỳ đối tượng nào; . Đó là lý do tại sao các phương thức tĩnh được gọi là "tĩnh";

Một phương thức tĩnh trong lớp cha có thể bị che khuất bởi một phương thức tĩnh khác trong lớp con, miễn là phương thức ban đầu không được khai báo là cuối cùng. Tuy nhiên, cả hai phương thức luôn có thể truy cập trực tiếp thông qua tên lớp tương ứng của chúng. Bạn không thể "ghi đè" một phương thức tĩnh bằng một phương thức thể hiện. Nói cách khác, bạn không thể có một phương thức tĩnh và một phương thức thể hiện có cùng chữ ký trong cùng một hệ thống phân cấp lớp

phương pháp cuối cùng và hiệu suất

Trong các ngôn ngữ như C++, mặc định là các phương thức hoạt động giống như các biến ẩn, vì vậy bạn phải khai báo rõ ràng các phương thức bạn muốn là động (hoặc, như C++ gọi chúng là ảo). Trong Java, các phương thức thể hiện, theo mặc định, là động. Nhưng bạn có thể sử dụng công cụ sửa đổi

    Cat simon = new Cat();
    Animal creature = simon;
28 để tuyên bố rằng một phương thức thể hiện không thể bị ghi đè trong một lớp con và nó sẽ không bị ràng buộc động

Chúng ta đã thấy

    Cat simon = new Cat();
    Animal creature = simon;
28 được sử dụng với các biến để biến chúng thành hằng số một cách hiệu quả. Khi được áp dụng cho một phương thức,
    Cat simon = new Cat();
    Animal creature = simon;
28 có nghĩa là việc triển khai của nó không đổi—không cho phép ghi đè.
    Cat simon = new Cat();
    Animal creature = simon;
28 cũng có thể được áp dụng cho toàn bộ lớp, điều đó có nghĩa là lớp không thể được phân lớp

Trước đây, liên kết phương thức động đi kèm với một hình phạt hiệu suất đáng kể và một số người vẫn có xu hướng sử dụng công cụ sửa đổi

    Cat simon = new Cat();
    Animal creature = simon;
28 để bảo vệ chống lại điều này. Các hệ thống thời gian chạy Java hiện đại loại bỏ nhu cầu về loại tinh chỉnh này. Thời gian chạy định hình có thể xác định phương thức nào không bị ghi đè và "lạc quan" nội tuyến chúng, xử lý chúng như thể chúng là phương thức cuối cùng cho đến khi cần thiết phải làm khác. Theo quy định, bạn nên sử dụng từ khóa
    Cat simon = new Cat();
    Animal creature = simon;
28 khi nó phù hợp với cấu trúc chương trình của bạn, không phải để xem xét hiệu suất

Trong một số phiên bản Java cũ hơn, trình biên dịch javac có thể được chạy với khóa -O, lệnh này yêu cầu trình biên dịch thực hiện một số tối ưu hóa nhất định, như nội tuyến, tĩnh. Hầu hết các tối ưu hóa này hiện được thực hiện trong thời gian chạy bởi các máy ảo thông minh hơn, do đó, các chuyển đổi như thế này thường không cần thiết

Một loại tối ưu hóa khác cho phép bạn đưa mã gỡ lỗi vào nguồn Java của mình mà không bị phạt về kích thước hoặc hiệu suất. Mặc dù Java không có bộ tiền xử lý để kiểm soát rõ ràng nguồn nào được đưa vào, nhưng bạn có thể nhận được một số tác dụng tương tự bằng cách đặt một khối mã có điều kiện trên một hằng số (i. e. ,

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
04 và
    Cat simon = new Cat();
    Animal creature = simon;
28) biến. Trình biên dịch Java đủ thông minh để loại bỏ mã này khi nó xác định rằng nó sẽ không được gọi. Ví dụ

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
8

Trong trường hợp này, trình biên dịch có thể nhận ra rằng điều kiện trên biến ________ 606 luôn là ________ 607 và phần thân của phương thức

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
08 sẽ được tối ưu hóa. Với trình biên dịch hiện đại, lệnh gọi phương thức thậm chí có thể được tối ưu hóa hoàn toàn

Lưu ý rằng loại mã gỡ lỗi này hữu ích cho các mục đích như ghi nhật ký. Ngược lại với các xác nhận mà chúng tôi đã đề cập trong Chương 4, được cho là các kiểm tra có/không nhằm đảm bảo tính chính xác của logic chương trình của bạn, các khối mã có điều kiện này có thể thực hiện định dạng đắt tiền hoặc xử lý đầu ra khác hữu ích trong quá trình phát triển nhưng bạn

Lựa chọn phương pháp xem xét lại

Bây giờ bạn đã có một cảm giác tốt, trực quan về cách các phương thức được chọn từ nhóm các tên phương thức có khả năng bị quá tải và bị ghi đè của một lớp. Tuy nhiên, nếu bạn muốn biết thêm chi tiết, chúng tôi sẽ cung cấp ngay bây giờ

Trong phần trước, chúng ta đã đưa ra một quy nạp quy nạp để giải quyết phương thức quá tải. Nó nói rằng một phương thức được coi là cụ thể hơn một phương thức khác nếu các đối số của nó có thể gán được cho các đối số của phương thức thứ hai. Bây giờ chúng ta có thể mở rộng quy tắc này để bao gồm việc giải quyết các phương thức bị ghi đè bằng cách thêm điều kiện sau. để cụ thể hơn một phương thức khác, loại lớp chứa phương thức này cũng phải được gán cho loại lớp chứa phương thức thứ hai

Điều đó nghĩa là gì? . Bởi vì các loại lớp con có thể gán cho các loại lớp cha, nhưng không phải ngược lại, độ phân giải được đẩy theo cách mà chúng tôi mong đợi xuống chuỗi đối với các lớp con. Điều này bổ sung một cách hiệu quả chiều thứ hai cho tìm kiếm, trong đó độ phân giải được đẩy xuống cây thừa kế đối với các lớp được tinh chỉnh hơn và đồng thời hướng tới phương thức quá tải cụ thể nhất trong một lớp nhất định

Ngoại lệ và phương pháp ghi đè

Một phương thức ghi đè có thể thay đổi hành vi của một đối tượng, nhưng theo một số cách, nó vẫn phải thực hiện hợp đồng của phương thức ban đầu với người dùng. Cụ thể, một phương thức ghi đè phải tuân thủ mệnh đề

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
09 của phương thức ban đầu. Phương thức mới không thể ném các loại ngoại lệ được kiểm tra mới. Nó chỉ có thể tuyên bố rằng nó ném các loại ngoại lệ có thể gán cho những loại được ném bởi phương thức trong lớp cha; . Nếu phương thức mới không đưa ra bất kỳ ngoại lệ đã kiểm tra nào của bản gốc, thì nó không phải khai báo chúng và những người gọi phương thức thông qua lớp con không phải đề phòng chúng. (Bằng cách này, bạn có thể ghi đè lên một phương thức để “xử lý” các ngoại lệ cho người dùng. )

Vì vậy, phương thức mới có thể khai báo chính xác các ngoại lệ được kiểm tra giống như phương thức ban đầu hoặc nó có tùy chọn để tinh chỉnh các loại đó bằng cách tuyên bố rằng nó đưa ra các kiểu con cụ thể hơn so với phương thức đã ghi đè. Điều này không giống như việc chỉ nói rằng phương thức có thể đơn giản ném các kiểu con của các ngoại lệ đã khai báo của nó; . Phương pháp mới thực sự có thể xác định lại mệnh đề

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
09 của phương pháp cụ thể hơn. Kỹ thuật này được gọi là gõ hiệp biến của mệnh đề
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
09, có nghĩa là các loại ngoại lệ mà người dùng phải bảo vệ thay đổi để trở nên tinh tế hơn với loại phụ

Hãy nhanh chóng xem lại mệnh đề

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
09 thực sự có nghĩa là gì. Nếu một phương thức tuyên bố rằng nó có thể ném ra một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
13, thì điều đó thực sự nói rằng nó có thể ném ra các ngoại lệ kiểu
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
13 hoặc các kiểu con của nó. Ví dụ,
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
15 là một loại của
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
13. Một phương thức tuyên bố rằng nó có thể ném
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
13 thực sự có thể ném
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
15 hoặc bất kỳ kiểu con nào khác của
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
13 khi chạy

    Cat simon = new Cat();
    Animal creature = simon;
2

Khi chúng ta gọi phương thức này, trình biên dịch sẽ đảm bảo rằng chúng ta cho phép khả năng xảy ra bất kỳ loại

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
13 nào, bằng cách sử dụng khối
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
21 hoặc bằng cách ném ngoại lệ từ phương thức của chúng ta

Khi chúng ta ghi đè một phương thức trong một lớp con, chúng ta có cơ hội viết lại mệnh đề

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
09 của phương thức một chút. Phương thức mới vẫn phải tương thích ngược với phương thức ban đầu, vì vậy mọi ngoại lệ được kiểm tra mà nó ném ra phải được gán cho những ngoại lệ được ném bởi phương thức bị ghi đè. Nhưng chúng ta có thể cụ thể hơn nếu muốn, tinh chỉnh loại ngoại lệ để phù hợp với hành vi của phương thức mới. Ví dụ

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
0

Trong mã này,

    Cat simon = new Cat();
    Animal creature = simon;
4 xác định rằng nó có thể ném một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
24 từ phương thức
    Cat simon = new Cat();
    Animal creature = simon;
3 của nó.
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
26 là một lớp con của
    Cat simon = new Cat();
    Animal creature = simon;
4, vì vậy phương thức
    Cat simon = new Cat();
    Animal creature = simon;
3 của nó cũng phải có khả năng ném một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
24. Tuy nhiên, phương thức
    Cat simon = new Cat();
    Animal creature = simon;
3 của
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
26 thực sự tuyên bố rằng nó ném ra một ngoại lệ cụ thể hơn.
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
32. Nó có thể làm điều này vì
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
32 là một kiểu con của
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
24. Nếu chúng ta đang làm việc trực tiếp với một loại
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
26, trình biên dịch sẽ cho phép chúng ta chỉ bắt được loại
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
32 và không yêu cầu chúng ta đề phòng loại chung chung hơn là
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
24

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
1

Mặt khác, nếu chúng tôi không quan tâm tại sao thực phẩm không ăn được, chúng tôi có thể tự do bảo vệ

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
24 chung chung hơn và coi nó như bất kỳ
    Cat simon = new Cat();
    Animal creature = simon;
4 nào khác

Tóm lại, một phương thức ghi đè có thể tinh chỉnh không chỉ hành vi của phương thức cha mà còn cả loại ngoại lệ được kiểm tra mà nó đưa ra. Tiếp theo, chúng ta sẽ nói về các phương thức bị ghi đè thay đổi kiểu trả về của chúng theo cùng một cách

Kiểu trả về và phương thức bị ghi đè

Để một phương thức đủ điều kiện là một phương thức được ghi đè trong một lớp con, phương thức đó phải có cùng số lượng và loại đối số. Nó phải có cùng “đầu vào” như nó vốn có. Như chúng ta đã thấy trong phần trước, các phương thức ghi đè có thể tinh chỉnh “đầu ra” của chúng ở một mức độ nào đó. Cụ thể, họ có thể thu hẹp mệnh đề

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
09 của mình bằng cách tuyên bố rằng họ ném các kiểu con của các kiểu ngoại lệ của phương thức ban đầu. Còn “đầu ra” chính của một phương pháp thì sao?

Điều này có nghĩa là khi bạn ghi đè một phương thức, bạn có thể thay đổi kiểu trả về thành kiểu con của kiểu trả về của phương thức ban đầu. Ví dụ: nếu lớp

    Cat simon = new Cat();
    Animal creature = simon;
4 của chúng tôi có một phương thức xuất xưởng có tên là
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
42 tạo ra một thể hiện của
    Cat simon = new Cat();
    Animal creature = simon;
4, thì lớp
    Cat simon = new Cat();
    Animal creature = simon;
1 của chúng tôi có thể tinh chỉnh kiểu trả về thành
    Cat simon = new Cat();
    Animal creature = simon;
1

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
2

Như chúng ta sẽ thấy ở phần sau, kỹ thuật viết mã này rất hữu ích vì nó loại bỏ một số thao tác truyền đối tượng trong thời gian chạy.

Tài liệu tham khảo đặc biệt. cái này và siêu

Các tham chiếu đặc biệt

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
46 và
    Cat simon = new Cat();
    Animal creature = simon;
84 cho phép bạn lần lượt tham chiếu đến các thành viên của thể hiện đối tượng hiện tại hoặc các thành viên của lớp cha. Chúng ta đã thấy
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
46 được sử dụng ở những nơi khác để truyền tham chiếu đến đối tượng hiện tại và để tham chiếu đến các biến thể hiện bị che khuất. Tham chiếu
    Cat simon = new Cat();
    Animal creature = simon;
84 cũng làm như vậy đối với phụ huynh của một lớp. Bạn có thể sử dụng nó để chỉ các thành viên của một siêu lớp đã bị che khuất hoặc bị ghi đè. Khả năng gọi phương thức ban đầu của lớp cha cho phép chúng ta sử dụng nó như một phần của phương thức mới, ủy quyền cho hành vi của nó trước hoặc sau khi chúng ta thực hiện công việc bổ sung

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
3

Trong ví dụ này, lớp

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
26 của chúng ta ghi đè phương thức ________ 651 để trước tiên thực hiện một số kiểm tra trên đối tượng ________ 652. Sau khi thực hiện công việc của mình, nó sử dụng
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
53 để gọi triển khai (nếu không sẽ bị ghi đè và không thể truy cập được) của
    Cat simon = new Cat();
    Animal creature = simon;
3 trong lớp cha của nó

    Cat simon = new Cat();
    Animal creature = simon;
84 nhắc tìm kiếm phương thức hoặc biến bắt đầu trong phạm vi của lớp cha trực tiếp thay vì lớp hiện tại. Phương thức hoặc biến được kế thừa được tìm thấy có thể nằm trong lớp cha trực tiếp hoặc một lớp tiếp theo trên cây. Việc sử dụng tham chiếu
    Cat simon = new Cat();
    Animal creature = simon;
84 khi được áp dụng cho các phương thức được ghi đè của lớp cha là đặc biệt; . Không có
    Cat simon = new Cat();
    Animal creature = simon;
84, sẽ không có cách nào để truy cập các phương thức bị ghi đè

Một diễn viên yêu cầu trình biên dịch thay đổi loại rõ ràng của một tham chiếu đối tượng. Công dụng chính của ép kiểu là khi một đối tượng tạm thời được gán cho một kiểu tổng quát hơn. Ví dụ: nếu một

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
58 được gán cho một biến loại
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
59, để sử dụng lại nó như một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
58, chúng ta phải thực hiện ép kiểu để lấy lại nó. Trình biên dịch chỉ nhận ra các loại biến được khai báo và không biết rằng chúng ta đã thực sự đặt một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
58 vào đó. Trong Java, các diễn viên được kiểm tra cả khi biên dịch và khi chạy để đảm bảo rằng chúng hợp pháp. Tại thời điểm biên dịch, trình biên dịch Java sẽ ngăn bạn cố gắng thực hiện một phép ép kiểu không thể hoạt động (chẳng hạn như chuyển trực tiếp một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
62 thành một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
58). Và trong thời gian chạy, Java sẽ kiểm tra xem các phôi có hợp lý không (chẳng hạn như
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
59 đến
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
58 của chúng tôi) có thực sự chính xác đối với các đối tượng thực có liên quan hay không

Cố gắng truyền một đối tượng sang một loại không tương thích trong thời gian chạy sẽ dẫn đến ____666

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
67
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
68. Chỉ các phép truyền giữa các đối tượng trong cùng một hệ thống phân cấp thừa kế (và, như chúng ta sẽ thấy sau, với các giao diện phù hợp) là hợp pháp trong Java và vượt qua sự giám sát kỹ lưỡng của trình biên dịch và hệ thống thời gian chạy. Diễn viên trong Java chỉ ảnh hưởng đến việc xử lý các tham chiếu; . Đây là một quy tắc quan trọng cần ghi nhớ. Bạn không bao giờ thay đổi đối tượng được trỏ tới bởi một tham chiếu bằng cách ép kiểu nó;

Có thể sử dụng phép truyền để thu hẹp hoặc hạ thấp loại tham chiếu—để làm cho nó cụ thể hơn. Thông thường, chúng tôi sẽ làm điều này khi chúng tôi phải truy xuất một đối tượng từ một loại bộ sưu tập tổng quát hơn hoặc khi nó đã được sử dụng trước đó như một loại ít dẫn xuất hơn. (Ví dụ nguyên mẫu đang sử dụng một đối tượng trong bộ sưu tập, như chúng ta sẽ thấy trong Chương 11. ) Tiếp tục với ví dụ về

    Cat simon = new Cat();
    Animal creature = simon;
5 của chúng ta

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
4

Chúng ta không thể gán lại tham chiếu trong

    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
48 cho biến
    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
46 mặc dù chúng ta biết rằng nó chứa một thể hiện của một
    Cat simon = new Cat();
    Animal creature = simon;
5 (Simon). Chúng ta phải thực hiện ép kiểu được chỉ định để thu hẹp tham chiếu. Lưu ý rằng một phép gán ẩn đã được thực hiện khi chúng tôi thực hiện theo cách khác để mở rộng tham chiếu
    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
46 thành loại
    Cat simon = new Cat();
    Animal creature = simon;
4 trong lần gán đầu tiên. Trong trường hợp này, một diễn viên rõ ràng sẽ hợp pháp nhưng không cần thiết

Tất cả điều này có nghĩa là bạn không thể nói dối hoặc đoán về một đối tượng. Nếu bạn có một đối tượng

    Cat simon = new Cat();
    Animal creature = simon;
5, bạn có thể sử dụng nó như một
    Cat simon = new Cat();
    Animal creature = simon;
4 hoặc thậm chí là ________ 659 vì tất cả các lớp Java đều là lớp con của ________ 659. Nhưng nếu bạn có một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
59 mà bạn nghĩ là một
    Cat simon = new Cat();
    Animal creature = simon;
5, thì bạn phải thực hiện ép diễn viên để đưa nó trở lại một
    Cat simon = new Cat();
    Animal creature = simon;
4 hoặc một
    Cat simon = new Cat();
    Animal creature = simon;
5. Nếu bạn không chắc chắn liệu
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
59 là một
    Cat simon = new Cat();
    Animal creature = simon;
5 hay một
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
85 trong thời gian chạy, bạn có thể kiểm tra nó với
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
86 trước khi thực hiện ép kiểu. Nếu bạn không kiểm tra và bạn truyền sai, hệ thống thời gian chạy sẽ đưa ra một lỗi
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
66
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
67
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
68

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
5

Như chúng tôi đã đề cập trước đó, việc ép kiểu có thể ảnh hưởng đến việc lựa chọn các mục thời gian biên dịch, chẳng hạn như các biến và các phương thức quá tải, nhưng không ảnh hưởng đến việc lựa chọn các phương thức bị ghi đè. Hình 6-4 cho thấy sự khác biệt. Như được hiển thị trong nửa trên của sơ đồ, việc chuyển tham chiếu

    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }
46 sang loại
    Cat simon = new Cat();
    Animal creature = simon;
4 (mở rộng nó) ảnh hưởng đến việc lựa chọn biến bóng mờ
    Cat simon = new Cat();
    Animal creature = simon;
2 bên trong nó. Tuy nhiên, như nửa dưới của sơ đồ chỉ ra, việc ép kiểu không ảnh hưởng đến việc lựa chọn phương thức bị ghi đè
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
82

Nếu một lớp chứa một phương thức trừu tượng ________.

Hình 6-4. Đúc và lựa chọn các phương pháp và biến

Truyền trong Java là điều mà các lập trình viên cố gắng tránh. Điều này không chỉ bởi vì nó chỉ ra một điểm yếu trong cách gõ tĩnh của mã, mà bởi vì các phép truyền cũng có thể đơn giản là tẻ nhạt khi sử dụng và làm cho mã khó đọc hơn. Thật không may, rất nhiều mã được viết bằng Java trong quá khứ không có lựa chọn nào khác ngoài việc dựa vào ép kiểu để nó có thể hoạt động với bất kỳ loại đối tượng nào mà người dùng yêu cầu. java5. 0 đã giới thiệu một tính năng ngôn ngữ mới, generics, một phần để giải quyết vấn đề này. Generics cho phép người dùng "gõ" mã Java cho một loại đối tượng cụ thể, loại bỏ nhu cầu truyền trong nhiều tình huống. Chúng ta sẽ đề cập chi tiết về khái quát trong Chương 8 và xem cách chúng giảm nhu cầu truyền trong hầu hết mã Java

Sử dụng Superclass Constructor

Trước đây khi chúng ta nói về các hàm tạo, chúng ta đã thảo luận về cách câu lệnh đặc biệt

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
94 gọi một hàm tạo bị quá tải khi nhập vào một hàm tạo khác. Tương tự, câu lệnh
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
95 gọi hàm tạo của một lớp cha một cách rõ ràng. Tất nhiên, chúng ta cũng đã nói về cách Java thực hiện một chuỗi lệnh gọi hàm tạo bao gồm hàm tạo của siêu lớp, vậy tại sao lại sử dụng rõ ràng
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
95? . Nếu chúng ta muốn gọi một hàm tạo của lớp bậc trên nhận các đối số, chúng ta phải thực hiện điều đó một cách rõ ràng bằng cách sử dụng
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
95

Nếu chúng ta định gọi một hàm tạo của lớp bậc trên với

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
95, thì đó phải là câu lệnh đầu tiên của hàm tạo của chúng ta, giống như
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
94 phải là lệnh gọi đầu tiên chúng ta thực hiện trong một hàm tạo quá tải. Đây là một ví dụ đơn giản

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
6

Trong ví dụ này, chúng tôi sử dụng

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
95 để tận dụng lợi thế của việc triển khai hàm tạo của lớp cha và tránh sao chép mã để thiết lập đối tượng dựa trên tên của nó. Trên thực tế, vì lớp
    Cat simon = new Cat();
    Animal creature = simon;
01 không định nghĩa hàm tạo mặc định (không có đối số), nên chúng tôi không có lựa chọn nào khác ngoài việc gọi
    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
95 một cách rõ ràng. Nếu không, trình biên dịch sẽ phàn nàn rằng nó không thể tìm thấy hàm tạo mặc định thích hợp để gọi. Nói cách khác, nếu bạn phân lớp một lớp có tất cả các hàm tạo đều nhận đối số, thì bạn phải gọi một trong các hàm tạo của lớp cha một cách rõ ràng từ ít nhất một trong các hàm tạo của lớp con của bạn

Các biến thể hiện của lớp được khởi tạo khi trả về từ hàm tạo của lớp bậc trên, cho dù đó là do lệnh gọi rõ ràng tới

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
95 hay lệnh gọi ẩn tới hàm tạo mặc định của lớp bậc trên

tiết lộ đầy đủ. Constructor và khởi tạo

Bây giờ chúng ta có thể kể toàn bộ câu chuyện về cách các hàm tạo được liên kết với nhau và khi khởi tạo biến thể hiện xảy ra. Quy tắc có ba phần và được áp dụng lặp lại cho mỗi hàm tạo liên tiếp được gọi

  • Nếu câu lệnh đầu tiên của hàm tạo là một câu lệnh thông thường—nghĩa là không phải lệnh gọi tới

        class Cat extends Mammal {
            // inherits weight and heartRate
            boolean longHair;
            ...
    
            // inherits eat() and breathe()
            void purr() {
                ...
            }
        }
    94 hoặc
        class Cat extends Mammal {
            // inherits weight and heartRate
            boolean longHair;
            ...
    
            // inherits eat() and breathe()
            void purr() {
                ...
            }
        }
    95—Java sẽ chèn một lệnh gọi ngầm định tới
        class Cat extends Mammal {
            // inherits weight and heartRate
            boolean longHair;
            ...
    
            // inherits eat() and breathe()
            void purr() {
                ...
            }
        }
    95 để gọi hàm tạo mặc định của siêu lớp. Khi trở về từ lệnh gọi đó, Java khởi tạo các biến thể hiện của lớp hiện tại và tiến hành thực thi các câu lệnh của hàm tạo hiện tại

  • Nếu câu lệnh đầu tiên của hàm tạo là lời gọi hàm tạo của lớp bậc trên thông qua

        class Cat extends Mammal {
            // inherits weight and heartRate
            boolean longHair;
            ...
    
            // inherits eat() and breathe()
            void purr() {
                ...
            }
        }
    95, Java sẽ gọi hàm tạo của lớp bậc trên đã chọn. Khi trả về, Java khởi tạo các biến thể hiện của lớp hiện tại và tiếp tục với các câu lệnh của hàm tạo hiện tại

  • Nếu câu lệnh đầu tiên của một hàm tạo là một lệnh gọi đến một hàm tạo bị quá tải thông qua

        class Cat extends Mammal {
            // inherits weight and heartRate
            boolean longHair;
            ...
    
            // inherits eat() and breathe()
            void purr() {
                ...
            }
        }
    94, thì Java sẽ gọi hàm tạo đã chọn và khi trả về, chỉ cần tiếp tục với các câu lệnh của hàm tạo hiện tại. Cuộc gọi đến hàm tạo của lớp cha đã xảy ra trong hàm tạo bị quá tải, rõ ràng hoặc ngầm định, vì vậy việc khởi tạo các biến thể hiện đã xảy ra

Các phương thức và lớp trừu tượng

Một phương thức trong Java có thể được khai báo bằng công cụ sửa đổi

    Cat simon = new Cat();
    Animal creature = simon;
09 để chỉ ra rằng nó chỉ là một nguyên mẫu. Một phương thức trừu tượng không có phần thân; . Bạn không thể trực tiếp sử dụng một lớp có chứa một phương thức trừu tượng;

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
7

Trong Java, một lớp chứa một hoặc nhiều phương thức trừu tượng phải được khai báo rõ ràng là một lớp trừu tượng, đồng thời sử dụng công cụ sửa đổi

    Cat simon = new Cat();
    Animal creature = simon;
09

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
8

Một lớp trừu tượng có thể chứa các phương thức phi trừu tượng khác và các khai báo biến thông thường, nhưng nó không thể được khởi tạo. Để được sử dụng, nó phải được phân lớp và các phương thức trừu tượng của nó phải được "ghi đè" bằng các phương thức triển khai phần thân. Không phải tất cả các phương thức trừu tượng đều phải được triển khai trong một lớp con duy nhất, nhưng một lớp con không ghi đè tất cả các phương thức trừu tượng của lớp cha của nó bằng các triển khai cụ thể, thực tế cũng phải được khai báo

    Cat simon = new Cat();
    Animal creature = simon;
09

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }
9

Các lớp trừu tượng cung cấp một khung cho các lớp sẽ được “điền vào” bởi người triển khai. Ví dụ, lớp

    Cat simon = new Cat();
    Animal creature = simon;
12 có một phương thức trừu tượng duy nhất có tên là
    Cat simon = new Cat();
    Animal creature = simon;
13. Các lớp con khác nhau của
    Cat simon = new Cat();
    Animal creature = simon;
14 triển khai
    Cat simon = new Cat();
    Animal creature = simon;
13 theo cách riêng của chúng để đọc từ các nguồn riêng của chúng. Tuy nhiên, phần còn lại của lớp
    Cat simon = new Cat();
    Animal creature = simon;
14 cung cấp chức năng mở rộng được xây dựng trên phương thức
    Cat simon = new Cat();
    Animal creature = simon;
13 đơn giản. Lớp con của
    Cat simon = new Cat();
    Animal creature = simon;
14 kế thừa các phương thức phi trừu tượng này để cung cấp chức năng dựa trên phương thức
    Cat simon = new Cat();
    Animal creature = simon;
13 đơn giản mà lớp con thực hiện

một quizlet lớp trừu tượng là gì?

Một lớp trừu tượng là một lớp không thể khởi tạo . Nó được sử dụng bằng cách tạo một lớp con kế thừa có thể được khởi tạo.

Khi một phương thức trong một lớp con có cùng chữ ký với một phương thức trong lớp cha thì nó?

Giải thích. Khi một phương thức trong một lớp con có cùng tên và kiểu ký hiệu như một phương thức trong lớp cha, thì phương thức trong lớp con sẽ ghi đè phương thức trong lớp cha .

Điều gì là cần thiết cho một phương thức giao diện có phần thân?

Lớp con kế thừa gì từ lớp cha?

Một lớp con kế thừa tất cả các thành viên (trường, phương thức và lớp lồng nhau) từ lớp cha của nó. Các hàm tạo không phải là thành viên, vì vậy chúng không được kế thừa bởi các lớp con, nhưng hàm tạo của lớp cha có thể được gọi từ lớp con.