Tại sao Java không cho phép các định nghĩa hàm xuất hiện bên ngoài lớp?


22

Không giống như C ++, trong Java, chúng ta không thể chỉ có các khai báo hàm trong lớp và các định nghĩa bên ngoài lớp. Tại sao nó như vậy?

Có phải để nhấn mạnh rằng một tệp duy nhất trong Java chỉ nên chứa một lớp và không có gì khác không?



1
Theo định nghĩa, bạn có nghĩa là thuộc tính, hoặc chữ ký phương thức tập tin tiêu đề ala?
Deco

Một câu trả lời châm biếm tốt cho câu hỏi của bạn: steve-yegge.blogspot.com/2006/03/iêu
Brandon

Câu trả lời:


33

Sự khác biệt giữa C ++ và Java là ở chỗ ngôn ngữ coi đơn vị liên kết nhỏ nhất của chúng.

Vì C được thiết kế để cùng tồn tại với lắp ráp, nên đơn vị đó là chương trình con được gọi bởi một địa chỉ. (Điều này đúng với các ngôn ngữ khác biên dịch thành tệp đối tượng gốc, chẳng hạn như FORTRAN.) Nói cách khác, tệp đối tượng chứa hàm foo()sẽ có ký hiệu được gọi _foosẽ được giải quyết thành không có gì ngoài địa chỉ như0xdeadbeeftrong quá trình liên kết. Đó là tất cả có. Nếu chức năng là để lấy các đối số, thì tùy thuộc vào người gọi để đảm bảo mọi thứ mà chức năng mong đợi được sắp xếp theo thứ tự trước khi gọi địa chỉ của nó. Thông thường, điều này được thực hiện bằng cách xếp chồng mọi thứ lên ngăn xếp và trình biên dịch sẽ đảm nhiệm công việc lẩm cẩm và đảm bảo các nguyên mẫu khớp với nhau. Không có kiểm tra này giữa các tệp đối tượng; nếu bạn tăng liên kết cuộc gọi, cuộc gọi sẽ không diễn ra theo kế hoạch và bạn sẽ không nhận được cảnh báo về cuộc gọi đó. Mặc dù nguy hiểm, điều này làm cho các tệp đối tượng được biên dịch từ nhiều ngôn ngữ (bao gồm cả tập hợp) có thể được liên kết với nhau thành một chương trình hoạt động mà không có quá nhiều phiền phức.

C ++, mặc dù tất cả sự huyền ảo bổ sung của nó, hoạt động theo cùng một cách. Trình biên dịch shoehorns không gian tên, lớp và phương thức / thành viên / vv. vào quy ước này bằng cách làm phẳng nội dung của các lớp thành các tên duy nhất được xáo trộn theo cách làm cho chúng trở nên độc nhất. Ví dụ, một phương thức như Foo::bar(int baz)có thể được đưa vào _ZN4Foo4barEikhi được đưa vào một tệp đối tượng và một địa chỉ như 0xBADCAFEtrong thời gian chạy. Điều này hoàn toàn phụ thuộc vào trình biên dịch, vì vậy nếu bạn cố gắng liên kết hai đối tượng có các sơ đồ xáo trộn khác nhau, bạn sẽ không gặp may. Xấu xí như thế này, điều đó có nghĩa là bạn có thể sử dụng một extern "C"khối để vô hiệu hóa việc xáo trộn, giúp cho mã C ++ có thể dễ dàng truy cập bằng các ngôn ngữ khác. C ++ thừa hưởng khái niệm các hàm nổi tự do từ C, phần lớn là do định dạng đối tượng gốc cho phép nó.

Java là một con thú khác sống trong một thế giới cách điện với định dạng tệp đối tượng của riêng nó, .classtệp. Các tệp lớp chứa rất nhiều thông tin về nội dung của chúng cho phép môi trường thực hiện mọi thứ với các lớp trong thời gian chạy mà cơ chế liên kết riêng thậm chí không thể mơ tới. Thông tin đó phải bắt đầu từ đâu đó và điểm bắt đầu làclass. Thông tin có sẵn cho phép mã được biên dịch tự mô tả mà không cần các tệp riêng biệt chứa mô tả trong mã nguồn như bạn có trong C, C ++ hoặc các ngôn ngữ khác. Điều đó mang lại cho bạn tất cả các ngôn ngữ lợi ích an toàn loại sử dụng thiếu liên kết gốc, ngay cả trong thời gian chạy và là thứ cho phép bạn loại bỏ một lớp tùy ý ra khỏi tệp bằng cách sử dụng sự phản chiếu và sử dụng nó với một lỗi được bảo đảm nếu có gì đó không khớp .

Nếu bạn chưa tìm ra nó, tất cả sự an toàn này đều đi kèm với sự đánh đổi: mọi thứ bạn liên kết với chương trình Java đều phải là Java. (Bằng "liên kết", ý tôi là bất cứ lúc nào một cái gì đó trong một tệp lớp đều đề cập đến một thứ khác.) Bạn có thể liên kết (theo nghĩa gốc) với mã gốc bằng cách sử dụng JNI , nhưng có một hợp đồng ngầm nói rằng nếu bạn phá vỡ khía cạnh bản địa , bạn sở hữu cả hai mảnh.

Java rất lớn và không đặc biệt nhanh trên phần cứng có sẵn khi nó được giới thiệu lần đầu tiên, giống như Ada đã có trong thập kỷ trước. Chỉ Jim Gosling mới có thể nói chắc chắn động cơ của anh ta là gì khi tạo ra đơn vị liên kết nhỏ nhất của lớp Java, nhưng tôi phải đoán rằng sự phức tạp thêm rằng việc thêm các float nổi miễn phí sẽ thêm vào thời gian chạy có thể là một kẻ giết người thỏa thuận.


liên kết bị phá vỡ, nhận được liên kết lưu trữ web
noɥʇʎԀʎzɐɹƆ

14

Tôi tin rằng câu trả lời là, trên Wikipedia, rằng Java được thiết kế đơn giản và hướng đối tượng. Các hàm có nghĩa là hoạt động trên các lớp mà chúng được định nghĩa. Với dòng suy nghĩ đó, có các hàm bên ngoài một lớp không có ý nghĩa gì. Tôi sẽ đi đến kết luận rằng Java không cho phép điều đó bởi vì nó không phù hợp với OOP thuần túy.

Một tìm kiếm nhanh trên Google đối với tôi không mang lại nhiều động lực thiết kế ngôn ngữ Java.


6
Không phải sự tồn tại của các kiểu nguyên thủy đã loại trừ Java khỏi "OOP thuần túy" sao?
Radu Murzea

5
@SoboLAN: Có, và thêm các chức năng sẽ làm cho nó thậm chí ít "OOP thuần túy" hơn.
Giorgio

8
@Sobolan phụ thuộc vào định nghĩa của bạn về "OOP thuần túy". Tại một số điểm, tất cả mọi thứ là sự kết hợp của các bit trong bộ nhớ máy tính ...
jwenting

3
@jwenting: Chà, mục đích của sự trừu tượng hóa trong các ngôn ngữ lập trình là để ẩn các bit bên dưới càng nhiều càng tốt. Bạn càng có thể thấy các bit đó trong chương trình của mình, bạn càng nên bắt đầu nghĩ rằng ngôn ngữ lập trình của bạn cung cấp các khái niệm trừu tượng (trừ khi nó được xây dựng gần với kim loại trên mục đích). Vì vậy, vâng, mọi thứ tập trung vào việc thao túng các bit, nhưng các ngôn ngữ khác nhau cung cấp các mức độ trừu tượng khác nhau, nếu không bạn sẽ không thể phân biệt lắp ráp với ngôn ngữ cấp cao hơn.
Giorgio

2
Phụ thuộc vào định nghĩa của bạn về "OOP thuần túy": mọi giá trị dữ liệu là một đối tượng và mọi hoạt động dữ liệu là lời gọi phương thức kết quả thu được thông qua việc truyền thông điệp. Theo tôi biết không có gì hơn cho nó.
Giorgio

11

Câu hỏi thực sự là cái gì sẽ là công đức của việc tiếp tục làm mọi thứ theo cách C ++ và mục đích ban đầu của tệp tiêu đề là gì? Câu trả lời ngắn gọn là kiểu tệp tiêu đề cho phép thời gian biên dịch nhanh hơn trên các dự án lớn, trong đó nhiều lớp có khả năng tham chiếu cùng loại. Điều này là không cần thiết trong JAVA và .NET do bản chất của trình biên dịch.

Xem câu trả lời này tại đây: Các tệp tiêu đề có thực sự tốt không?


1
+1, mặc dù tôi thực sự đã nghe mọi người nói rằng họ thích có sự tách biệt giữa giao diện công cộng và triển khai riêng tư mà các tệp tiêu đề cung cấp. Những người đó là sai, tất nhiên. ;)
vaughandroid

6
@Baqueta Trong Java, bạn có thể đạt được điều này với interfaceclass:) Không cần tiêu đề!
Andres F.

1
Tôi sẽ không bao giờ hiểu được mong muốn của việc phải xem 3+ tệp để hiểu cách một cá thể lớp đơn giản hoạt động.
Erik Reppen

Thông thường @ErikReppen, giao diện (hoặc tệp tiêu đề trong C) là thứ mà khách hàng nhận được ở dạng người dùng có thể đọc được để viết các giải pháp của họ, phần còn lại chỉ được cung cấp ở dạng nhị phân (tất nhiên là trong Java, không cần cung cấp nguồn của các giao diện, classfiles và javadoc sẽ làm).
jwenting

@jwenting Tôi chưa nghĩ đến trường hợp đó. Tôi đã sử dụng nhiều hơn để nghĩ về tên khốn tội nghiệp tiếp theo phải duy trì cơ sở mã ngớ ngẩn này, bởi vì tất cả thường xuyên là tôi ở công việc tiếp theo làm tôi bối rối sau khi dành hàng giờ để nhìn vào một giao diện kỳ ​​quái và siêu lớp / siêu lớp kiến trúc vòng tròn.
Erik Reppen

3

Một tệp Java đại diện cho một lớp. Nếu bạn có một thủ tục bên ngoài lớp học, phạm vi sẽ là gì? Nó sẽ là toàn cầu? Hoặc nó sẽ thuộc về lớp mà tệp Java đại diện?

Có lẽ, bạn đặt nó trong tệp Java đó thay vì một tệp khác vì một lý do - bởi vì nó đi với lớp đó nhiều hơn bất kỳ lớp nào khác. Nếu một thủ tục bên ngoài một lớp thực sự được liên kết với lớp đó, thì tại sao không buộc nó đi vào bên trong lớp đó, nơi nó thuộc về? Java xử lý điều này như một phương thức tĩnh bên trong lớp.

Nếu một thủ tục bên ngoài được cho phép, có lẽ nó sẽ không có quyền truy cập đặc biệt vào lớp có tệp được khai báo, do đó giới hạn nó ở một chức năng tiện ích không thay đổi bất kỳ dữ liệu nào.

Mặt trái duy nhất có thể có của giới hạn Java này là nếu bạn thực sự có các quy trình toàn cầu không liên quan đến bất kỳ lớp nào, cuối cùng bạn sẽ tạo một lớp MyGlobals để giữ chúng và nhập lớp đó trong tất cả các tệp khác sử dụng các quy trình đó .

Trong thực tế, cơ chế nhập khẩu Java cần hạn chế này để hoạt động. Với tất cả các API có sẵn, trình biên dịch java cần biết chính xác những gì cần biên dịch và những gì cần biên dịch, do đó, các câu lệnh nhập rõ ràng ở đầu tệp. Không cần phải nhóm các quả cầu của bạn thành một lớp nhân tạo, làm thế nào bạn có thể yêu cầu trình biên dịch Java biên dịch các quả cầu của bạn chứ không phải bất kỳ và tất cả các quả cầu trên đường dẫn của bạn? Điều gì về xung đột không gian tên nơi bạn có doStuff () và người khác có doStuff ()? Nó sẽ không hoạt động. Buộc bạn phải xác định MyClass.doStuff () và YourClass.doStuff () khắc phục các sự cố này. Buộc các thủ tục của bạn đi vào bên trong MyClass thay vì bên ngoài nó chỉ làm rõ hạn chế này và không áp đặt các hạn chế bổ sung đối với mã của bạn.

Java có một số điều sai - việc tuần tự hóa có quá nhiều mụn cóc nhỏ đến mức gần như quá khó để có ích (nghĩ rằng SerialVersionUID). Nó cũng có thể được sử dụng để phá vỡ singletons và các mẫu thiết kế phổ biến khác. Phương thức clone () trên Object nên được tách thành deepClone () và nôngClone () và an toàn kiểu. Tất cả các lớp API có thể đã được tạo thành bất biến theo mặc định (cách chúng ở trong Scala). Nhưng hạn chế rằng tất cả các thủ tục phải thuộc về một lớp là một điều tốt. Nó phục vụ chủ yếu để đơn giản hóa và làm rõ ngôn ngữ và mã của bạn mà không áp đặt bất kỳ hạn chế khó khăn nào.


3

Tôi nghĩ rằng hầu hết những người trả lời và cử tri của họ đã hiểu sai câu hỏi. Nó phản ánh rằng họ không biết C ++.

"Định nghĩa" và "Tuyên bố" là những từ có ý nghĩa rất cụ thể trong C ++.

OP không có nghĩa là thay đổi cách Java hoạt động. Đây là một câu hỏi hoàn toàn về cú pháp. Tôi nghĩ đó là một câu hỏi hợp lệ.

Trong C ++, có hai cách để xác định hàm thành viên.

Cách đầu tiên là cách Java. Chỉ cần đặt tất cả mã bên trong dấu ngoặc nhọn:

class Box {
public:
    // definition of member function
    void change(int newInt) { 
        this._m = newInt;
    }
private:
    int _m
}

Cách thứ hai:

class Box {
public:  
    // declaration of member function
    void change(int newInt); 
private:
    int _m
}

// definition of member function
// this can be in the same file as the declaration
void Box::change(int newInt) {
    this._m = newInt;
}

Cả hai chương trình đều giống nhau. Hàm changevẫn là hàm thành viên: nó không tồn tại bên ngoài lớp. Hơn nữa, định nghĩa lớp PHẢI bao gồm tên và loại của TẤT CẢ các hàm và biến thành viên, giống như trong Java.

Jonathan Henson đúng rằng đây là một yếu tố của cách các tiêu đề hoạt động trong C ++: nó cho phép bạn đặt các khai báo trong tệp tiêu đề và các triển khai trong một tệp .cpp riêng để chương trình của bạn không vi phạm ODR (Quy tắc một định nghĩa). Nhưng nó có những ưu điểm bên ngoài: nó cho phép bạn nhìn thấy giao diện của một lớp lớn trong nháy mắt.

Trong Java, bạn có thể ước chừng hiệu ứng này với các lớp hoặc giao diện trừu tượng, nhưng các lớp này không thể có cùng tên với lớp triển khai, điều này làm cho nó khá vụng về.


và điều đó cho thấy cả hai bạn không hiểu người cũng như Java. Bạn có thể sử dụng cùng tên cho giao diện và triển khai, miễn là chúng không nằm trong cùng một gói.
jwenting

Tôi coi gói (hoặc không gian tên hoặc lớp bên ngoài) là một phần của tên.
Erik van Velzen

2

Tôi nghĩ đó là một tạo tác của cơ chế tải lớp. Mỗi tệp lớp là một thùng chứa cho một đối tượng có thể tải. Không có chỗ "bên ngoài" của các tập tin lớp.


1
Tôi không thấy lý do tại sao việc tổ chức mã nguồn theo các đơn vị riêng biệt sẽ ngăn việc giữ định dạng tệp lớp như hiện tại.
Mat

có sự tương ứng 1: 1 giữa các tệp lớp và tệp nguồn, đây là một trong những quyết định thiết kế tốt hơn trong toàn bộ hệ thống.
ddyer

@ddyer Bạn chưa bao giờ thấy Foo $ 2 $ 1. class sau đó? (xem tên tệp lớp bên trong Java )

Điều đó sẽ làm cho nó một ánh xạ "lên"? trong mọi trường hợp, mỗi lớp được tạo từ việc biên dịch chính xác một tệp nguồn, đây là một quyết định thiết kế tốt.
ddyer

0

C #, rất giống với Java, có loại tính năng này thông qua việc sử dụng các phương thức một phần, ngoại trừ các phương thức một phần là riêng tư.

Phương thức từng phần: http://msdn.microsoft.com/en-us/l Library / 6b0scde8.aspx

Các lớp học và phương pháp một phần: http://msdn.microsoft.com/en-us/l Library / wa80x488.aspx

Tôi không thấy bất kỳ lý do nào khiến Java không thể làm như vậy, nhưng có lẽ vấn đề là liệu có nhu cầu nhận thức từ cơ sở người dùng để thêm tính năng này vào ngôn ngữ hay không.

Hầu hết các công cụ tạo mã cho C # tạo các lớp một phần để nhà phát triển có thể dễ dàng thêm mã được viết thủ công vào lớp trong một tệp riêng, nếu muốn.


0

C ++ yêu cầu toàn bộ văn bản của lớp được biên dịch như một phần của mọi đơn vị biên dịch sử dụng bất kỳ thành viên nào trong đó hoặc tạo ra bất kỳ trường hợp nào. Cách duy nhất để duy trì thời gian biên dịch lành mạnh là có văn bản của lớp chứa - trong phạm vi có thể - chỉ những thứ thực sự cần thiết cho người tiêu dùng. Thực tế là các phương thức C ++ thường được viết bên ngoài các lớp có chứa chúng là một hack rất tệ hại được thúc đẩy bởi thực tế là việc yêu cầu trình biên dịch xử lý văn bản của mọi phương thức lớp một lần cho mọi đơn vị biên dịch mà lớp được sử dụng sẽ dẫn đến hoàn toàn thời gian xây dựng điên rồ.

Trong Java, các tệp lớp được biên dịch chứa, trong số những thứ khác, thông tin phần lớn tương đương với tệp C ++ .h. Người tiêu dùng của lớp có thể trích xuất tất cả thông tin họ cần từ tệp đó mà không cần phải có trình biên dịch xử lý tệp .java. Không giống như C ++, nơi tệp .h chứa thông tin được cung cấp cho cả các triển khai và máy khách của các lớp có trong đó, luồng trong Java bị đảo ngược: tệp mà máy khách sử dụng không phải là tệp nguồn được sử dụng trong quá trình biên dịch mã lớp, nhưng thay vào đó được trình biên dịch tạo ra bằng cách sử dụng thông tin tệp mã lớp. Do không cần phân chia mã lớp giữa một tệp chứa thông tin khách hàng cần và tệp chứa triển khai, Java không cho phép phân chia như vậy.


-1

Tôi nghĩ một phần của nó là Java rất giống một ngôn ngữ bảo hộ liên quan đến việc sử dụng các nhóm lớn. Các lớp không thể được ghi đè hoặc xác định lại. Bạn có 4 cấp độ sửa đổi truy cập xác định rất cụ thể cách các phương thức có thể và không thể được sử dụng. Mọi thứ đều mạnh mẽ / được nhập tĩnh để bảo vệ các nhà phát triển khỏi loại không phù hợp với Hijinx do người khác hoặc chính họ gây ra. Có các lớp và chức năng như các đơn vị nhỏ nhất của bạn giúp việc tái tạo mô hình ứng dụng kiến ​​trúc ứng dụng trở nên dễ dàng hơn rất nhiều.

So sánh với JavaScript khi các hàm danh từ / động từ có mục đích kép xuất hiện và được truyền qua như kẹo từ pinata mở, hàm tạo tương đương lớp có thể thay đổi nguyên mẫu của nó thêm các thuộc tính mới hoặc thay đổi các thuộc tính cũ đã được tạo bất cứ lúc nào, hoàn toàn không có gì ngăn bạn thay thế bất kỳ cấu trúc nào bằng phiên bản của chính nó, có 5 loại và chúng tự động chuyển đổi và tự động đánh giá trong các trường hợp khác nhau và bạn có thể đính kèm các thuộc tính mới vào bất cứ thứ gì:

function nounAndVerb(){
}
nounAndVerb.newProperty = 'egads!';

Cuối cùng, đó là về hốc và thị trường. JavaScript là thích hợp và tai hại trong tay 100 nhà phát triển (nhiều trong số đó có thể là tầm thường) như Java đã từng nằm trong tay các nhóm nhà phát triển nhỏ đang cố gắng viết giao diện người dùng web với nó. Khi bạn làm việc với 100 nhà phát triển, điều cuối cùng bạn muốn là một anh chàng sáng tạo lại mô hình chết tiệt. Khi bạn làm việc với UI hoặc phát triển nhanh là một mối quan tâm quan trọng hơn, điều cuối cùng bạn muốn là bị chặn đường khi thực hiện những việc tương đối đơn giản một cách nhanh chóng đơn giản vì sẽ rất dễ làm chúng một cách ngu ngốc nếu bạn không cẩn thận.

Tuy nhiên, vào cuối ngày, cả hai đều sử dụng các ngôn ngữ phổ biến vì vậy có một chút tranh luận triết học ở đó. Thịt bò cá nhân lớn nhất của tôi với Java và C # là tôi chưa bao giờ thấy hoặc làm việc với một cơ sở mã di sản mà phần lớn các nhà phát triển dường như đã hiểu giá trị của OOP cơ bản. Khi bạn tham gia vào trò chơi phải bọc mọi thứ trong một lớp, có lẽ sẽ dễ dàng hơn khi cho rằng bạn đang làm OOP thay vì chức năng spaghetti được ngụy trang thành chuỗi lớn 3-5 lớp.

Điều đó nói rằng, không có gì khủng khiếp hơn JavaScript được viết bởi một người chỉ biết đủ nguy hiểm và không ngại thể hiện điều đó. Và tôi nghĩ đó là ý tưởng chung. Gã đó được cho là phải chơi theo các quy tắc gần giống nhau trong Java. Tôi sẽ lập luận rằng tên khốn đó sẽ luôn luôn tìm cách.

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.