Почему Java не позволяет определениям функций присутствовать вне класса?


22

В отличие от C ++, в Java у нас не может быть просто объявлений функций в классе и определений вне класса. Почему это так?

Следует ли подчеркнуть, что один файл в Java должен содержать только один класс и ничего больше?



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

Хороший сатирический ответ на ваш вопрос: steve-yegge.blogspot.com/2006/03/…
Брэндон,

Ответы:


33

Разница между C ++ и Java заключается в том, что языки считают их наименьшей единицей связи.

Поскольку C был разработан, чтобы сосуществовать со сборкой, этот модуль является подпрограммой, вызываемой по адресу. (Это верно для других языков, которые компилируются в нативные объектные файлы, такие как FORTRAN.) Другими словами, объектный файл, содержащий функцию, foo()будет иметь вызываемый символ, _fooкоторый будет преобразован только в адрес, такой как0xdeadbeefво время ссылки. Это все, что есть. Если функция должна принимать аргументы, вызывающий должен убедиться, что все, что ожидает функция в порядке, перед вызовом своего адреса. Обычно это делается путем накапливания вещей в стеке, а компилятор берет на себя всю работу и проверяет соответствие прототипов. Нет проверки этого между объектными файлами; если вы отключите связь, вызов не будет выполнен как запланировано, и вы не получите предупреждение об этом. Несмотря на опасность, это позволяет без особых хлопот связывать объектные файлы, скомпилированные из нескольких языков (включая ассемблер), в работающую программу.

C ++, несмотря на всю свою дополнительную привлекательность, работает так же. Пространства имен, классы и методы / members / etc компилятора. в этом соглашении путем объединения содержимого классов в отдельные имена, которые искажены таким образом, что делает их уникальными. Например, такой метод Foo::bar(int baz)может быть искажен, _ZN4Foo4barEiкогда помещен в объектный файл, и адрес как 0xBADCAFEво время выполнения. Это полностью зависит от компилятора, поэтому, если вы попытаетесь связать два объекта, которые имеют разные схемы искажения, вам не повезет. Как это ни страшно, это означает, что вы можете использовать extern "C"блок, чтобы отключить искажение, что делает код C ++ легко доступным для других языков. C ++ унаследовал понятие свободно плавающих функций от C, в основном потому, что собственный формат объекта это позволяет.

Java - это другой зверь, который живет в изолированном мире с собственным форматом объектных файлов, .classфайлом. Файлы классов содержат огромное количество информации об их содержимом, что позволяет среде выполнять действия с классами во время выполнения, о которых механизм родной связи даже не мог мечтать. Эта информация должна где-то начинаться, и эта отправная точкаclass, Доступная информация позволяет скомпилированному коду описывать себя без необходимости в отдельных файлах, содержащих описание в исходном коде, как это было бы на C, C ++ или других языках. Это дает вам все преимущества безопасности типов, использующие отсутствие родного связывания даже во время выполнения, и позволяет выловить произвольный класс из файла, используя отражение, и использовать его с гарантированной ошибкой, если что-то не совпадает ,

Если вы еще не поняли, вся эта безопасность идет с компромиссом: все, что вы связываете с программой на Java, должно быть Java. (Под «ссылкой» я имею в виду, что что-то в одном файле класса ссылается на что-то в другом.) Вы можете связать (в собственном смысле) с нативным кодом, используя JNI , но есть неявный контракт, который говорит, что если вы нарушите нативную сторону Вы владеете обеими частями.

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


ссылка битая, получите ссылку на веб-архив
noɥʇʎԀʎzɐɹƆ

14

Я считаю, что ответ, согласно Википедии, заключается в том, что Java была разработана, чтобы быть простой и объектно-ориентированной. Функции предназначены для работы с классами, в которых они определены. При таком образе мышления иметь функции вне класса не имеет смысла. Я собираюсь сделать вывод, что Java не позволяет этого, потому что он не подходит для чистого ООП.

Быстрый поиск в Google для меня мало что дал по мотивам дизайна языка Java.


6
Разве существование примитивных типов не исключает Java из «чистого ООП»?
Раду Мурзеа

5
@SoboLAN: Да, и добавление функций сделает его еще менее «чистым ООП».
Джорджио

8
@SoboLAN, который зависит от вашего определения «чистого ООП». В какой-то момент все это комбинация битов в памяти компьютера, в конце концов ...
jwenting

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

2
Зависит от вашего определения «чистого ООП»: каждое значение данных - это объект, а каждая операция с данными - это вызов метода результата, полученный путем передачи сообщения. Насколько я знаю, здесь больше ничего нет.
Джорджио

11

Реальный вопрос в том, что было бы полезно продолжать делать так, как С ++, и какова была первоначальная цель заголовочного файла? Короткий ответ заключается в том, что стиль файла заголовка позволяет быстрее компилировать большие проекты, в которых многие классы могут ссылаться на один и тот же тип. Это не обязательно в JAVA и .NET из-за природы компиляторов.

Смотрите этот ответ здесь: действительно ли заголовочные файлы хороши?


1
+1, хотя на самом деле я слышал, как люди говорят, что им нравится иметь разделение между открытым интерфейсом и частной реализацией, которую предоставляют заголовочные файлы. Эти люди, конечно, не правы. ;)
vaughandroid

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

1
Я никогда не пойму желательности необходимости просматривать файлы 3+, чтобы понять, как работает простой экземпляр класса.
Эрик Реппен

@ErikReppen, как правило, интерфейс (или заголовочный файл в C) - это то, что клиенты получают в удобочитаемой форме для написания своих решений, а остальные предоставляются только в двоичном виде (конечно, в Java, нет необходимости предоставлять источники из интерфейсов, файлы классов и Javadoc будет делать).
С

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

3

Файл Java представляет класс. Если бы у вас была процедура вне класса, какой была бы область? Будет ли это глобальным? Или это будет принадлежать классу, который представляет файл Java?

Предположительно, вы поместили его в этот файл Java вместо другого файла по причине - потому что он идет с этим классом больше, чем с любым другим классом. Если процедура вне класса была на самом деле связана с этим классом, то почему бы не заставить ее войти в тот класс, к которому она принадлежит? Java обрабатывает это как статический метод внутри класса.

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

Единственным возможным недостатком этого ограничения Java является то, что если у вас действительно есть глобальные процедуры, которые не связаны с каким-либо классом, вы в конечном итоге создадите класс MyGlobals для их хранения и импортируете этот класс во все остальные ваши файлы, которые используют эти процедуры. ,

Фактически, для работы механизма импорта Java необходимо это ограничение. Имея все доступные API, компилятор java должен точно знать, что компилировать и что компилировать, таким образом, явные операторы импорта в верхней части файла. Без того , чтобы сгруппировать глобал в класс искусственного, как бы вы сказать , компилятор Java , чтобы собрать ваши глобал и не любые и все Глобал на вашем пути к классам? Как насчет столкновения пространства имен, когда у вас есть doStuff (), а у кого-то еще есть doStuff ()? Это не сработало бы. Принудительное указание MyClass.doStuff () и YourClass.doStuff () устраняет эти проблемы. Принудительное выполнение ваших процедур внутри MyClass, а не за его пределами, только разъясняет это ограничение и не накладывает дополнительных ограничений на ваш код.

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


3

Я думаю, что большинство людей, которые ответили, и их избиратели неправильно поняли вопрос. Это отражает то, что они не знают C ++.

«Определение» и «Декларация» - это слова с очень специфическим значением в 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 (Правило одного определения). Но у него есть и другие преимущества: он позволяет сразу увидеть интерфейс большого класса.

В Java вы можете аппроксимировать этот эффект абстрактными классами или интерфейсами, но они не могут иметь то же имя, что и класс реализации, что делает его довольно неуклюжим.


и это показывает, что вы оба не понимаете ни людей, ни Java. Вы можете использовать одно и то же имя для интерфейса и реализации, если они не находятся в одном пакете.
jwenting

Я считаю, что пакет (или пространство имен, или внешний класс) является частью имени.
Эрик ван Вельзен

2

Я думаю, что это артефакт механизма загрузки классов. Каждый файл класса является контейнером для загружаемого объекта. Там нет места "вне" файлов классов.


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

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

@ddyer Вы никогда не видели класс Foo $ 2 $ 1.класс? (см. имена файлов классов внутреннего класса 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 # заключается в том, что я никогда не видел и не работал с устаревшей кодовой базой, где большинство разработчиков, похоже, понимали ценность базового ООП. Когда вы приходите в игру, чтобы обернуть все в классе, возможно, будет проще предположить, что вы выполняете ООП, а не функцию спагетти, замаскированную в виде массивных цепочек из 3-5 строк.

Тем не менее, нет ничего более ужасного, чем JavaScript, написанный кем-то, кто знает только достаточно, чтобы быть опасным, и не боится показать это. И я думаю, что это общая идея. Этот парень предположительно должен играть по тем же правилам в Java. Я бы сказал, что ублюдок всегда найдет выход.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.