Tại sao cuối cùng, không được phép trong các phương thức giao diện Java 8?


335

Một trong những tính năng hữu ích nhất của Java 8 là các defaultphương thức mới trên các giao diện. Về cơ bản có hai lý do (có thể có những lý do khác) tại sao chúng được giới thiệu:

Từ quan điểm của người thiết kế API, tôi muốn có thể sử dụng các công cụ sửa đổi khác trên các phương thức giao diện, vd final. Điều này sẽ hữu ích khi thêm các phương thức tiện lợi, ngăn chặn ghi đè "tình cờ" trong việc thực hiện các lớp:

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

Trên đây là thực tế phổ biến nếu Senderlà một lớp:

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

Bây giờ, defaultfinalrõ ràng là mâu thuẫn với các từ khóa, nhưng bản thân từ khóa mặc định sẽ không được yêu cầu nghiêm ngặt , vì vậy tôi cho rằng mâu thuẫn này là có chủ ý, để phản ánh sự khác biệt tinh tế giữa "phương thức lớp với cơ thể" (chỉ phương thức) và "giao diện" phương thức với cơ thể " (phương thức mặc định), nghĩa là sự khác biệt mà tôi chưa hiểu.

Tại một số thời điểm, hỗ trợ cho các sửa đổi như staticfinaltrên các phương thức giao diện chưa được khám phá đầy đủ, trích dẫn Brian Goetz :

Phần khác là chúng ta sẽ đi bao xa để hỗ trợ các công cụ xây dựng lớp trong các giao diện, chẳng hạn như phương thức cuối cùng, phương thức riêng tư, phương thức được bảo vệ, phương thức tĩnh, v.v. Câu trả lời là: chúng ta chưa biết

Kể từ thời điểm đó vào cuối năm 2011, rõ ràng, hỗ trợ cho staticcác phương thức trong giao diện đã được thêm vào. Rõ ràng, điều này đã thêm rất nhiều giá trị cho chính các thư viện JDK, chẳng hạn như với Comparator.comparing().

Câu hỏi:

Lý do final(và cũng static final) không bao giờ được đưa vào giao diện Java 8 là gì?


10
Tôi xin lỗi vì là một tấm chăn ướt, nhưng cách duy nhất mà câu hỏi thể hiện trong tiêu đề sẽ được trả lời trong các điều khoản của SO là thông qua trích dẫn từ Brian Goetz hoặc nhóm Chuyên gia JSR. Tôi hiểu rằng BG đã yêu cầu một cuộc thảo luận công khai, nhưng đó chính xác là những gì trái với các điều khoản của SO, vì đó là 'chủ yếu dựa trên quan điểm'. Dường như với tôi rằng các trách nhiệm đang được dồn nén ở đây. Đó là công việc của Nhóm chuyên gia và Quy trình cộng đồng Java rộng hơn, để kích thích thảo luận và đưa ra các lý do. Không phải của SO. Vì vậy, tôi đang bỏ phiếu để đóng 'chủ yếu dựa trên ý kiến'.
Hầu tước Lorne

2
Như chúng ta đã biết, finalngăn không cho một phương thức bị ghi đè và xem cách bạn PHẢI ghi đè các phương thức được kế thừa từ các giao diện, tôi không hiểu tại sao nó lại có ý nghĩa để làm cho nó cuối cùng. Trừ khi nó là để biểu thị rằng phương thức này là SAU cuối cùng ghi đè lên nó một lần .. Trong trường hợp đó, có thể có khó khăn không? Nếu tôi không hiểu điều này đúng, xin vui lòng cho tôi kmow. Có vẻ thú vị
Vince Emigh

22
@EJP: "Nếu tôi có thể làm điều đó, thì bạn cũng có thể, và trả lời câu hỏi của riêng bạn, do đó sẽ không cần phải hỏi." Điều đó sẽ áp dụng cho khá nhiều câu hỏi trên diễn đàn này, phải không? Tôi luôn có thể tự Google một số chủ đề trong 5 giờ và sau đó học một cái gì đó mà mọi người khác phải học theo cách khó khăn không kém. Hoặc chúng tôi chờ đợi một vài phút để ai đó đưa ra câu trả lời thậm chí tốt hơn rằng mọi người trong tương lai (bao gồm 12 upvote và 8 sao cho đến nay) có thể kiếm lợi nhuận, vì SO được tham chiếu rất nhiều trên Google. Vì vậy, vâng. Câu hỏi này hoàn toàn có thể phù hợp với mẫu Hỏi & Đáp của SO.
Lukas Eder

5
@VinceEmigh " ... xem cách bạn PHẢI ghi đè các phương thức được kế thừa từ các giao diện ... " Điều đó không đúng trong Java 8. Java 8 cho phép bạn triển khai các phương thức trong các giao diện, có nghĩa là bạn không phải triển khai chúng trong việc triển khai chúng các lớp học. Ở đây, finalsẽ có một cách sử dụng trong việc ngăn chặn các lớp thực hiện ghi đè lên việc thực hiện mặc định của một phương thức giao diện.
awksp

28
@EJP bạn không bao giờ biết: Brian Goetz có thể trả lời !
assylias

Câu trả lời:


419

Câu hỏi này, ở một mức độ nào đó, có liên quan đến lý do tại sao không được phép đồng bộ hóa trong các phương thức giao diện Java 8?

Điều quan trọng để hiểu về các phương thức mặc định là mục tiêu thiết kế chính là tiến hóa giao diện , chứ không phải "biến giao diện thành các đặc điểm (tầm thường)". Mặc dù có một số sự chồng chéo giữa hai người, và chúng tôi đã cố gắng giải quyết vấn đề sau, nơi nó không cản trở người trước, những câu hỏi này được hiểu rõ nhất khi được nhìn trong ánh sáng này. (Lưu ý quá mà phương pháp lớp học được sẽ khác biệt so với các phương pháp giao diện, không có vấn đề gì mục đích, nhờ thực tế là phương pháp giao diện có thể được thừa hưởng nhân.)

Ý tưởng cơ bản của một phương thức mặc định là: đó là một phương thức giao diện với cách triển khai mặc định và một lớp dẫn xuất có thể cung cấp một cách thực hiện cụ thể hơn. Và bởi vì trung tâm thiết kế là sự phát triển giao diện, nên mục tiêu thiết kế quan trọng là các phương thức mặc định có thể được thêm vào các giao diện sau khi thực tế theo cách tương thích nguồn và tương thích nhị phân.

Câu trả lời quá đơn giản cho "tại sao không phải là phương thức mặc định cuối cùng" là khi đó phần thân sẽ không chỉ đơn giản là phần thực hiện mặc định, nó sẽ là phần thực hiện duy nhất. Mặc dù đó là một câu trả lời quá đơn giản, nhưng nó cho chúng ta một manh mối rằng câu hỏi đã đi theo một hướng nghi vấn.

Một lý do khác tại sao các phương thức giao diện cuối cùng là nghi vấn là chúng tạo ra các vấn đề không thể cho người thực hiện. Ví dụ: giả sử bạn có:

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

Ở đây, mọi thứ đều tốt; Ckế thừa foo()từ A. Bây giờ giả sử Bđược thay đổi để có một foophương thức, với mặc định:

interface B { 
    default void foo() { ... }
}

Bây giờ, khi chúng ta biên dịch lại C, trình biên dịch sẽ cho chúng ta biết rằng nó không biết nên thừa hưởng hành vi nào foo(), vì vậy Cphải ghi đè lên nó (và có thể chọn ủy quyền A.super.foo()nếu muốn giữ lại hành vi tương tự.) Nhưng nếu Bđã làm cho nó mặc định final, và Akhông thuộc quyền kiểm soát của tác giả C? Bây giờ Clà không thể phá vỡ; nó không thể biên dịch mà không ghi đè foo(), nhưng nó không thể ghi đè foo()nếu nó là cuối cùng B.

Đây chỉ là một ví dụ, nhưng vấn đề là tính hữu hạn cho các phương thức thực sự là một công cụ có ý nghĩa hơn trong thế giới của các lớp thừa kế đơn (nói chung là cặp đôi với hành vi), hơn là các giao diện chỉ đóng góp hành vi và có thể nhân lên thừa hưởng. Thật khó để lý giải về "những giao diện khác có thể được trộn lẫn vào người triển khai cuối cùng" và việc cho phép một phương thức giao diện là cuối cùng có thể gây ra những vấn đề này (và chúng sẽ nổ tung không phải ở người viết giao diện, mà là trên người dùng nghèo, những người cố gắng thực hiện nó.)

Một lý do khác để không cho phép họ là họ sẽ không có nghĩa là những gì bạn nghĩ. Việc triển khai mặc định chỉ được xem xét nếu lớp (hoặc siêu lớp của nó) không cung cấp khai báo (cụ thể hoặc trừu tượng) của phương thức. Nếu một phương thức mặc định là cuối cùng, nhưng một siêu lớp đã triển khai phương thức đó, thì mặc định sẽ bị bỏ qua, đây có thể không phải là điều mà tác giả mặc định mong đợi khi tuyên bố nó là cuối cùng. (Hành vi kế thừa này là sự phản ánh của trung tâm thiết kế cho các phương thức mặc định - tiến hóa giao diện. Có thể thêm một phương thức mặc định (hoặc triển khai mặc định cho phương thức giao diện hiện tại) vào các giao diện hiện có đã triển khai, mà không thay đổi hành vi của các lớp hiện có thực hiện giao diện,


88
Thật tuyệt vời khi thấy bạn trả lời các câu hỏi về các tính năng ngôn ngữ mới! Thật hữu ích khi làm rõ về ý định và chi tiết của thiết kế khi tìm ra cách chúng ta nên sử dụng các tính năng mới. Là những người khác có liên quan đến thiết kế đóng góp cho SO - hay bạn chỉ đang làm điều này? Tôi sẽ theo dõi câu trả lời của bạn dưới thẻ java-8 - Tôi muốn biết liệu có những người khác làm điều tương tự để tôi cũng có thể theo dõi họ không.
Shorn

10
@Shorn Stuart Marks đã hoạt động trong thẻ java-8. Jeremy Manson đã đăng trong quá khứ. Tôi cũng nhớ đã thấy tin nhắn từ Joshua Bloch nhưng không thể tìm thấy chúng ngay bây giờ.
assylias

1
Xin chúc mừng vì đã đưa ra các phương thức giao diện mặc định như là một cách hoàn hảo thanh lịch hơn nhiều mà C # thực hiện với các phương thức mở rộng được triển khai xấu và khá xấu xí của nó. Đối với câu hỏi này, câu trả lời về việc không thể giải quyết một loại xung đột tên để giải quyết vấn đề, nhưng phần còn lại của những lý do được cung cấp là triết lý không thuyết phục. (Nếu tôi muốn một phương thức giao diện là cuối cùng, thì bạn nên cho rằng tôi phải có lý do chính đáng để tôi không cho phép bất kỳ ai cung cấp bất kỳ triển khai nào khác với tôi.)
Mike Nakis

1
Tuy nhiên, giải quyết xung đột tên có thể đạt được theo cách tương tự như cách thực hiện trong C #, bằng cách thêm tên của giao diện cụ thể đang được triển khai vào khai báo phương thức triển khai. Vì vậy, điều tôi rút ra được từ tất cả những điều này là các phương thức giao diện mặc định không thể là cuối cùng trong Java bởi vì điều đó sẽ yêu cầu sửa đổi bổ sung cho cú pháp mà bạn không cảm thấy tốt. (Có lẽ quá sắc nét?)
Mike Nakis

18
@Trying Các lớp trừu tượng vẫn là cách duy nhất để giới thiệu trạng thái hoặc triển khai các phương thức Object cốt lõi. Các phương thức mặc định là cho hành vi thuần túy ; các lớp trừu tượng là cho hành vi kết hợp với nhà nước.
Brian Goetz

43

Trong danh sách gửi thư lambda có rất nhiều cuộc thảo luận về nó . Một trong những thứ dường như chứa rất nhiều cuộc thảo luận về tất cả những thứ đó là như sau: Khả năng hiển thị phương thức giao diện đa dạng (là Người bảo vệ cuối cùng) .

Trong cuộc thảo luận này, Talden, tác giả của câu hỏi ban đầu hỏi một cái gì đó rất giống với câu hỏi của bạn:

Quyết định làm cho tất cả các thành viên giao diện công khai thực sự là một quyết định không may. Rằng bất kỳ việc sử dụng giao diện trong thiết kế nội bộ đều làm lộ ra các chi tiết riêng tư thực hiện là một vấn đề lớn.

Đó là một khó khăn để khắc phục mà không cần thêm một số sắc thái khó hiểu hoặc tương thích phá vỡ ngôn ngữ. Một sự phá vỡ khả năng tương thích của cường độ và sự tinh tế tiềm năng đó sẽ không thể nhận ra được vì vậy một giải pháp phải tồn tại mà không phá vỡ mã hiện có.

Có thể giới thiệu lại từ khóa 'gói' như một công cụ xác định truy cập khả thi. Sự vắng mặt của một công cụ xác định trong một giao diện sẽ ngụ ý truy cập công cộng và sự vắng mặt của một công cụ xác định trong một lớp ngụ ý truy cập gói. Những công cụ xác định nào có ý nghĩa trong một giao diện không rõ ràng - đặc biệt là, để giảm thiểu gánh nặng kiến ​​thức cho các nhà phát triển, chúng tôi phải đảm bảo rằng các công cụ truy cập có nghĩa giống nhau trong cả lớp và giao diện nếu chúng có mặt.

Trong trường hợp không có các phương thức mặc định, tôi đã suy đoán rằng trình xác định của một thành viên trong giao diện ít nhất phải hiển thị như chính giao diện đó (vì vậy giao diện thực sự có thể được thực hiện trong tất cả các bối cảnh có thể nhìn thấy) - với các phương thức mặc định không phải là chắc chắn như vậy

Đã có bất kỳ thông tin liên lạc rõ ràng nào về việc liệu đây có phải là một cuộc thảo luận trong phạm vi có thể không? Nếu không, nó nên được tổ chức ở nơi khác.

Cuối cùng , câu trả lời của Brian Goetz là:

Vâng, điều này đã được khám phá.

Tuy nhiên, hãy để tôi đặt một số kỳ vọng thực tế - các tính năng ngôn ngữ / VM có thời gian dài, thậm chí những thứ trông có vẻ tầm thường như thế này. Thời gian đề xuất ý tưởng tính năng ngôn ngữ mới cho Java SE 8 đã trôi qua khá nhiều.

Vì vậy, rất có thể nó không bao giờ được thực hiện bởi vì nó không bao giờ là một phần của phạm vi. Nó không bao giờ được đề xuất trong thời gian để được xem xét.

Trong một cuộc thảo luận sôi nổi khác về các phương pháp phòng thủ cuối cùng về chủ đề này, Brian nói lại :

Và bạn đã nhận được chính xác những gì bạn muốn. Đó chính xác là những gì tính năng này thêm vào - nhiều kế thừa hành vi. Tất nhiên chúng tôi hiểu rằng mọi người sẽ sử dụng chúng như những đặc điểm. Và chúng tôi đã làm việc chăm chỉ để đảm bảo rằng mô hình thừa kế mà họ đưa ra là đơn giản và đủ sạch để mọi người có thể có kết quả tốt khi làm như vậy trong nhiều tình huống khác nhau. Đồng thời, chúng tôi đã chọn không đẩy chúng ra khỏi ranh giới của những gì hoạt động đơn giản và sạch sẽ, và điều đó dẫn đến phản ứng "aw, bạn đã không đi đủ xa" trong một số trường hợp. Nhưng thực sự, hầu hết các chủ đề này dường như đang càu nhàu rằng kính chỉ đầy 98%. Tôi sẽ lấy 98% đó và tiếp tục với nó!

Vì vậy, điều này củng cố lý thuyết của tôi rằng nó đơn giản không phải là một phần của phạm vi hoặc một phần của thiết kế của họ. Những gì họ đã làm là cung cấp đủ chức năng để giải quyết các vấn đề về tiến hóa API.


4
Tôi thấy rằng tôi nên bao gồm tên cũ, "phương pháp phòng thủ", trong cuộc phiêu lưu của tôi sáng nay. +1 để đào cái này lên.
Marco13

1
Đẹp đào lên sự thật lịch sử. Kết luận của bạn phù hợp với câu trả lời chính thức
Lukas Eder

Tôi không thấy lý do tại sao nó sẽ phá vỡ sự tương thích ngược. trận chung kết không được phép trước đây. bây giờ họ cho phép riêng tư, nhưng không được bảo vệ. myeh ... private không thể triển khai ... một giao diện mở rộng giao diện khác có thể triển khai các phần của giao diện cha mẹ, nhưng nó phải phơi bày khả năng quá tải cho người khác ...
mmm

17

Nó sẽ được khó khăn để tìm thấy và xác định "THE" câu trả lời, cho resons đề cập trong các ý kiến từ @EJP: Có khoảng 2 (+/- 2) người trên thế giới những người có thể cung cấp cho các câu trả lời rõ ràng nào cả . Và nghi ngờ, câu trả lời có thể chỉ là một cái gì đó như "Hỗ trợ các phương thức mặc định cuối cùng dường như không xứng đáng với nỗ lực tái cấu trúc các cơ chế giải quyết cuộc gọi nội bộ". Tất nhiên, đây chỉ là suy đoán, nhưng ít nhất nó được hỗ trợ bởi các bằng chứng tinh tế, như Tuyên bố này (của một trong hai người) trong danh sách gửi thư của OpenJDK :

"Tôi cho rằng nếu các phương thức" mặc định cuối cùng "được cho phép, chúng có thể cần phải viết lại từ invokespecial nội bộ sang invokeinterface người dùng có thể nhìn thấy."

và các sự kiện tầm thường như thế mà một phương thức đơn giản không được coi là phương thức cuối cùng (thực sự) khi nó là một defaultphương thức, như hiện được thực hiện trong phương thức Phương thức :: is_final_method trong OpenJDK.

Hơn nữa thông tin thực sự "có thẩm quyền" thực sự rất khó tìm, ngay cả với các nghiên cứu web quá mức và bằng cách đọc nhật ký cam kết. Tôi nghĩ rằng nó có thể liên quan đến sự mơ hồ tiềm ẩn trong quá trình giải quyết các cuộc gọi phương thức giao diện với các invokeinterfacelệnh gọi và phương thức lớp, tương ứng với invokevirtualhướng dẫn: Đối với invokevirtualhướng dẫn, có thể có một tra cứu vtable đơn giản , bởi vì phương thức phải được kế thừa từ một siêu lớp, hoặc được thực hiện bởi lớp trực tiếp. Ngược lại, một invokeinterfacecuộc gọi phải kiểm tra trang web cuộc gọi tương ứng để tìm ra giao diện cuộc gọi này thực sự đề cập đến (điều này được giải thích chi tiết hơn trong trang InterfaceCalls của HotSpot Wiki). Tuy nhiên,finalcác phương thức hoàn toàn không được chèn vào vtable hoặc thay thế các mục hiện có trong vtable (xem klassVtable.cpp. Line 333 ) và tương tự, các phương thức mặc định đang thay thế các mục hiện có trong vtable (xem klassVtable.cpp, Line 202 ) . Vì vậy, lý do thực tế (và do đó, câu trả lời) phải được ẩn sâu hơn bên trong các cơ chế giải quyết cuộc gọi phương thức (khá phức tạp), nhưng có lẽ các tham chiếu này sẽ vẫn được coi là hữu ích, chỉ dành cho những người khác quản lý để có được câu trả lời thực tế từ đó.


Cảm ơn cho cái nhìn sâu sắc thú vị. Tác phẩm của John Rose là một dấu vết thú vị. Mặc dù vậy, tôi vẫn không đồng ý với @EJP. Để làm ví dụ ngược lại, hãy xem câu trả lời của tôi cho một câu hỏi rất thú vị, có phong cách rất giống của Peter Lawrey . Nó tốt để đào ra sự kiện lịch sử, và tôi luôn vui mừng khi tìm thấy chúng ở đây trên Stack Overflow (nơi nào khác?). Tất nhiên, câu trả lời của bạn vẫn chỉ là suy đoán và tôi không tin chắc 100% rằng các chi tiết triển khai JVM sẽ là lý do cuối cùng (ý định chơi chữ) để JLS được viết ra bằng cách này hay cách khác ...
Lukas Eder

@LukasEder Chắc chắn, những loại câu hỏi này rất thú vị và IMHO phù hợp với mẫu Hỏi & Đáp. Tôi nghĩ có hai điểm cốt yếu gây ra tranh chấp ở đây: Thứ nhất là bạn hỏi "lý do". Điều này có thể trong nhiều trường hợp chỉ không chính thức được ghi nhận. Ví dụ: không có "lý do" được đề cập trong JLS tại sao không có dấu không dấu int, nhưng hãy xem stackoverflow.com/questions/430346 ... ...
Marco13

... ... Thứ hai là bạn chỉ hỏi "trích dẫn có thẩm quyền", điều này làm giảm số người dám viết câu trả lời từ "vài chục" xuống ... "gần như bằng không". Ngoài ra, tôi không chắc về cách phát triển của JVM và cách viết của JLS được đan xen, tức là sự phát triển ảnh hưởng đến những gì được viết vào JLS, nhưng ... tôi sẽ tránh mọi suy đoán ở đây; -)
Marco13

1
Tôi vẫn nghỉ trường hợp của tôi. Xem ai đã trả lời câu hỏi khác của tôi :-) Bây giờ sẽ rõ ràng với câu trả lời có thẩm quyền, ở đây trên Stack Overflow tại sao quyết định không hỗ trợ synchronizedvề defaultphương pháp.
Lukas Eder

3
@LukasEder Tôi thấy, giống nhau ở đây. Ai có thể mong đợi điều đó? Những lý do khá thuyết phục, đặc biệt đối với finalcâu hỏi này , và hơi khiêm tốn khi dường như không ai khác nghĩ về những ví dụ tương tự (hoặc, có thể, một số người nghĩ về những ví dụ này, nhưng không cảm thấy đủ thẩm quyền để trả lời). Vì vậy, bây giờ (xin lỗi, tôi phải làm điều này :) từ cuối cùng được nói.
Marco13

4

Tôi sẽ không nghĩ rằng việc chỉ định finalphương pháp giao diện thuận tiện là không cần thiết , tôi có thể đồng ý mặc dù điều đó thể hữu ích, nhưng dường như các chi phí đã vượt quá lợi ích.

Dù sao, những gì bạn phải làm là viết javadoc thích hợp cho phương thức mặc định, hiển thị chính xác phương thức đó là gì và không được phép làm. Theo cách đó, các lớp thực hiện giao diện "không được phép" thay đổi việc thực hiện, mặc dù không có gì đảm bảo.

Bất cứ ai cũng có thể viết một Collectiongiao diện tuân thủ giao diện và sau đó thực hiện mọi thứ trong các phương pháp hoàn toàn trái ngược trực quan, không có cách nào để bảo vệ bạn khỏi điều đó, ngoài việc viết các bài kiểm tra đơn vị mở rộng.


2
Hợp đồng Javadoc là một giải pháp hợp lệ cho ví dụ cụ thể mà tôi đã liệt kê trong câu hỏi của mình, nhưng câu hỏi thực sự không phải là về trường hợp sử dụng phương thức giao diện tiện lợi. Câu hỏi là về một lý do finalcó thẩm quyền tại sao đã được quyết định không cho phép trên các interfacephương thức Java 8 . Tỷ lệ chi phí / lợi ích không đủ là một ứng cử viên tốt, nhưng cho đến nay, đó là suy đoán.
Lukas Eder

0

Chúng tôi thêm defaulttừ khóa vào phương thức của chúng tôi trong interfacekhi chúng tôi biết rằng lớp mở rộng interfacecó thể hoặc không thể overridethực hiện. Nhưng nếu chúng ta muốn thêm một phương thức mà chúng ta không muốn bất kỳ lớp thực hiện nào ghi đè thì sao? Vâng, hai tùy chọn đã có sẵn cho chúng tôi:

  1. Thêm một default finalphương thức.
  2. Thêm một staticphương thức.

Bây giờ, Java nói rằng nếu chúng ta có một classtriển khai hai hoặc nhiều hơn interfacesđể chúng có một defaultphương thức có tên và chữ ký chính xác giống nhau, nghĩa là chúng trùng lặp, thì chúng ta cần cung cấp một triển khai phương thức đó trong lớp của chúng ta. Bây giờ trong trường hợp default finalphương thức, chúng tôi không thể cung cấp một triển khai và chúng tôi bị mắc kẹt. Và đó là lý do tại sao finaltừ khóa không được sử dụng trong các giao diện.

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.