Những lý do tốt để cấm kế thừa trong Java?


178

Các lý do chính đáng để cấm kế thừa trong Java là gì, ví dụ bằng cách sử dụng các lớp hoặc các lớp cuối cùng bằng cách sử dụng một hàm tạo không tham số riêng tư? Những lý do tốt để làm cho một phương pháp cuối cùng là gì?


1
Pedantry: Trình xây dựng mặc định là trình xây dựng mặc định được trình biên dịch Java tạo ra nếu bạn không viết rõ ràng một trong mã của mình. Bạn có nghĩa là không xây dựng đối số.
John Topley

Không phải đó là "hàm tạo mặc định ngầm" sao?
cretzel

2
"Bạn không phải cung cấp bất kỳ hàm tạo nào cho lớp của mình, nhưng bạn phải cẩn thận khi thực hiện việc này. Trình biên dịch sẽ tự động cung cấp một hàm tạo không có đối số, mặc định cho bất kỳ lớp nào không có hàm tạo." java.sun.com/docs/books/tutorial/java/javaOO/constructor.html - John Topley
John Topley

Câu trả lời:


184

Tài liệu tham khảo tốt nhất của bạn ở đây là Mục 19 của cuốn sách tuyệt vời "Java hiệu quả" của Joshua Bloch, được gọi là "Thiết kế và tài liệu để thừa kế hoặc nếu không thì cấm". (Đó là mục 17 trong phiên bản thứ hai và mục 15 trong phiên bản đầu tiên.) Bạn thực sự nên đọc nó, nhưng tôi sẽ tóm tắt.

Sự tương tác của các lớp được thừa kế với cha mẹ của chúng có thể gây ngạc nhiên và không thể lường trước được nếu tổ tiên không được thiết kế để được thừa kế từ đó.

Các lớp học do đó nên có hai loại:

  1. Các lớp được thiết kế để được mở rộng và có đủ tài liệu để mô tả cách thực hiện

  2. Các lớp được đánh dấu cuối cùng

Nếu bạn đang viết mã nội bộ hoàn toàn thì đây có thể là một chút quá mức cần thiết. Tuy nhiên, nỗ lực thêm liên quan đến việc thêm năm ký tự vào một tệp lớp là rất nhỏ. Nếu bạn chỉ viết cho tiêu chuẩn nội bộ thì một lập trình viên tương lai luôn có thể xóa 'bản cuối cùng' - bạn có thể nghĩ về nó như một lời cảnh báo rằng "lớp này không được thiết kế với tính kế thừa".


6
Theo kinh nghiệm của tôi, điều này không quá mức nếu có ai ngoài tôi sẽ sử dụng mã. Tôi chưa bao giờ hiểu tại sao Java có các phương thức overridable theo mặc định.
Eric Weilnau

9
Để hiểu một số Java hiệu quả, bạn cần hiểu thiết kế của Josh. Anh ta nói [một cái gì đó giống như] bạn nên luôn thiết kế các giao diện lớp như thể chúng là một API công khai. Tôi nghĩ rằng ý kiến ​​của đa số là cách tiếp cận này thường quá nặng nề và không linh hoạt. (YAGNI, v.v.)
Tom Hawtin - tackline

9
Câu trả lời tốt. (Tôi không thể cưỡng lại việc chỉ ra rằng đó là sáu ký tự, vì cũng có một khoảng
trắng

36
Xin lưu ý rằng việc tạo lớp cuối cùng có thể khiến việc kiểm tra trở nên khó khăn hơn (khó hơn là sơ khai hoặc giả)
notnoop

10
Nếu lớp cuối cùng đang ghi vào một giao diện, thì việc nhạo báng không phải là vấn đề.
Fred Haslam

26

Bạn có thể muốn làm cho một phương thức cuối cùng để các lớp ghi đè không thể thay đổi hành vi được tính trong các phương thức khác. Các phương thức được gọi trong constructor thường được khai báo là cuối cùng để bạn không gặp phải bất ngờ khó chịu nào khi tạo đối tượng.


Không hiểu câu trả lời này. Nếu bạn không phiền, bạn có thể giải thích ý của bạn bằng cách "ghi đè các lớp không thể thay đổi hành vi được tính trong các phương thức khác" không? Tôi đang hỏi trước tiên vì tôi không hiểu câu này và thứ hai là vì Netbeans đang khuyến nghị rằng một trong những chức năng của tôi mà tôi đang gọi từ nhà xây dựng, nên được final.
Nav

1
@Nav Nếu bạn thực hiện một phương thức cuối cùng, thì một lớp ghi đè sẽ không thể có phiên bản riêng của phương thức đó (hoặc đó sẽ là một lỗi biên dịch). Bằng cách đó, bạn biết rằng hành vi bạn thực hiện trong phương thức đó sẽ không thay đổi sau này khi người khác mở rộng lớp.
Bill Lizard

19

Một lý do để thực hiện một trận chung kết lớp sẽ là nếu bạn muốn buộc thành phần trên kế thừa. Điều này thường được mong muốn trong việc tránh khớp nối chặt chẽ giữa các lớp.


15

Có 3 trường hợp sử dụng mà bạn có thể đi cho các phương pháp cuối cùng.

  1. Để tránh lớp dẫn xuất ghi đè chức năng lớp cơ sở cụ thể.
  2. Điều này là cho mục đích bảo mật, trong đó lớp cơ sở đang cung cấp một số chức năng cốt lõi quan trọng của khung công tác mà lớp dẫn xuất không được yêu cầu thay đổi nó.
  3. Các phương thức cuối cùng nhanh hơn các phương thức cá thể, vì không sử dụng khái niệm bảng ảo cho các phương thức cuối cùng và riêng tư. Vì vậy, bất cứ nơi nào có khả năng, hãy thử sử dụng các phương pháp cuối cùng.

Mục đích để thực hiện một trận chung kết lớp:

Vì vậy, không có cơ thể có thể mở rộng các lớp và thay đổi hành vi của họ.

Ví dụ: Integpper lớp Integer là lớp cuối cùng. Nếu lớp đó không phải là cuối cùng, thì bất kỳ ai cũng có thể mở rộng Integer thành lớp của riêng mình và thay đổi hành vi cơ bản của lớp nguyên. Để tránh điều này, java đã tạo tất cả các lớp bao bọc như các lớp cuối cùng.


Không thuyết phục lắm với ví dụ của bạn. Nếu đó là trường hợp, tại sao không làm cho các phương thức và biến cuối cùng thay vì làm cho toàn bộ lớp cuối cùng. Bạn có thể vui lòng chỉnh sửa câu trả lời của bạn với các chi tiết.
Rajkiran

4
Ngay cả khi bạn thực hiện tất cả các biến và phương thức là cuối cùng, những người khác vẫn có thể kế thừa lớp của bạn và thêm chức năng bổ sung và thay đổi hành vi cơ bản của lớp.
dùng1923551

3
> Nếu lớp đó không phải là cuối cùng, thì bất kỳ ai cũng có thể mở rộng Integer thành lớp của riêng mình và thay đổi hành vi cơ bản của lớp nguyên. Giả sử điều này được cho phép và lớp mới này được gọi DerivedInteger, nó vẫn không thay đổi lớp Integer ban đầu và bất cứ ai sử dụng DerivedIntegerđều có nguy cơ của riêng họ, vì vậy tôi vẫn không hiểu tại sao đó là vấn đề.
Siddhartha


7

Kế thừa giống như một cái cưa - rất mạnh mẽ, nhưng khủng khiếp trong tay kẻ xấu. Hoặc bạn thiết kế một lớp được kế thừa (có thể hạn chế tính linh hoạt và mất nhiều thời gian hơn) hoặc bạn nên cấm nó.

Xem các mục Phiên bản Java 2 hiệu quả 16 và 17 hoặc bài đăng trên blog của tôi "Thuế thừa kế" .


Liên kết đến bài viết trên blog bị hỏng. Ngoài ra, bạn có nghĩ rằng bạn có thể giải thích thêm một chút về điều này?

@MichaelT: Đã sửa lỗi liên kết, cảm ơn - hãy theo dõi để biết thêm chi tiết :) (Tôi không có thời gian để thêm vào điều này ngay bây giờ, tôi sợ.)
Jon Skeet

@JonSkeet, Liên kết đến bài đăng trên blog bị hỏng.
Lucifer

@Kedarnath: Nó hoạt động với tôi ... nó đã bị hỏng trong một thời gian, nhưng giờ nó đang chỉ đến đúng trang, trên codeblog.jonskeet.uk ...
Jon Skeet

4

Hmmm ... tôi có thể nghĩ về hai điều:

Bạn có thể có một lớp liên quan đến các vấn đề bảo mật nhất định. Bằng cách phân lớp nó và cung cấp cho hệ thống của bạn phiên bản phân lớp của nó, kẻ tấn công có thể phá vỡ các hạn chế bảo mật. Ví dụ: ứng dụng của bạn có thể hỗ trợ các plugin và nếu một plugin chỉ có thể phân lớp các lớp liên quan đến bảo mật của bạn, thì ứng dụng đó có thể sử dụng thủ thuật này để bằng cách nào đó đưa một phiên bản được phân lớp của nó vào vị trí. Tuy nhiên, đây là điều mà Sun phải giải quyết liên quan đến các applet và những thứ tương tự, có thể không phải là một trường hợp thực tế như vậy.

Một thực tế hơn nhiều là để tránh một đối tượng trở nên đột biến. Ví dụ: vì Chuỗi là bất biến, mã của bạn có thể giữ các tham chiếu đến nó một cách an toàn

 String blah = someOtherString;

thay vì sao chép chuỗi trước. Tuy nhiên, nếu bạn có thể phân lớp Chuỗi, bạn có thể thêm các phương thức vào chuỗi cho phép sửa đổi giá trị chuỗi, bây giờ không có mã nào có thể tin được nữa rằng chuỗi sẽ giữ nguyên nếu nó chỉ sao chép chuỗi như trên, thay vào đó, nó phải sao chép chuỗi chuỗi.


2

Ngoài ra, nếu bạn đang viết một lớp nguồn đóng thương mại, bạn có thể không muốn mọi người có thể thay đổi chức năng, đặc biệt là nếu bạn cần hỗ trợ cho nó và mọi người đã ghi đè phương thức của bạn và phàn nàn rằng việc gọi nó mang lại kết quả bất ngờ.


2

Nếu bạn đánh dấu các lớp và phương thức là cuối cùng, bạn có thể nhận thấy mức tăng hiệu suất nhỏ, vì thời gian chạy không phải tìm phương thức lớp đúng để gọi cho một đối tượng nhất định. Các phương thức không phải là cuối cùng được đánh dấu là ảo để chúng có thể được mở rộng đúng cách nếu cần, các phương thức cuối cùng có thể được liên kết trực tiếp hoặc biên dịch nội tuyến trong lớp.


1
Tôi tin rằng đây là một huyền thoại, vì các máy ảo thế hệ hiện tại sẽ có thể tối ưu hóa và thu nhỏ lại khi cần thiết. Tôi nhớ đã nhìn thấy một số băng ghế dự bị này và phân loại nó như một huyền thoại.
Miguel Ping

1
Sự khác biệt trong việc tạo mã không phải là một huyền thoại, nhưng có lẽ hiệu suất đạt được là. Như tôi đã nói mức tăng nhỏ của nó, một trang web đã đề cập đến mức tăng hiệu suất 3,5%, một số cao hơn, trong hầu hết các trường hợp không đáng để sử dụng tất cả các phát xít trên mã của bạn.
ý vào

1
Nó không phải là một huyền thoại. Trong thực tế, trình biên dịch chỉ có thể nội tuyến V phương thức cuối cùng. Tôi không chắc chắn liệu có bất kỳ JIT nào "nội tuyến" hoạt động nghệ thuật không nhưng tôi nghi ngờ rằng nó có thể vì bạn có thể đang tải một lớp dẫn xuất sau này.
Uri

1
Microbenchmark == hạt muối. Điều đó nói rằng, sau khi khởi động chu kỳ 10B, các cuộc gọi trên 10B cho thấy về cơ bản không có sự khác biệt nào dưới Eclipse trên máy tính xách tay của tôi. Hai phương thức trả về String, trận chung kết là 6,9643us, không kết thúc là 6,9641us. Đồng bằng đó là tiếng ồn nền, IMHO.
joel.neely

1

Để ngăn chặn mọi người làm những việc có thể gây nhầm lẫn cho bản thân và những người khác. Hãy tưởng tượng một thư viện vật lý nơi bạn có một số hằng số hoặc phép tính xác định. Nếu không sử dụng từ khóa cuối cùng, ai đó có thể xuất hiện và xác định lại các phép tính hoặc hằng số cơ bản KHÔNG BAO GIỜ thay đổi.


2
Có điều gì đó tôi không hiểu về lập luận đó - nếu ai đó đã thay đổi những tính toán / hằng số không nên thay đổi - không có bất kỳ thất bại nào do lỗi 100% đó? Nói cách khác, làm thế nào để thay đổi có lợi gì?
matt b

Vâng, về mặt kỹ thuật là "lỗi" của họ nhưng hãy tưởng tượng người khác sử dụng thư viện không được biết về các thay đổi, có thể dẫn đến nhầm lẫn. Tôi đã luôn nghĩ rằng đó là lý do nhiều lớp Java Base được đánh dấu cuối cùng, để ngăn mọi người hình thành thay đổi chức năng cơ bản.
Anson Smith

@matt b - Bạn dường như đang giả định rằng nhà phát triển thực hiện thay đổi đã làm như vậy một cách có ý thức hoặc họ sẽ quan tâm rằng đó là lỗi của họ. Nếu ai đó ngoài tôi sẽ sử dụng mã của tôi, tôi sẽ đánh dấu nó cuối cùng trừ khi nó có nghĩa là phải thay đổi.
Eric Weilnau

Đây thực sự là một cách sử dụng khác nhau của 'trận chung kết' so với câu hỏi được hỏi về.
DJClayworth

Câu trả lời này dường như nhầm lẫn giữa tính kế thừa và tính biến đổi của các trường riêng lẻ với khả năng viết một lớp con. Bạn có thể đánh dấu các trường và phương thức riêng lẻ là cuối cùng (ngăn chặn việc xác định lại chúng) mà không cấm kế thừa.
joel.neely

0

Bạn muốn làm cho một phương thức cuối cùng để các lớp ghi đè không thay đổi hành vi của nó. Khi bạn muốn có thể thay đổi hành vi, hãy đặt phương thức ở chế độ công khai. Khi bạn ghi đè một phương thức công khai, nó có thể được thay đổi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.