Điểm của lớp cuối cùng của Wikipedia trong Java là gì?


569

Tôi đang đọc một cuốn sách về Java và nó nói rằng bạn có thể khai báo cả lớp là final. Tôi không thể nghĩ bất cứ điều gì tôi sử dụng này.

Tôi chỉ mới lập trình và tôi tự hỏi liệu các lập trình viên có thực sự sử dụng điều này trên các chương trình của họ không . Nếu họ làm, khi nào họ sử dụng nó để tôi có thể hiểu nó tốt hơn và biết khi nào nên sử dụng nó.

Nếu Java là hướng đối tượng và bạn khai báo một lớp final, thì nó không dừng ý tưởng về lớp có các đặc điểm của các đối tượng?

Câu trả lời:


531

Trước hết, tôi khuyên bạn nên viết bài này: Java: Khi nào nên tạo một lớp cuối cùng


Nếu họ làm, khi nào họ sử dụng nó để tôi có thể hiểu nó tốt hơn và biết khi nào nên sử dụng nó.

Một finallớp chỉ đơn giản là một lớp không thể mở rộng .

(Điều đó không có nghĩa là tất cả các tham chiếu đến các đối tượng của lớp sẽ hoạt động như thể chúng được khai báo là final.)

Khi nào hữu ích để khai báo một lớp là cuối cùng được bao phủ trong các câu trả lời của câu hỏi này:

Nếu Java là hướng đối tượng và bạn khai báo một lớp final, thì nó không dừng ý tưởng về lớp có các đặc điểm của các đối tượng?

Trong một số ý nghĩa có.

Bằng cách đánh dấu một lớp là cuối cùng, bạn vô hiệu hóa một tính năng mạnh mẽ và linh hoạt của ngôn ngữ cho phần đó của mã. Một số lớp Tuy nhiên, không nên (và trong một số trường hợp có thể không) được thiết kế để tận subclassing vào tài khoản trong một cách tốt. Trong những trường hợp này, việc đánh dấu lớp là cuối cùng, mặc dù nó giới hạn OOP. (Tuy nhiên, hãy nhớ rằng một lớp cuối cùng vẫn có thể mở rộng một lớp không phải là cuối cùng.)


39
Để thêm vào câu trả lời, một trong những nguyên tắc của Java hiệu quả là ưu tiên thành phần hơn sự kế thừa. Việc sử dụng từ khóa cuối cùng cũng giúp thực thi nguyên tắc đó.
Riggy

9
"Bạn làm điều đó chủ yếu vì lý do hiệu quả và bảo mật." Tôi nghe nhận xét này khá thường xuyên (ngay cả Wikipedia cũng nói điều này) nhưng tôi vẫn không hiểu lý do đằng sau lập luận này. Có ai đó quan tâm để giải thích làm thế nào, giả sử, một java.lang.String không phải là cuối cùng sẽ kết thúc hoặc không hiệu quả hoặc không an toàn?
MRA

27
@MRA Nếu tôi tạo một phương thức chấp nhận Chuỗi làm tham số, tôi cho rằng đó là bất biến, bởi vì Chuỗi là. Do đó, tôi biết rằng tôi có thể gọi bất kỳ phương thức nào trên đối tượng String một cách an toàn và không thay đổi Chuỗi đã truyền. Nếu tôi mở rộng Chuỗi và thay đổi triển khai chuỗi con để thay đổi Chuỗi thực tế, thì đối tượng Chuỗi mà bạn mong đợi là bất biến không còn là bất biến.
Cruncher

1
@Sortofabeginner Và ngay khi bạn nói rằng bạn muốn tất cả các phương thức và trường Chuỗi là cuối cùng, để bạn có thể tạo một số lớp với chức năng bổ sung ... Tại thời điểm đó, bạn cũng có thể tạo một lớp có một chuỗi và tạo các phương thức hoạt động trên chuỗi đó.
Cruncher

1
@Shay cuối cùng (trong số những thứ khác) được sử dụng để làm cho một đối tượng bất biến, vì vậy tôi sẽ không nói rằng họ không có gì để làm với nhau. Xem tại đây docs.oracle.com/javase/tutorial/essential/concurrency/,
Celeritas

184

Trong Java, các mục với công cụ finalsửa đổi không thể thay đổi!

Điều này bao gồm các lớp cuối cùng, các biến cuối cùng và các phương thức cuối cùng:

  • Một lớp cuối cùng không thể được mở rộng bởi bất kỳ lớp nào khác
  • Một biến cuối cùng không thể được gán lại giá trị khác
  • Một phương thức cuối cùng không thể được ghi đè

40
Câu hỏi thực tế là tại sao , không phải cái gì .
Francesco Menzani

8
Câu lệnh "Trong Java, các mục với công cụ finalsửa đổi không thể thay đổi!", Quá phân loại và trên thực tế, không hoàn toàn chính xác. Như Grady Booch nói, "Một đối tượng có trạng thái, hành vi và bản sắc". Mặc dù chúng tôi không thể thay đổi danh tính của một đối tượng một khi tham chiếu của nó được đánh dấu là cuối cùng, chúng tôi có cơ hội thay đổi trạng thái của nó bằng cách gán các giá trị mới cho các finaltrường không của nó (tất nhiên, tất cả đều có chúng.) lập kế hoạch để có được Chứng chỉ Java của Oracle (chẳng hạn như 1Z0-808, v.v.) nên ghi nhớ điều này vì có thể có câu hỏi về khía cạnh này trong bài kiểm tra ...
Igor Soudakevitch

33

Một kịch bản trong đó cuối cùng là quan trọng, khi bạn muốn ngăn chặn sự kế thừa của một lớp, vì lý do bảo mật. Điều này cho phép bạn đảm bảo rằng mã bạn đang chạy không thể bị ghi đè bởi ai đó.

Một kịch bản khác là để tối ưu hóa: Tôi dường như nhớ rằng trình biên dịch Java sẽ thực hiện một số lệnh gọi hàm từ các lớp cuối cùng. Vì vậy, nếu bạn gọi a.x()và a được khai báo final, chúng tôi biết tại thời điểm biên dịch mã sẽ là gì và có thể nội tuyến vào chức năng gọi. Tôi không biết liệu điều này có thực sự được thực hiện hay không, nhưng cuối cùng thì đó là một khả năng.


7
Việc đặt nội tuyến thường chỉ được thực hiện bởi trình biên dịch chỉ trong thời gian chạy. Nó cũng hoạt động mà không cần cuối cùng, nhưng trình biên dịch JIT có thêm một chút việc phải làm để chắc chắn rằng không có các lớp mở rộng (hoặc các lớp mở rộng này không chạm vào phương thức này).
Paŭlo Ebermann

Một bài viết hay về vấn đề nội tuyến và tối ưu hóa có thể được tìm thấy ở đây: lemire.me/blog/archives/2014/12/17/ Kẻ
Josh Hemann 18/12/14

24

Ví dụ tốt nhất là

chuỗi lớp cuối cùng công khai

đó là một lớp bất biến và không thể mở rộng. Tất nhiên, có nhiều thứ hơn là làm cho lớp cuối cùng trở nên bất biến.


Hehe, đôi khi nó bảo vệ các nhà phát triển Rube Goldergian khỏi chính họ.
Zoidberg

16

Đọc có liên quan: Nguyên tắc đóng mở của Bob Martin.

Trích dẫn chính:

Các thực thể phần mềm (Lớp, Mô-đun, Hàm, v.v.) nên được mở để Gia hạn, nhưng đóng để Sửa đổi.

Các finaltừ khóa là phương tiện để thực thi điều này trong Java, cho dù nó được sử dụng trên các phương pháp hoặc trên lớp.


6
@Sean: Không khai báo nó finallàm cho lớp đóng để mở rộng chứ không phải mở? Hay tôi đang dùng nó theo đúng nghĩa đen?
Goran Jovic ngày

4
@Goran trên toàn cầu áp dụng cuối cùng, vâng. Điều quan trọng là áp dụng có chọn lọc vào cuối cùng ở những nơi bạn không muốn sửa đổi (và tất nhiên là cung cấp các móc nối tốt để gia hạn)
Sean Patrick Floyd

26
Trong OCP, "sửa đổi" đề cập đến sửa đổi mã nguồn và "phần mở rộng" đề cập đến kế thừa thực hiện. Do đó, việc sử dụng finalkhai báo lớp / phương thức sẽ không có ý nghĩa nếu bạn muốn đóng mã thực thi để sửa đổi nhưng mở để mở rộng bằng cách kế thừa.
Rogério

1
@Rogerio Tôi đã mượn tài liệu tham khảo (và diễn giải) từ Spring Framework Reference (MVC) . IMHO điều này có ý nghĩa hơn nhiều so với phiên bản gốc.
Sean Patrick Floyd

Gia hạn là chết. Vô ích. Tàn phá. Bị phá hủy. Tôi không quan tâm đến OCP. Không bao giờ có một cái cớ để mở rộng một lớp học.
Josh Woodcock

15

Nếu bạn tưởng tượng hệ thống phân cấp lớp như một cái cây (như trong Java), các lớp trừu tượng chỉ có thể là các nhánh và các lớp cuối cùng là các lớp chỉ có thể là các lá. Các lớp rơi vào cả hai loại này có thể là cả nhánh và lá.

Không có vi phạm các nguyên tắc OO ở đây, cuối cùng chỉ đơn giản là cung cấp một đối xứng đẹp.

Trong thực tế, bạn muốn sử dụng cuối cùng nếu bạn muốn các đối tượng của mình là bất biến hoặc nếu bạn đang viết API, để báo hiệu cho người dùng API rằng lớp này không nhằm mục đích mở rộng.


13

Từ khóa finalcó nghĩa là một cái gì đó là cuối cùng và không cần phải sửa đổi theo bất kỳ cách nào. Nếu một lớp nếu được đánh dấu finalthì nó không thể được mở rộng hoặc phân lớp. Nhưng câu hỏi là tại sao chúng ta đánh dấu một lớp học final? IMO có nhiều lý do:

  1. Tiêu chuẩn hóa: Một số lớp thực hiện các chức năng tiêu chuẩn và chúng không có nghĩa là phải sửa đổi, ví dụ như các lớp thực hiện các chức năng khác nhau liên quan đến thao tác chuỗi hoặc hàm toán học, v.v.
  2. Lý do bảo mật : Đôi khi chúng tôi viết các lớp thực hiện các chức năng liên quan đến xác thực và mật khẩu khác nhau và chúng tôi không muốn chúng bị thay đổi bởi bất kỳ ai khác.

Tôi đã nghe nói rằng lớp đánh dấu finalcải thiện hiệu quả nhưng thật lòng tôi không thể thấy lập luận này mang nhiều trọng lượng.

Nếu Java là hướng đối tượng và bạn khai báo một lớp cuối cùng, thì nó không dừng ý tưởng về lớp có các đặc điểm của các đối tượng?

Có lẽ có, nhưng đôi khi đó là mục đích dự định. Đôi khi chúng tôi làm điều đó để đạt được lợi ích lớn hơn của bảo mật, vv bằng cách hy sinh khả năng của lớp này được mở rộng. Nhưng một lớp cuối cùng vẫn có thể mở rộng một lớp nếu nó cần.

Mặt khác, chúng ta nên ưu tiên thành phần hơn kế thừafinaltừ khóa thực sự giúp thực thi nguyên tắc này.


6

Hãy cẩn thận khi bạn thực hiện một lớp "cuối cùng". Bởi vì nếu bạn muốn viết một bài kiểm tra đơn vị cho một lớp cuối cùng, bạn không thể phân lớp lớp cuối cùng này để sử dụng kỹ thuật phá vỡ phụ thuộc "Phương pháp phân lớp và ghi đè" được mô tả trong cuốn sách "Làm việc hiệu quả với mã kế thừa" của Michael C. Feathers . Trong cuốn sách này, Feathers nói: "Nghiêm túc mà nói, rất dễ tin rằng niêm phong và cuối cùng là một sai lầm sai lầm, rằng chúng không bao giờ nên được thêm vào ngôn ngữ lập trình. Nhưng lỗi thực sự nằm ở chúng ta. các thư viện nằm ngoài tầm kiểm soát của chúng tôi, chúng tôi chỉ đang yêu cầu sự cố. "


6

final class có thể tránh phá vỡ API công khai khi bạn thêm phương thức mới

Giả sử rằng trên phiên bản 1 của Baselớp bạn làm:

public class Base {}

và một khách hàng làm:

class Derived extends Base {
    public int method() { return 1; }
}

Sau đó, nếu trong phiên bản 2, bạn muốn thêm một methodphương thức vào Base:

class Base {
    public String method() { return null; }
}

nó sẽ phá mã khách hàng

Nếu chúng ta đã sử dụng final class Basethay thế, máy khách sẽ không thể kế thừa và việc bổ sung phương thức sẽ không phá vỡ API.


5

Nếu lớp được đánh dấu final, điều đó có nghĩa là cấu trúc của lớp không thể được sửa đổi bởi bất kỳ thứ gì bên ngoài. Trường hợp dễ thấy nhất là khi bạn đang thực hiện kế thừa đa hình truyền thống, về cơ bản class B extends Asẽ không hoạt động. Về cơ bản, đây là cách để bảo vệ một số phần trong mã của bạn (trong phạm vi) .

Để làm rõ, việc đánh dấu lớp finalkhông đánh dấu các trường của nó finalvà vì thế không bảo vệ các thuộc tính đối tượng mà thay vào đó là cấu trúc lớp thực tế.


1
Các thuộc tính đối tượng có nghĩa là gì? Có nghĩa là tôi có thể sửa đổi biến thành viên của lớp nếu lớp được khai báo cuối cùng? Vì vậy, mục đích duy nhất của lớp cuối cùng là ngăn chặn sự kế thừa.
Adam Lyu

5

ĐỂ ĐỊA CHỈ CÁC VẤN ĐỀ LỚP CUỐI CÙNG:

Có hai cách để làm cho một lớp cuối cùng. Đầu tiên là sử dụng từ khóa cuối cùng trong khai báo lớp:

public final class SomeClass {
  //  . . . Class contents
}

Cách thứ hai để thực hiện một trận chung kết lớp là khai báo tất cả các hàm tạo của nó là riêng tư:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

Đánh dấu nó cuối cùng giúp bạn tránh khỏi rắc rối nếu phát hiện ra rằng nó thực sự là một trận chung kết, để chứng minh cái nhìn về lớp Test này. nhìn công khai từ cái nhìn đầu tiên.

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

Thật không may, vì hàm tạo duy nhất của lớp là riêng tư, nên không thể mở rộng lớp này. Trong trường hợp của lớp Test, không có lý do gì mà lớp phải là cuối cùng. Lớp Test là một ví dụ tốt về cách các lớp cuối cùng ẩn có thể gây ra vấn đề.

Vì vậy, bạn nên đánh dấu nó là cuối cùng khi bạn ngầm hoàn thành một lớp cuối cùng bằng cách đặt nó ở chế độ riêng tư.


4

Một lớp cuối cùng là một lớp không thể mở rộng. Ngoài ra các phương thức có thể được khai báo là cuối cùng để chỉ ra rằng không thể bị ghi đè bởi các lớp con.

Ngăn chặn lớp được phân lớp có thể đặc biệt hữu ích nếu bạn viết API hoặc thư viện và muốn tránh bị mở rộng để thay đổi hành vi cơ sở.


4

Một lợi thế của việc giữ một lớp học là cuối cùng: -

Lớp chuỗi được giữ cuối cùng để không ai có thể ghi đè các phương thức của nó và thay đổi chức năng. ví dụ, không ai có thể thay đổi chức năng của phương thức length (). Nó sẽ luôn trả về độ dài của một chuỗi.

Nhà phát triển của lớp này muốn không ai thay đổi chức năng của lớp này, vì vậy anh ta giữ nó là cuối cùng.


3

Có, đôi khi bạn có thể muốn điều này mặc dù, vì lý do bảo mật hoặc tốc độ. Nó cũng được thực hiện trong C ++. Nó có thể không được áp dụng cho các chương trình, nhưng nhiều hơn như vậy cho các khuôn khổ. http://www.glenmccl.com/perfj_025.htm


3

Trong java sử dụng từ khóa cuối cùng cho các dịp dưới đây.

  1. Biến cuối cùng
  2. Phương pháp cuối cùng
  3. Lớp cuối cùng

Trong java các biến cuối cùng không thể gán lại, các lớp cuối cùng không thể mở rộng và các phương thức cuối cùng không thể ghi đè.


1

Lớp cuối cùng không thể được mở rộng. Vì vậy, nếu bạn muốn một lớp hành xử theo một cách nhất định và không ai đó ghi đè các phương thức (với mã độc hại và hiệu quả hơn), bạn có thể khai báo cả lớp là phương thức cuối cùng hoặc cụ thể mà bạn không muốn đã thay đổi

Vì việc khai báo một lớp không ngăn chặn một lớp được khởi tạo, điều đó không có nghĩa là nó sẽ ngăn lớp đó có các đặc điểm của một đối tượng. Chỉ là bạn sẽ phải tuân theo các phương thức giống như cách chúng được khai báo trong lớp.


1

nghĩ về FINAL như là "Kết thúc của dòng" - anh chàng đó không thể sinh con nữa. Vì vậy, khi bạn nhìn thấy nó theo cách này, có rất nhiều tình huống trong thế giới thực mà bạn sẽ bắt gặp, điều đó đòi hỏi bạn phải gắn cờ 'điểm cuối của dòng' vào lớp. Đó là Thiết kế hướng miền - nếu miền của bạn yêu cầu ENTITY (lớp) nhất định không thể tạo các lớp con, thì hãy đánh dấu nó là FINAL.

Tôi nên lưu ý rằng không có gì ngăn bạn kế thừa một lớp "nên được gắn thẻ là cuối cùng". Nhưng điều đó thường được phân loại là "lạm dụng quyền thừa kế" và được thực hiện bởi vì hầu hết bạn thường muốn kế thừa một số chức năng từ lớp cơ sở trong lớp của bạn.

Cách tiếp cận tốt nhất là xem xét tên miền và để nó ra lệnh cho các quyết định thiết kế của bạn.


1

Như đã nói ở trên, nếu bạn muốn không ai có thể thay đổi chức năng của phương thức thì bạn có thể khai báo nó là cuối cùng.

Ví dụ: Đường dẫn tệp máy chủ ứng dụng để tải xuống / tải lên, tách chuỗi dựa trên offset, các phương thức như vậy bạn có thể khai báo nó là Final để các hàm phương thức này sẽ không bị thay đổi. Và nếu bạn muốn các phương thức cuối cùng như vậy trong một lớp riêng biệt, thì hãy định nghĩa lớp đó là lớp Cuối cùng. Vì vậy, lớp Final sẽ có tất cả các phương thức cuối cùng, trong đó phương thức Final có thể được khai báo và định nghĩa trong lớp không phải là cuối cùng.



1

Giả sử bạn có một Employeelớp có phương thức greet. Khi greetphương thức được gọi nó chỉ đơn giản là in Hello everyone!. Vì vậy, đó là hành vi dự kiến của greetphương pháp

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Bây giờ, hãy để GrumpyEmployeelớp con Employeegreetphương thức ghi đè như dưới đây.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Bây giờ trong đoạn mã dưới đây có một cái nhìn về sayHellophương pháp. Nó lấy Employeeví dụ như một tham số và gọi phương thức chào với hy vọng nó sẽ nói Hello everyone!Nhưng những gì chúng ta nhận được là Get lost!. Sự thay đổi hành vi này là doEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Tình huống này có thể tránh được nếu Employeelớp học được thực hiện final. Chỉ cần tưởng tượng số lượng hỗn loạn mà một lập trình viên táo bạo có thể gây ra nếu StringClass không được tuyên bố là final.


1

Lớp cuối cùng không thể được mở rộng hơn nữa. Nếu chúng ta không cần tạo một lớp kế thừa trong java, chúng ta có thể sử dụng phương pháp này.

Nếu chúng ta chỉ cần làm cho các phương thức cụ thể trong một lớp không bị ghi đè, chúng ta chỉ có thể đặt từ khóa cuối cùng trước chúng. Ở đó lớp vẫn được kế thừa.


-1

Định hướng đối tượng không phải là về thừa kế, mà là về đóng gói. Và thừa kế phá vỡ đóng gói.

Tuyên bố một trận chung kết lớp có ý nghĩa hoàn hảo trong rất nhiều trường hợp. Bất kỳ đối tượng nào đại diện cho giá trị của người Viking như màu sắc hoặc số tiền có thể là cuối cùng. Họ đứng một mình.

Nếu bạn đang viết thư viện, hãy làm cho các lớp của bạn cuối cùng trừ khi bạn rõ ràng thụt lề chúng để được dẫn xuất. Mặt khác, mọi người có thể lấy được các lớp của bạn và ghi đè các phương thức, phá vỡ các giả định / bất biến của bạn. Điều này có thể có ý nghĩa bảo mật là tốt.

Joshua Bloch trong Lời hiệu quả Java Java khuyên bạn nên thiết kế rõ ràng cho việc thừa kế hoặc cấm nó và ông lưu ý rằng thiết kế để kế thừa không phải là dễ dàng.

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.