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
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 đóCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
1 có cả biến thể hiệnCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
2 và phương thứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
3. Họ được thừa hưởng từCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
5 là một loạiCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
1 cuối cùng là một loạiCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4. Đối tượngCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
5 kế thừa tất cả các đặc điểm của đối tượngCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
1 và ngược lại, đối tượngCat
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ứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
42 và biếnclass
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-1class
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
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à
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ụ sauclass
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
Hình 6-1. Một hệ thống phân cấp lớp
Ví dụ
5Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
46 trong ví dụ này có thể được gán cho biến loạiclass
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
4Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
48 vìclass
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
5 là một kiểu con củaCat
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ượngCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4 cũng sẽ chấp nhận một thể hiện của một loạiCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
5 hoặc bất kỳ loạiCat
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 đóCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
2 được khai báo ở ba vị trí. dưới dạng biến cục bộ trong phương thứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
85 của lớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
1, dưới dạng biến thể hiện của lớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
1 và dưới dạng biến thể hiện của lớpCat
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óCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
89 với một biếnCat
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ịchCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
Đây là một ví dụ đơn giản
4class
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
Trong ví dụ này, chúng tôi tạo bóng cho biến thể hiện
31 để thay đổi loại của nó từCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
89 thànhCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
30. [] Các phương thức được định nghĩa trong lớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
34 xem biến số nguyênCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
31, trong khi các phương thức được định nghĩa trong lớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
36 xem biến dấu phẩy độngCat
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ủaCat
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ênCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
31Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
Bởi vì cả hai biến đều tồn tại trong
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óaCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
84 làm từ hạn định trên tài liệu tham khảoCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
8Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
Bên trong
36, từ khóaCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
84 được sử dụng theo cách này sẽ chọn biếnCat
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ụngCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
84 đầy đủ hơn một chútCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
36 là mộtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
34 bằng cách sử dụng nó thông qua một biến kiểuCat
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ếnCat
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ânCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
3Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
Đ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
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 đóCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
Để 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
Hình 6-3. Ghi đè phương thức
Trong Hình 6-3,
1 thay thế phương phápCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
15 củaclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ượngCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
5 cũng được ghi đè để khác với hành vi của đối tượngCat
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ớpCat
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ơnCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
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ứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
82 của nó, thì chúng ta vẫn nhận được phương thứcclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
82 được triển khai trong lớpclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
5, không phải phương thức trong lớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
8Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
5 hoạt động giống như mộtCat
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ếnCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
48 ở đây có thể hoạt động giống như một tham chiếuclass
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
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ếuCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4 sẽ tìm thấy một triển khai trong lớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4, không phải lớpCat
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ớpCat
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ượngCat
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úngCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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à
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ứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
82 của lớpclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
5 của chúng ta sẽ ghi đè một phương thức trong lớp cha như vậyCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
1class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
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 độngCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
Chúng ta đã thấy
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ớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
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óaCat
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ấtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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. ,
04 vàclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ụCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
8class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
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ànclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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 đề
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. ]class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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 đề
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ụclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
Hãy nhanh chóng xem lại mệnh đề
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ộtclass
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ểuclass
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ủaclass
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émclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
13 thực sự có thể némclass
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ủaclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
13 khi chạyclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
2Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
13 nào, bằng cách sử dụng khốiclass
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 taclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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 đề
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
[]
{
...
}
}
0class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
Trong mã này,
4 xác định rằng nó có thể ném mộtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
24 từ phương thứcclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
3 của nó.Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
26 là một lớp con củaclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
4, vì vậy phương thứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
3 của nó cũng phải có khả năng ném mộtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
24. Tuy nhiên, phương thứcclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
3 củaCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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ủaclass
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ạiclass
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ạiclass
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
[]
{
...
}
}
24class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
1class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ệ
24 chung chung hơn và coi nó như bất kỳclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
4 nào khácCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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 đề
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?class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
Đ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
4 của chúng tôi có một phương thức xuất xưởng có tên làCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
42 tạo ra một thể hiện củaclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
4, thì lớpCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
1 của chúng tôi có thể tinh chỉnh kiểu trả về thànhCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
1Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
2class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
46 vàclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ấyCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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ếuclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ổ sungCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
3class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
Trong ví dụ này, lớp
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ụngclass
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ủaclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ếuCat
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 đèCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
58 được gán cho một biến loạiclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
59, để sử dụng lại nó như mộtclass
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ộtclass
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ộtclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
62 thành mộtclass
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 đếnclass
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ôngclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
67class
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ó;class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ề
5 của chúng taCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
Chúng ta không thể gán lại tham chiếu trong
48 cho biếnclass
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ộtclass
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
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ếuCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
46 thành loạiclass
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
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ếtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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
5, bạn có thể sử dụng nó như mộtCat
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ộtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
59 mà bạn nghĩ là mộtclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
5, thì bạn phải thực hiện ép diễn viên để đưa nó trở lại mộtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
4 hoặc mộtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
5. Nếu bạn không chắc chắn liệuCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
59 là mộtclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
5 hay mộtCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
85 trong thời gian chạy, bạn có thể kiểm tra nó vớiclass
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ỗiclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
66class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
67class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
68class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
5class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
46 sang loạiclass
Animal
{
float
weight
;
...
void
eat
[]
{
...
}
...
}
class
Mammal
extends
Animal
{
// inherits weight
int
heartRate
;
...
// inherits eat[]
void
breathe
[]
{
...
}
}
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 đèCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
82class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
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ệnhclass
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àngclass
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ụngclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
95class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
Nếu chúng ta định gọi một hàm tạo của lớp bậc trên với
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ảnclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
6class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
Trong ví dụ này, chúng tôi sử dụng
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ớpclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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ọiCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
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ạnclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
95 hay lệnh gọi ẩn tới hàm tạo mặc định của lớp bậc trênclass
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
[]
{
...
}
}
class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
[]
{
...
}
}
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
[]
{
...
}
}
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
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;Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
7class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
09Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
8class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
09Cat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
9class
Cat
extends
Mammal
{
// inherits weight and heartRate
boolean
longHair
;
...
// inherits eat[] and breathe[]
void
purr
[]
{
...
}
}
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
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ủaCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
14 triển khaiCat
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ớpCat
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ứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
13 đơn giản. Lớp con củaCat
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ứcCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;
13 đơn giản mà lớp con thực hiệnCat
simon
=
new
Cat
[];
Animal
creature
=
simon
;