Lý lịch
Có ba nơi mà trận chung kết có thể xuất hiện trong Java:
Làm cho một lớp cuối cùng ngăn chặn tất cả các lớp con của lớp. Tạo một phương thức cuối cùng ngăn các lớp con của phương thức ghi đè lên nó. Làm cho một trường cuối cùng ngăn chặn nó được thay đổi sau này.
Quan niệm sai lầm
Có những tối ưu hóa xảy ra xung quanh các phương pháp và trường cuối cùng.
Phương pháp cuối cùng giúp HotSpot dễ dàng tối ưu hóa hơn thông qua nội tuyến. Tuy nhiên, HotSpot thực hiện điều này ngay cả khi phương thức không phải là cuối cùng vì nó hoạt động với giả định rằng nó đã không bị ghi đè cho đến khi được chứng minh khác đi. Thông tin thêm về điều này trên SO
Một biến cuối cùng có thể được tối ưu hóa mạnh mẽ, và nhiều hơn về điều đó có thể được đọc trong phần JLS 17.5.3 .
Tuy nhiên, với sự hiểu biết đó, người ta nên biết rằng cả hai tối ưu hóa này đều không phải là về một lớp cuối cùng. Không có hiệu suất đạt được bằng cách làm cho một lớp cuối cùng.
Khía cạnh cuối cùng của một lớp cũng không liên quan gì đến tính bất biến. Người ta có thể có một lớp bất biến (như BigInteger ) không phải là cuối cùng, hoặc một lớp có thể thay đổi và cuối cùng (như StringBuilder ). Quyết định về việc nếu một lớp học nên là cuối cùng là một câu hỏi về thiết kế.
Thiết kế cuối cùng
Chuỗi là một trong những loại dữ liệu được sử dụng nhiều nhất. Chúng được tìm thấy như là chìa khóa của bản đồ, chúng lưu trữ tên người dùng và mật khẩu, chúng là những gì bạn đọc từ bàn phím hoặc trường trên trang web. Dây ở khắp mọi nơi .
Bản đồ
Điều đầu tiên cần xem xét về những gì sẽ xảy ra nếu bạn có thể phân lớp Chuỗi là nhận ra rằng ai đó có thể xây dựng một lớp Chuỗi có thể thay đổi sẽ xuất hiện thành Chuỗi. Điều này sẽ làm rối bản đồ ở khắp mọi nơi.
Hãy xem xét mã giả thuyết này:
Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");
t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);
one.prepend("z");
Đây là một vấn đề với việc sử dụng khóa có thể thay đổi nói chung với Bản đồ, nhưng điều mà tôi đang cố gắng đạt được đó là đột nhiên một số điều về Bản đồ bị phá vỡ. Entry không còn ở đúng vị trí trong bản đồ. Trong HashMap, giá trị băm đã (nên có) thay đổi và do đó, nó không còn ở mục bên phải. Trong TreeMap, cây hiện bị hỏng do một trong các nút nằm ở phía sai.
Vì việc sử dụng Chuỗi cho các khóa này rất phổ biến, nên ngăn chặn hành vi này bằng cách tạo Chuỗi cuối cùng.
Bạn có thể quan tâm đến việc đọc Tại sao String là bất biến trong Java? để biết thêm về bản chất bất biến của Chuỗi.
Chuỗi bất chính
Có một số tùy chọn bất chính khác nhau cho Chuỗi. Hãy xem xét nếu tôi tạo một Chuỗi luôn trả về đúng khi được gọi bằng ... và chuyển nó vào kiểm tra mật khẩu? Hoặc làm cho nó để các bài tập cho MyString sẽ gửi một bản sao của Chuỗi đến một số địa chỉ email?
Đây là những khả năng rất thực tế khi bạn có khả năng phân lớp Chuỗi.
Tối ưu hóa chuỗi Java.lang
Trong khi trước khi tôi đề cập rằng trận chung kết không làm cho String nhanh hơn. Tuy nhiên, lớp String (và các lớp khác trong java.lang
) sử dụng thường xuyên việc bảo vệ các trường và phương thức mức gói để cho phép các java.lang
lớp khác có thể sửa đổi nội bộ thay vì luôn luôn thông qua API công khai cho Chuỗi. Các hàm như getChars không có kiểm tra phạm vi hoặc lastIndexOf được sử dụng bởi StringBuffer hoặc hàm tạo chia sẻ mảng bên dưới (lưu ý rằng đó là một điều Java 6 đã bị thay đổi do vấn đề bộ nhớ).
Nếu ai đó tạo một lớp con của Chuỗi, nó sẽ không thể chia sẻ những tối ưu hóa đó (trừ khi đó là một phần của java.lang
quá, nhưng đó là một gói kín ).
Thật khó để thiết kế một cái gì đó để mở rộng
Thiết kế một cái gì đó để có thể mở rộng là khó khăn . Điều đó có nghĩa là bạn phải tiết lộ các phần trong nội bộ của mình để lấy thứ khác để có thể sửa đổi.
Một chuỗi có thể mở rộng không thể bị rò rỉ bộ nhớ . Những phần của nó sẽ cần phải được tiếp xúc với các lớp con và thay đổi mã đó có nghĩa là các lớp con sẽ bị phá vỡ.
Java tự hào về khả năng tương thích ngược và bằng cách mở các lớp cốt lõi để mở rộng, người ta sẽ mất một số khả năng đó để sửa chữa mọi thứ trong khi vẫn duy trì khả năng tính toán với các lớp con bên thứ ba.
Checkstyle có một quy tắc mà nó thi hành (điều đó thực sự làm tôi thất vọng khi viết mã nội bộ) được gọi là "DesignForExtension", điều này thực thi rằng mọi lớp đều:
- trừu tượng
- Sau cùng
- Thực hiện trống
Các lý do cho là:
Phong cách thiết kế API này bảo vệ các siêu lớp khỏi bị phá vỡ bởi các lớp con. Nhược điểm là các lớp con bị hạn chế về tính linh hoạt, đặc biệt là chúng không thể ngăn chặn việc thực thi mã trong siêu lớp, nhưng điều đó cũng có nghĩa là các lớp con không thể làm hỏng trạng thái của siêu lớp bằng cách quên gọi phương thức siêu.
Cho phép các lớp thực hiện được mở rộng có nghĩa là các lớp con có thể có thể làm hỏng trạng thái của lớp mà nó dựa trên và làm cho nó đảm bảo khác nhau mà siêu lớp đưa ra là không hợp lệ. Đối với một thứ phức tạp như String, gần như chắc chắn rằng việc thay đổi một phần của nó sẽ phá vỡ thứ gì đó.
Nhà phát triển
Đó là một phần của việc trở thành một nhà phát triển. Xem xét khả năng mỗi nhà phát triển sẽ tạo ra lớp con Chuỗi riêng của họ với bộ sưu tập của riêng mình utils trong đó. Nhưng bây giờ các lớp con này không thể được tự do gán lại cho nhau.
WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.
Cách này dẫn đến sự điên rồ. Truyền tới Chuỗi khắp mọi nơi và kiểm tra xem Chuỗi có phải là một thể hiện của lớp Chuỗi của bạn hay không, tạo một Chuỗi mới dựa trên chuỗi đó và ... chỉ, không. Đừng.
Tôi chắc rằng bạn có thể viết một lớp String tốt ... nhưng để lại văn bản triển khai nhiều chuỗi cho những người điên người viết C ++ và phải đối phó với std::string
và char*
và một cái gì đó từ tăng và SString , và tất cả các phần còn lại .
Chuỗi ma thuật Java
Có một số điều kỳ diệu mà Java làm với String. Những điều này giúp lập trình viên dễ dàng xử lý hơn nhưng lại đưa ra một số điểm không nhất quán trong ngôn ngữ. Việc cho phép các lớp con trên String sẽ có một số suy nghĩ rất quan trọng về cách đối phó với những điều kỳ diệu này:
Chuỗi ký tự (JLS 3.10.5 )
Có mã cho phép một người làm:
String foo = "foo";
Điều này không được nhầm lẫn với quyền anh của các loại số như Integer. Bạn không thể làm 1.toString()
, nhưng bạn có thể làm "foo".concat(bar)
.
Các +
nhà điều hành (JLS 15.18.1 )
Không có loại tham chiếu nào khác trong Java cho phép một toán tử được sử dụng trên nó. Chuỗi là đặc biệt. Toán tử nối chuỗi cũng hoạt động ở cấp trình biên dịch để "foo" + "bar"
trở thành "foobar"
khi nó được biên dịch thay vì trong thời gian chạy.
Chuyển đổi chuỗi (JLS 5.1.11 )
Tất cả các đối tượng có thể được chuyển đổi thành Chuỗi chỉ bằng cách sử dụng chúng trong ngữ cảnh Chuỗi.
Chuỗi thực tập ( JavaDoc )
Lớp String có quyền truy cập vào nhóm Chuỗi cho phép nó có các biểu diễn chính tắc của đối tượng được điền ở kiểu biên dịch với chuỗi ký tự Chuỗi.
Việc cho phép một lớp con của Chuỗi có nghĩa là các bit này với Chuỗi giúp việc lập trình dễ dàng hơn sẽ trở nên rất khó hoặc không thể thực hiện khi các loại Chuỗi khác có thể.