Чому Java не дозволяє визначенням функцій бути присутніми поза класом?


22

На відміну від C ++, у Java ми не можемо мати лише декларації функцій у класі та визначення поза класом. Чому так?

Чи варто підкреслювати, що один файл на Java повинен містити лише один клас і більше нічого?


2
Java дозволяє внутрішні класи та вкладені класи: docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html docs.oracle.com/javase/tutorial/java/javaOO/nested.html і анонімні класи також: cs.rit .edu / ~ tmh / курси / allJava / Anonymous.html
FrustratedWithFormsDesigner

1
Під визначенням ви маєте на увазі властивості чи метод підпису файлів заголовків ala?
Деко

Гарна сатирична відповідь на ваше запитання: steve-yegge.blogspot.com/2006/03/…
Брендон

Відповіді:


33

Різниця між C ++ та Java полягає в тому, що мови вважають їх найменшою одиницею зв'язку.

Оскільки C був розроблений для співіснування з складанням, цей блок є підпрограмою, що викликається адресою. (Це стосується інших мов, які компілюються в рідні об’єктні файли, наприклад, FORTRAN.) Іншими словами, об’єктний файл, що містить функцію, foo()буде мати символ, який називається, _fooякий буде вирішений нічим, крім такою адресою, як0xdeadbeefпід час зв’язування. Це все є. Якщо функція полягає в тому, щоб брати аргументи, перед тим, як викликати її адресу, переконайтесь, що абонент переконається, що все, що очікує функція. Зазвичай це робиться шляхом складання речей на стек, і компілятор піклується про бурхливу роботу і переконання, що прототипи збігаються. Немає перевірки цього між файлами об’єктів; якщо ви перейдете на з'єднання дзвінків, виклик не вимкнеться, як було заплановано, і ви не збираєтесь отримувати попередження про це. Незважаючи на небезпеку, це дає можливість об'єктні файли, зібрані з декількох мов (включаючи складання), бути об'єднаними разом у функціонуючу програму без великої суєти.

C ++, незважаючи на всю свою додаткову вигадливість, працює так само. Простіри імен, класи та методи компілятора вбудовувача компілятора, тощо. до цієї конвенції шляхом вирівнювання вмісту класів в одиночні імена, які керуються таким чином, що робить їх унікальними. Наприклад, такий метод, як, наприклад, Foo::bar(int baz)може потрапити _ZN4Foo4barEiв об'єктний файл і адресу, наприклад, 0xBADCAFEпід час виконання. Це повністю залежить від компілятора, тому якщо ви спробуєте зв’язати два об'єкти, які мають різні схеми керування, вам не пощастить. Некрасиво, як це, це означає, що ви можете використовувати extern "C"блок для відключення керування, що дозволяє зробити код C ++ легко доступним для інших мов. C ++ успадкував від C поняття вільно плаваючих функцій, значною мірою тому, що формат нативного об'єкта дозволяє це.

Java - це інший звір, який живе в ізольованому світі зі своїм власним форматним файлом об'єкта - .classфайлом. Файли класів містять велику кількість інформації про їх вміст, що дозволяє оточуючому середовищу робити речі з класами під час виконання, про які не можна було навіть мріяти про власну механізм зв’язку. Ця інформація повинна починатися десь, і ця відправна точка - цеclass. Наявна інформація дозволяє скомпільованому коду описувати себе без необхідності окремих файлів, що містять опис у вихідному коді, як це було б у C, C ++ або інших мовах. Це дає вам усі мови переваг безпеки, які використовують відсутність нативного зв’язку, навіть під час виконання, і це те, що дозволяє виловлювати довільний клас з файлу за допомогою відображення та використовувати його з гарантованим збоєм, якщо щось не відповідає .

Якщо ви цього ще не зрозуміли, уся ця безпека є компромісом: все, що ви посилаєтесь на програму Java, має бути Java. (Під "посиланням" я маю на увазі будь-коли щось в одному файлі класу посилається на щось в іншому.) Ви можете зв’язати (у рідному розумінні) з нативним кодом за допомогою JNI , але є неявна контракт, який говорить про те, що якщо ви порушите рідну сторону , ви володієте обома штуками.

Java була великою і не особливо швидкою на доступному обладнання, коли вона була вперше представлена, як і Ада в попереднє десятиліття. Тільки Джим Гослінг може точно сказати, які його мотивації були у створенні найменшої одиниці зв’язку Java класу Java, але я мусив би здогадатися, що додаткова складність, що додавання вільних плавців додало б під час виконання, могло бути вбивцею.


посилання зламане, отримати посилання в веб-архіві
noɥʇʎԀʎzɐɹƆ

14

Я вважаю, що у Вікіпедії відповідь полягає в тому, що Java була розроблена так, щоб вона була простою та об'єктно орієнтованою. Функції мають на увазі функціонування на класах, для яких вони визначені. З цим напрямом мислення мати функції поза класом не має сенсу. Я збираюся стрибнути до висновку, що Java не дозволяє це, оскільки він не вписувався в чистий OOP.

Швидкий пошук Google для мене не приніс особливих мотивів дизайну мови Java.


6
Чи не існування примітивних типів виключає Java з "чистого OOP"?
Раду Мурзеа

5
@SoboLAN: Так, і додавання функцій зробить його ще менш "чистим OOP".
Джорджіо

8
@SoboLAN, що залежить від вашого визначення "чистого OOP". У якийсь момент все є поєднанням бітів в пам'яті комп’ютера, зрештою ...
jwenting

3
@jwenting: Ну, мета абстракції в мовах програмування - максимально приховати основні біти. Чим більше ви зможете побачити ці біти у вашій програмі, тим більше вам слід починати думати, що ваша мова програмування пропонує витікаючі абстракції (якщо вона спеціально не побудована близько до металу). Так, так, все зводиться до маніпулювання бітами, але різні мови пропонують різні рівні абстракції, інакше ви не зможете відрізнити збірку від мови вищого рівня.
Джорджіо

2
Залежить від вашого визначення поняття "чистий OOP": кожне значення даних є об'єктом, і кожна операція з даними є викликом методу результату, отриманого при передачі повідомлення. Наскільки я знаю, нічого більше в цьому немає.
Джорджіо

11

Справжнє запитання полягає в тому, у чому полягає заслуга продовжувати робити речі способом C ++ і в чому полягала початкова мета файлу заголовка? Коротка відповідь полягає в тому, що стиль файлу заголовка дозволяє швидше збирати час для великих проектів, в яких багато класів потенційно можуть посилатися на один і той же тип. Це не обов'язково в JAVA та .NET через характер компіляторів.

Дивіться цю відповідь тут: Чи гарні файли заголовків насправді?


1
+1, хоча я насправді чув, як люди говорять, що їм подобається розмежування між публічним інтерфейсом та приватною реалізацією, яку надають файли заголовків. Ці люди помиляються, звичайно. ;)
vaughandroid

6
@Baqueta У Java ви можете досягти цього за допомогою interfaceі class:) Не потрібно в заголовках!
Андрес Ф.

1
Я ніколи не зрозумію бажаність перегляду файлів 3+, щоб зрозуміти, як працює простий екземпляр класу.
Ерік Реппен

@ErikReppen, як правило, інтерфейс (або файл заголовка в C) - це те, що клієнти отримують у читаній для користувача формі, щоб написати свої рішення, а решта надається лише у двійковій формі (звичайно, на Java, немає необхідності надавати джерела інтерфейсів, класних файлів і javadoc).
jwenting

@jwenting Я не думав про цей випадок. Я більше звик думати про наступного убогого сволота, якому доводиться підтримувати цю нерозумну базу коду, тому що занадто часто це я на наступній роботі, колючи очима, витрачаючи години, дивлячись на якийсь химерний інтерфейс і під / суперклас -кругла архітектура.
Ерік Реппен

3

Файл Java представляє клас. Якби у вас була процедура поза класом, якою була б сфера застосування? Це було б глобальним? Або він належав би до класу, який представляє файл Java?

Імовірно, ви помістите його в цей файл Java замість іншого файлу з причини - тому що він відповідає цьому класу більше, ніж будь-який інший клас. Якщо процедура поза класом насправді була пов'язана з цим класом, то чому б не змусити її зайти всередину того класу, де він належить? Java обробляє це як статичний метод всередині класу.

Якби була дозволена процедура зовнішнього класу, вона, ймовірно, не мала б спеціального доступу до класу, у файлі якого він був задекларований, таким чином обмеживши його функцією утиліти, яка не змінює жодних даних.

Єдине можливе обмеження цього обмеження Java - це те, що якщо у вас справді є глобальні процедури, не пов'язані з жодним класом, ви в кінцевому підсумку створюєте клас MyGlobals для їх зберігання та імпортуєте цей клас у всі ваші інші файли, які використовують ці процедури .

Насправді механізм імпорту Java потребує цього обмеження, щоб функціонувати. З урахуванням усіх доступних API, компілятору Java потрібно точно знати, що потрібно компілювати та проти чого компілювати, таким чином, явні заяви про імпорт у верхній частині файлу. Без того, щоб згрупувати глобали в штучний клас, як би ви сказали компілятору Java скласти ваші глобальні, а не будь-які та всі глобалі на вашому класному шляху? Як щодо зіткнення простору імен, де у вас є doStuff (), а хтось інший має doStuff ()? Це б не спрацювало. Примушуючи вказати MyClass.doStuff () та YourClass.doStuff () виправляє ці проблеми. Примушуючи ваші процедури заходити всередину MyClass замість зовні, це лише уточнює це обмеження і не накладає додаткових обмежень на ваш код.

У Java помилився ряд речей - серіалізація має стільки маленьких бородавок, що бути майже надто важким, щоб бути корисним (думаю, SerialVersionUID). Він також може бути використаний для розбивання одинакових та інших поширених моделей дизайну. Метод clone () на Object повинен бути розділений на deepClone () та shallowClone () та бути безпечним для типу. Усі класи API могли бути зроблені незмінними за замовчуванням (як вони є у Scala). Але обмеження, що всі процедури повинні належати класу, є хорошим. Він служить в першу чергу для спрощення та уточнення мови та вашого коду, не встановлюючи обтяжуючих обмежень.


3

Я думаю, що більшість людей, які відповіли, та їхні виборці неправильно зрозуміли це питання. Це відображає, що вони не знають C ++.

"Визначення" та "Декларація" - це слова з дуже конкретним значенням у С ++.

ОП не означає змінювати спосіб роботи Java. Це питання суто про синтаксис. Я думаю, що це питання дійсне.

У C ++ є два способи визначення функції члена.

Перший спосіб - це спосіб Java. Просто введіть увесь код всередині дужок:

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

Другий спосіб:

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;
}

Обидві програми однакові. Функція changeвсе ще є функцією-членом: вона не існує поза класом. Крім того, визначення класу ОБОВ'ЯЗКОВО включає в себе імена та типи ВСІХ функцій та змінних членів, як і в Java.

Джонатан Хенсон має рацію, що це артефакт способу роботи заголовків у C ++: він дозволяє розміщувати декларації у файлі заголовка та його реалізації в окремому файлі .cpp, щоб ваша програма не порушувала ODR (One Definition Rule). Але в цьому є і плюси: він дозволяє з першого погляду побачити інтерфейс великого класу.

У Java ви можете наблизити цей ефект за допомогою абстрактних класів або інтерфейсів, але вони не можуть мати те саме ім'я, що і клас реалізації, що робить його досить незграбним.


і це показує, що ви обидва не розумієте людей, ні Java. Ви можете використовувати те саме ім'я для інтерфейсу та реалізації, якщо вони не в одному пакеті.
jwenting

Я вважаю пакет (або простір імен, або зовнішній клас) частиною імені.
Ерік ван Вельцен

2

Я думаю, що це артефакт механізму завантаження класу. Кожен файл класу є контейнером для завантажуваного об'єкта. Немає місця "поза" файлів класу.


1
Я не розумію, чому організація вихідного коду в окремі одиниці заважатиме зберігати формат файлу класу таким, яким він є.
Мат

між файлами класу та вихідними файлами є відповідність 1: 1, що є одним із кращих дизайнерських рішень у загальній системі.
ddyer

@ddyer Ви ніколи не бачили Foo $ 2 $ 1.class тоді? (див. назви файлів внутрішнього класу Java )

Це зробило б це "на" відображення? у будь-якому випадку кожен клас формується з компіляції саме одного вихідного файлу, що є хорошим дизайнерським рішенням.
ddyer

0

C #, який дуже схожий на Java, має таку особливість завдяки використанню часткових методів, за винятком того, що часткові методи є виключно приватними.

Часткові методи: http://msdn.microsoft.com/en-us/library/6b0scde8.aspx

Часткові класи та методи: http://msdn.microsoft.com/en-us/library/wa80x488.aspx

Я не бачу жодної причини, чому Java не могла би зробити те саме, але, мабуть, просто зводиться до того, чи існує базова потреба користувачів, щоб додати цю функцію до мови.

Більшість інструментів генерації коду для C # генерують часткові класи, щоб розробник міг легко додавати вручну записаний код до класу в окремому файлі.


0

C ++ вимагає, щоб весь текст класу був складений як частина кожного компіляційного блоку, який використовує будь-які його члени або створює будь-які екземпляри. Єдиний спосіб зберегти час компіляції здоровим - це те, щоб текст самого класу містив - наскільки це можливо - лише ті речі, які фактично необхідні його споживачам. Те, що методи C ++ часто записуються за межами класів, які містять їх, - це справді неприємний мерзенний хакер, мотивований тим, що необхідність компілятора обробляти текст кожного методу класу один раз для кожної одиниці компіляції, де використовується клас, призведе до цілком божевільний час побудови.

У Java складені файли класів містять, серед іншого, інформацію, яка значною мірою еквівалентна файлу C ++ .h. Споживачі класу можуть витягати всю необхідну інформацію з цього файлу, без необхідності компілятора обробляти файл .java. На відміну від C ++, де файл .h містить інформацію, яка стає доступною як для реалізацій, так і для клієнтів класів, що містяться в ньому, потік у Java обернено: файл, який використовують клієнти, не є вихідним файлом, що використовується при компіляції код класу, але замість цього виробляється компілятором, використовуючи інформацію про файл коду класу. Оскільки немає необхідності розділяти код класу між файлом, що містить інформацію клієнтів, і файлом, що містить реалізацію, Java не допускає такого розбиття.


-1

Я думаю, що частиною цього є те, що Java - це дуже протекціоністська мова, що стосується використання у великих командах. Класи не можна перезаписувати чи переосмислювати. У вас є 4 рівні модифікаторів доступу, які конкретно визначають, як можна і як не можна використовувати методи. Все є сильним / статично набраним, щоб захистити розробників від невідповідності типу hijinx, спричиненої іншими або самими собою. Наявність класів та функцій як ваших найменших одиниць значно полегшує винахідлення парадигми щодо створення архітектури програми.

Порівняйте з JavaScript, де функції подвійного іменника / дієслова першокласного функціонують дощу та передаються навколо них, як цукерки, від розімкнутої пінати, конструктор функцій, еквівалентних класам, може змінити свій прототип, додавши нові властивості або змінивши старі для випадків, коли Ви вже створені в будь-який час, абсолютно ніщо не заважає замінити будь-яку конструкцію на власну її версію, є 5 типів, і вони автоматично перетворюються та автоматично оцінюються за різних обставин, і ви можете приєднати нові властивості, щоб прокляття біля будь-чого:

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

Зрештою, мова йде про ніші та ринки. JavaScript є приблизно таким же підходящим і згубним в руках 100 (багато з яких, ймовірно, посередні) розробників, як Java колись була в руках невеликих груп розробників, які намагалися написати веб-інтерфейс. Коли ви працюєте зі 100 дисками, останнє, що вам потрібно, - це один хлопець, який винаходить прокляту парадигму. Коли ви працюєте з користувальницьким інтерфейсом або швидкий розвиток є більш важливим питанням, останнє, що ви хочете, - це заблокувати роботу над швидкими відносно простими речами, просто тому, що робити їх дуже нерозумно, якщо ви не будете обережні.

Зрештою, вони обидві популярні мови загального користування, тому тут є трохи філософського аргументу. Моя найбільша особиста яловичина з Java та C # - це те, що я ніколи не бачив і не працював зі спадковою базою кодів, де більшість розробників, схоже, зрозуміли значення базового OOP. Коли ви вступаєте в гру, що має обгортати все в класі, можливо, просто простіше припустити, що ви робите OOP, а не функціонуєте спагетті, замасковані під масивні ланцюжки з 3-5 ліній класів.

При цьому немає нічого страшнішого, ніж JavaScript, написаний тим, хто знає достатньо, щоб бути небезпечним і не боїться показати це. І я думаю, що це загальна ідея. Цей хлопець нібито повинен грати приблизно за тими ж правилами на Java. Я б заперечував, що ублюдок завжди знайде спосіб.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.