Сравнение C Sharp и Java (Vjgfuyuny C Sharp n Java)

Перейти к навигации Перейти к поиску

C# и Java — два языка программирования, развивающих язык программирования C++, с синтаксисом, который во многом наследует синтаксис C++, и созданных во многом в условиях конкуренции, и, вследствие этого, обладающих определённым сходством, а также имеющих и ряд различий.

Общий взгляд

[править | править код]

Языки C# и Java появились в разное время. Язык Java был создан задолго до появления C#. Под названием Oak Java был разработан компанией Sun Microsystems в 1990 г., а в 1995 была выпущена первая бета-версия Java. Создание C# было анонсировано в 2000 году, а в 2002 году вышла первая версия платформы .NET, поддерживающей C#. Таким образом, если Java создавался опираясь в большей степени на опыт языков Objective C и C, то для C# такой опорой являлись C++ и сам Java[1]. И, несмотря на своё название, C# оказался ближе к Java, чем к C++[2][3].

С точки зрения разработчика языки Java и C# очень похожи. Оба языка являются строго типизированными, объектно-ориентированными. Оба вобрали в себя многое из синтаксиса C++, но в отличие от C++, проще в освоении для начинающих. Оба позаимствовали из C набор основных ключевых слов и служебных символов, в том числе фигурные скобки для выделения блоков. Оба языка реализуют управление памятью с помощью сборки мусора. Оба языка сопровождаются богатыми коллекциями библиотек. Но есть в языках также свои особенности и различия, сильные и слабые стороны. C# учёл многие недостатки Java, и исправил их в своей реализации[4]. Но и Java не стоит на месте, развиваясь параллельно с C#.

Кик Рэдек из Microsoft считает С# более сложным языком, чем Java[1]. По его мнению, «язык Java был построен таким образом, чтобы уберечь разработчика от стрельбы себе в ногу» (англ. «Java was built to keep a developer from shooting himself in the foot»), а «С# был построен так, чтобы дать разработчику пистолет, но оставить его на предохранителе» (англ. «C# was built to give the developer a gun but leave the safety turned on»).

  • обозначения начала/конца блока кода фигурными скобками;
  • обозначения, ассоциативность и приоритет большинства встроенных операций (присвоение, арифметические, логические, побитовые операции, операции инкремента/декремента, тернарная условная операция «?:»);
  • синтаксис описания и использования переменных и функций (порядок «тип имя», использование модификаторов, обязательность скобок для функций, описание формальных параметров);
  • синтаксис всех основных конструкций: условного оператора, циклов, оператора множественного выбора;

Синтаксических различий также достаточно.

Синтаксис Java C#
Импорт статических имён
(import static)
позволяет отдельно импортировать некоторые или все статические методы и переменные класса и использовать их имена без квалификации в импортирующем модуле Начиная с C# 6.0 это было введено (например (using static System.Math)).
Оператор switch Аргумент оператора switch должен относиться либо к целочисленному, либо к перечислимому типу. Начиная с версии Java 7 в операторе switch стало возможно использовать строковые литералы и это различие с C# было устранено[2]. Поддерживаются как константные типы, так и строковые. В C# 7 появилась поддержка ссылочных типов и null. Также возможно задавать дополнительные условия для блока case с помощью ключевого слова when[5]. В отличие от Java, прямого перехода к следующему блоку case нет. Для перехода к следующему блоку case, нужно использовать оператор goto[2].
Оператор перехода goto от использования goto сознательно отказались, однако существует механизм, позволяющий выйти на внешний цикл из вложенного, пометив его меткой и используя операторы break, continue вместе с меткой (continue <метка>;) goto сохранился, его обычное использование — передача управления на разные метки case в операторе switch и выход из вложенного цикла
Константы констант как таковых нет, вместо них используются статические переменные класса с модификатором final — эффект от их использования точно такой же отдельное понятие именованной типизированной константы и ключевое слово const
Точность вычислений с плавающей точкой Java содержит конструкцию strictfp, гарантирующую одинаковые результаты операций с плавающей точкой на всех платформах. C# полагается на реализацию, гарантии строго одинаковых результатов вычислений нет.
Отключение проверок В Java все динамические проверки включаются/выключаются только на уровне пакета C# содержит конструкции checked и unchecked, позволяющие локально включать и выключать динамическую проверку арифметического переполнения.


Механизм работы с динамическими данными и сборка мусора

[править | править код]

Оба языка реализуют одну модель работы с динамическими данными: объекты создаются динамически с помощью конструкции new, среда исполнения отслеживает наличие ссылок на них, а сборщик мусора периодически очищает память от объектов, ссылок на которые нет. Для оптимизации сборки мусора спецификации языков и сред исполнения не содержат ограничений на время жизни объекта после удаления последней ссылки на него — сборщик работает независимо от исполнения программы, поэтому реальное уничтожение объекта может произойти в любой момент после удаления последней ссылки до завершения работы программы. В реальности сборщики мусора оптимизируют исполнение так, чтобы обеспечить приемлемый расход памяти при минимальном замедлении работы программ.

И в Java, и в C# есть сильные и слабые ссылки на объекты. Оба языка поддерживают методы-финализаторы. Из-за неопределённости момента удаления объекта финализаторы не могут использоваться для освобождения системных ресурсов, занятых объектом, что вынуждает создавать дополнительные методы для «очистки» объекта и вызывать их явно.

C# содержит в стандартной библиотеке интерфейс IDisposable и специальную конструкцию using, гарантирующую своевременный вызов метода очистки:

// DisposableClass реализует интерфейс IDisposable и описывает его метод Dispose
class DisposableClass : IDisposable
{
  public void Dispose()
  {
    // ... Здесь освобождаются занятые экземпляром ресурсы
  }
}

using (DisposableClass obj = new DisposableClass(...))
{
  // ... Код, использующий объект obj
}
// ... Здесь для объекта obj гарантированно уже вызван метод Dispose

В Java подобной конструкции нет и очистка объектов может быть выполнена только вручную:

class AnyClass {
  void clear() {
    // ... Здесь находится код очистки 
  }
}

AnyClass obj =  new AnyClass(...);
try {
  // ... код, использующий объект obj
}
finally {
  obj.clear(); // - явный вызов метода очистки объекта по завершении его использования
}

В Java 7 добавлена конструкция «try-with-resources», обеспечивающая автоматическую очистку полностью аналогично C#:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine(); 
}

При выходе из блока try все объекты, которым присвоено значение в его заголовке (круглых скобках перед блоком операторов), будут очищены. Обязательное условие — классы этих объектов должны реализовывать системный интерфейс java.lang.AutoCloseable.

Java позволяет зарегистрировать слушателя (listener), который будет получать сообщения, когда ссылка подвергается сборке мусора, что даёт улучшение производительности WeakHashMap.

C# (точнее, среда CLR) позволяет отменить выполнение финализатора для данного объекта методом GC.SuppressFinalize(obj) (напр., соединение SQL на файловом потоке). Это бывает полезным, поскольку финализация считается относительно дорогой операцией при сборке мусора, и объект с финализатором «живёт» дольше.

Объектные средства

[править | править код]

Оба языка — объектно-ориентированные, с синтаксисом, унаследованным от C++, но значительно переработанным. Код и данные могут описываться только внутри классов.

Инкапсуляция

[править | править код]

В Java модификатор protected в описании, помимо доступа из классов-потомков, разрешает доступ из всех классов, входящих в тот же пакет, что и класс-владелец.

В C# для объектов, которые должны быть видны в пределах сборки (примерный аналог пакета Java) введён отдельный модификатор internal (аналог default в Java), а protected сохраняет свой изначальный смысл, взятый из C++ — доступ только из классов-потомков. Допускается комбинировать internal и protected — тогда получится область доступа, соответствующая protected в Java.

Внутренние классы

[править | править код]

Оба языка позволяют определить класс внутри класса.

В Java внутренние классы используются для эмуляции замыканий. Внутренние классы Java имеют доступ к нестатическим членам родительского класса, то есть «знают о this»; кроме того, внутри методов можно определять локальные классы, имеющие доступ по чтению к локальным переменным, и безымянные (анонимные) локальные классы, которые фактически позволяют создавать экземпляры объектов и интерфейсов, перекрывающие методы своего класса, непосредственно в месте их использования. На этом механизме в Java-программах может строиться обработка событий (событие генерирует вызов метода, в исходном классе-обработчике являющегося абстрактным; там, где нужен конкретный обработчик события, программист создаёт экземпляр локального анонимного класса — наследника базового класса-обработчика и непосредственно использует его). Таким образом, исчезает необходимость в специальном типе и синтаксической поддержке для событий, но сам код, создающий обработчики, несколько более сложен для понимания. В частности, сложнее становятся области видимости переменных.

В C# есть замыкания и лямбды. Подход C# более напоминает C++: внутренние классы в C# имеют доступ только к статическим членам внешнего класса, а для доступа к нестатическим членам нужно явно указывать экземпляр внешнего класса. Локальные внутренние классы в C# не поддерживаются.

В Java начиная с 8 версии также появились лямбда-выражения.

В обоих языках методы определяются через функции класса. Тело метода располагается внутри описания класса. Поддерживаются статические методы, абстрактные методы. В C# также есть явная реализация методов интерфейса, что позволяет классу реализовывать методы интерфейса отдельно от собственных методов или давать разные реализации одноимённых методов, принадлежащих двум разным интерфейсам.

В Java 8 появился оператор default, позволяющий определить реализацию методов интерфейса "по умолчанию". Таким образом, реализующий интерфейс класс избавляется от обязанности реализовывать default-методы, но может перекрыть их.

Аналогичная возможность появилась в C# 9.0 - в рамках описания интерфейса разработчик может указать тела для любых методов. Точно так же, как и в Java, при реализации интерфейса разработчик может перекрыть default реализацию, но не обязан этого делать.

В Java примитивные типы (byte, int, double, float, boolean и пр.) передаются по значению, а для остальных (объектные) по значению передается ссылка на объект.

В C# в дополнение к примитивным типам передаются по значению структуры (struct) (т. н. значимые типы), остальные типы передаются по ссылке (т. н. ссылочные типы). C# также поддерживает явное описание передачи параметров по ссылке (ключевые слова ref, in и out). При использовании out компилятор контролирует наличие в методе присваивания значения. Также поддерживается возврат значений из методов по ссылке при помощи конструкции ref typename.

В C# разрешается давать методам наименование, совпадающее с названием класса, таким образом создается конструктор класса[6], (В Java программист также может определить конструктор, который будет на самом деле являться методом)[4].

В C# поддерживаются несколько специальных подвидов синтаксиса при описании тел методов:

- блоки итераторов: методы, возвращающие IEnumerable<T> или IEnumerator<T>, могут императивно описывать последовательность возвращаемых значений при помощи конструкций yield return и yield break.

- асинхронные методы: методы, возвращающие Task/ValueTask/Task<T>/ValueTask<T> и помеченные ключевым словом async, могут в своих телах использовать конструкцию await при вызове других методов, возвращающих Task/ValueTask/Task<T>/ValueTask<T>. Это позволяет реализовывать кооперативную многозадачность, т.к. конструкция await прерывает выполнение текущего метода до готовности запрошенного значения и передаёт управление шедулеру, который может приступить к выполнению следующей готовой задачи без передачи управления ядру ОС.

Начиная с C# 8.0 можно комбинировать обе возможности, порождая асинхронные итераторы - реализацию интерфейсов IAsyncEnumerable<T> / IAsyncEnumerator<T> с использованием конструкций await, yield return и yield break.

Виртуальность методов

[править | править код]

C# копирует концепцию виртуальных методов C++: виртуальный метод должен быть явно объявлен с ключевым словом virtual, прочие методы виртуальными не являются. Такое выборочное объявление виртуальных методов введено в C#, так как объявление всех методов виртуальными может сильно замедлить исполнение[7]. Кроме того, C# требует явного объявления о перекрытии виртуального метода в производном классе ключевым словом override. Если требуется скрыть (hide) виртуальный метод, то есть просто ввести новый метод с тем же именем и сигнатурой, требуется указать ключевое слово new (в случае отсутствия которого компилятор выдаёт предупреждение). Запрещается скрывать (заслонять) абстрактные методы. Объявление override-метода с ключевым словом sealed запрещает переопределять (override) этот метод в классах-потомках, однако по-прежнему позволяет скрыть его.

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

Подход Java синтаксически проще, он гарантирует, что всегда вызывается метод именно того класса, к которому относится объект. С другой стороны, виртуальность действительно нужна не всегда, а накладные расходы на вызов виртуальных методов несколько больше, поскольку эти вызовы обычно не проходят инлайн-подстановку и требуют дополнительного обращения к таблице виртуальных методов (хотя некоторые реализации JVM, включая реализацию Sun, реализуют инлайн-подстановку наиболее часто вызываемых виртуальных методов).

Виртуальность всех методов потенциально небезопасна: если программист по ошибке объявит метод, который уже есть в базовом классе, не имея намерения его перекрывать, а просто не обратив внимания на то, что такой метод уже есть, то новый метод перекроет одноимённый метод в базовом классе, хотя это и не входит в намерения разработчика. В C# подобная ошибка тоже возможна, но компилятор выдаст предупреждение, что перекрывающий метод объявлен без new и override. В Java 5 появился аналогичный механизм — если метод перекрывает виртуальный метод класса-предка, компилятор выдаёт предупреждение; чтобы предупреждение не выдавалось, необходимо отметить перекрывающий метод аннотацией «@Override».

Типы данных

[править | править код]

Примитивные типы

[править | править код]

Оба языка поддерживают идею примитивных типов (которые в C# являются подмножеством типов-значений — value types), и оба для трансляции примитивных типов в объектные обеспечивают их автоматическую «упаковку» в объекты (boxing) и «распаковку» (unboxing) (в Java — начиная с версии 5). В C# к примитивным типам можно обращаться как к объектам, и это является одной из причин, с которой связывают популярность C#. В Java разделяют примитивные типы и объектные, для обращения к примитивным типам как к объектам применяются классы-обёртки (например для типа int — обёртка Integer), это вызывает недовольство многих разработчиков на Java[8][9].

Примитивных типов в C# больше, чем в Java, за счёт беззнаковых целых типов (unsigned), имеющихся парно ко всем знаковым, и специального типа decimal для высокоточных вычислений с фиксированной запятой (в Java для этого служат классы java.math.BigInteger и java.math.BigDecimal).

В Java отказались от большинства беззнаковых типов ради упрощения языка. Одна из известных проблем с такими типами — сложность определения типа результата арифметических операций над двумя аргументами, один из которых является знаковым, другой — беззнаковым. Независимо от того, какие правила в отношении подобных операций примет язык, в некоторых ситуациях это приведёт к ошибкам (например, в C++ операция над знаковым и беззнаковым значением даёт беззнаковый результат; в итоге при счёте 16-битными числами выражение «40000 / (-4)» даст в результате не −10000, а 55536). Однако этот отказ порождает свои проблемы; поскольку значительная часть технических данных, используемых на низком уровне (например, различные служебные данные, передаваемые оборудованием и возвращаемые функциями API операционной системы) имеет именно беззнаковый целый тип, и отсутствие таких типов приводит к необходимости выполнять небезопасные операции конвертации данных, а в ряде случаев — заменять использование простой беззнаковой арифметики неочевидными комбинациями побитовых операций.

Структуры (записи)

[править | править код]

C# позволяет создавать пользовательские типы-значения, используя ключевое слово struct. Это прямое наследие языка C++ от которого создатели Java сознательно отказались. В отличие от экземпляров классов, экземпляры типов-значений создаются не в куче, а на стеке вызовов или в составе экземпляра объекта, в котором они объявлены, что в некоторых случаях повышает производительность кода. С точки зрения программиста они подобны классам, но с несколькими ограничениями: у них не может быть явного конструктора без параметров (но может быть конструктор с параметрами), от них нельзя наследовать[10] и они не могут явно наследоваться от других типов (всегда неявно наследуются от класса System.ValueType), но могут реализовывать интерфейсы. Кроме того, значения struct-типов поддерживают логику присваивания значения (то есть присваивание одной переменной значения другой приводит не к копированию ссылки на один и тот же объект, а к копированию значений полей одной структуры в другую). Начиная с версии 1.6, в Java тоже имеется возможность создавать объекты на стеке, но происходит это автоматически без участия пользователя.

В Java для того, чтобы от класса нельзя было наследоваться, его можно объявить финальным final, тем самым получив частичный аналог конструкции struct (копирование по значению при этом поддерживаться не будет всё равно). В C# для тех же целей используется модификатор sealed[11].

Перечислимые типы

[править | править код]

Перечислимые типы в C# происходят от примитивных целочисленных типов. Допустимым значением перечислимого типа является любое значение лежащего в его основе примитивного, хотя для его присваивания может потребоваться явное приведение типа. Это позволяет комбинировать значения перечислимого типа побитовой операцией «или», если они являются битовыми флагами.

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

Массивы и коллекции

[править | править код]

Массивы и коллекции тоже получили выражение в синтаксисе обоих языков, благодаря особой разновидности цикла for (цикл по коллекции, известный также как цикл foreach). В обоих языках массив является объектом класса Array, но в Java он не реализует какие-либо интерфейсы коллекций, хотя по массивам возможна итерация циклом for(:). Оба языка имеют в стандартной библиотеке классы типичных коллекций.

В Java могут быть объявлены, строго говоря, только одномерные массивы. Многомерный массив в Java — массив массивов. В C# есть как настоящие многомерные массивы, так и массивы массивов, которые в C# обычно называются «неровными», или «ступенчатыми» (jagged). Многомерные массивы всегда «прямоугольные» (говоря в двумерной терминологии), в то время как массивы массивов могут хранить строки разной длины (опять-таки в двумерном случае, в многомерном аналогично). Многомерные массивы ускоряют доступ к памяти (для них указатель разыменовывается только один раз), а неровные массивы работают медленнее, но экономят память, когда не все строки заполнены. Многомерные массивы требуют для своего создания лишь один вызов оператора new, а ступенчатые требуют явно выделять память в цикле для каждой строки.

Параметризованные (обобщённые) типы

[править | править код]

В обоих языках типы могут быть параметризованными, что поддерживает парадигму обобщённого программирования. Синтаксически определение типов достаточно близко — в обоих языках оно унаследовано от шаблонов (templates) C++, хотя и с некоторыми модификациями.

Обобщения типов в Java являются чисто языковой конструкцией и реализованы лишь в компиляторе. Компилятор заменяет все обобщённые типы на их верхние границы и вставляет соответствующее приведение типов в те места, где используется параметризируемый тип. В результате получается байт-код, который не содержит ссылок на обобщённые типы и их параметры. Такая техника реализации обобщённых типов называется затиранием типов (type erasure). Это означает, что информация об исходных обобщённых типах во время выполнения недоступна, и обусловливает некоторые ограничения, такие как невозможность создавать новые экземпляры массивов из аргументов обобщённого типа. Среда выполнения Java не знакома с системой обобщённых типов, вследствие чего новым реализациям JVM понадобились лишь минимальные обновления для работы с новым форматом классов.

C# пошёл другим путём. Поддержка обобщённости была интегрирована в саму виртуальную среду выполнения, впервые появившись в .NET 2.0. Язык здесь стал лишь внешним интерфейсом для доступа к этим возможностям среды. Как и в Java, компилятор производит статическую проверку типов, но в дополнение к этому JIT производит проверку корректности во время загрузки. Информация об обобщённых типах полностью присутствует во время выполнения и позволяет полную поддержку рефлексии обобщённых типов и создание их новых реализаций.

Подход Java требует дополнительных проверок во время выполнения, не гарантирует, что клиент кода будет следовать соответствию типов, и не обеспечивает рефлексии для обобщённых типов. Java не позволяет специализировать обобщённые типы примитивными (это можно сделать только заворачивая примитивные типы в классы), в то время как C# обеспечивает обобщение как для ссылочных типов, так и для типов-значений, включая примитивные. Вместо этого Java предлагает использование завёрнутых примитивных типов в качестве параметров (напр., List<Integer> вместо List<int>), но это даётся ценой дополнительного выделения динамической памяти. Как в Java, так и в C# специализации обобщённого типа на разных ссылочных типах дают одинаковый код[12], но для C# среда выполнения динамически генерирует оптимизированный код при специализации на типах-значениях (например, List<int>), что позволяет их хранить и извлекать из контейнеров без операций за- и разворачивания.

Обработка событий

[править | править код]

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

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

Замыкания включены в Java SE 8 en:Java version history#Java SE 8. Эти замыкания, как делегаты в C#, имеют полный доступ ко всем локальным переменным в данной области видимости, а не только доступ для чтения к переменным, помеченным словом final (как с анонимными вложенными классами).

Перегрузка операторов

[править | править код]

C# включает перегрузку операторов и задаваемое пользователем приведение типов, знакомые программирующим на C++. C# её поддерживает с некоторыми ограничениями, обеспечивающими логическую целостность, что при осторожном использовании помогает сделать код более лаконичным и читаемым. Java не включает перегрузку операций во избежание злоупотреблений ею и для поддержания простоты языка[13][14][15].

C# поддерживает концепцию «свойств» — псевдополей класса, к которым обеспечивается полностью контролируемый доступ путём создания методов для извлечения и записи значения поля. Описания свойств производятся с помощью конструкций get и set. В Java отсутствует такая концепция[16] (хотя никаких ограничений, чтобы реализовать её с помощью традиционных методов, нет).

C# также включает так называемые индексаторы, которые можно считать особым случаем перегрузки операций (аналогичным перегрузке operator[] в C++), или параметризованными свойствами. Индексатор — это свойство с именем this[], которое может иметь один или более параметров (индексов), причём индексы могут быть любого типа. Это позволяет создавать классы, экземпляры которых ведут себя подобно массивам / Map:

myList[4] = 5;
string name = xmlNode.Attributes["name"];
orders = customerMap[theCustomer];

Использование свойств не одобряется некоторыми авторитетными программистами. В частности, Джеффри Рихтер пишет:

«Лично мне свойства не нравятся, и я был бы рад, если бы их поддержку убрали из Microsoft .NET Framework и сопутствующих языков программирования. Причина в том, что свойства выглядят как поля, на самом деле являясь методами.»[17]

Согласно общепринятому в C# стилю именования, имена свойств визуально отличаются от полей тем, что начинаются с прописной буквы.

Условная компиляция

[править | править код]

C#, в отличие от Java, поддерживает условную компиляцию с использованием директив препроцессора. В нём также есть атрибут Conditional, означающий, что указанный метод вызывается только тогда, когда определена данная константа компиляции. Таким путём можно вставлять в код, например, проверки допущений (assertion checks), которые будут работать только в отладочной версии, когда определена константа DEBUG. В стандартной библиотеке .NET таков метод Debug.Assert().

Java версий 1.4 и выше включает в язык возможность проверки допущений, включаемую во время выполнения. Кроме того, конструкции if с константными условиями могут разворачиваться на этапе компиляции. Существуют сторонние реализации препроцессоров для Java, они используются преимущественно при разработке приложений для мобильных устройств.

Пространства имён, сборки, пакеты

[править | править код]

Внешние модули в Java и в C# подключаются сходным образом. В Java используется ключевое слово import, в С# — using. Пример[18]:

Существенным отличием между import в Java и using в C# является то, что C# использует концепцию пространств имён (namespace), напоминающую одноимённый механизм C++[18]. В Java используется концепция пакетов. Пространства имён никак не связаны с компилированными модулями (сборками, или assembly в терминологии Microsoft). Несколько сборок могут содержать одно и то же пространство имён, в одной сборке может объявляться несколько пространств имён, не обязательно вложенных. Модификаторы области видимости C# никак не связаны с пространствами имён. В Java же объявленные в одном пакете классы по умолчанию образуют единый компилированный модуль. Модификатор области видимости по умолчанию (отсутствие явного указания) ограничивает область видимости полей и методов класса пределами пакета.

В Java структура файлов и каталогов исходных текстов пакета по умолчанию связана со структурой пакета — пакету соответствует каталог, входящим в него подпакетам — подкаталоги этого каталога, файлы исходных текстов располагаются в каталогах, соответствующих пакету или подпакету, в который они входят. Таким образом, дерево исходных текстов повторяет структуру пакета. В C# местонахождение файла с исходным текстом никак не связано с его пространством имён.

Ни один из вариантов не обладает значительным превосходством в мощности, просто используются разные механизмы для разрешения неоднозначностей[18].

Расположение исходного текста в файлах

[править | править код]

В C# классы могут располагаться в файлах произвольным образом. Имя файла исходного кода никак не связано с именами определяемых в нём классов. Допускается расположить в одном файле несколько общедоступных (public) классов. Начиная с версии 2.0, C# позволяет также разбить класс на два и более файла (ключевое слово partial). Последняя особенность сделана для разделения кода, который пишется человеком, и генерируемого кода. Она используется, например, визуальными средствами построения интерфейса: часть класса, в которой находятся поля и методы, управляемые конструктором интерфейса, выделяются в отдельный файл.

В Java каждый файл может содержать только один общедоступный (public) класс, причём Java требует, чтобы имя файла совпадало с именем этого класса, что исключает путаницу в именах файлов и классов. Более того, согласно рекомендуемому Sun соглашению об оформлении кода, размер файла исходного кода не должен превышать 2000 строк кода, поскольку в файле большего размера труднее разбираться. Большой размер файла также считается признаком плохого проектирования.

Исключения

[править | править код]

Оба языка поддерживают механизм обработки исключений, синтаксически оформленный совершенно одинаково: в языке имеется оператор генерации исключения throw и блок обработки исключений try{}catch(){}finally{}, обеспечивающий перехват возникших внутри блока исключений, их обработку, а также гарантированное выполнение завершающих действий.

Java поддерживает проверяемые (checked) исключения: программист должен явно указать для каждого метода типы исключений, которые метод может выбросить, эти типы перечисляют в объявлении метода после ключевого слова throws. Если метод использует методы, выбрасывающие проверяемые исключения, он должен либо явно перехватывать все эти исключения, либо содержать их в собственном описании. Таким образом, код явно содержит перечень исключений, которые могут быть выброшены каждым методом. Иерархия типов исключений содержит также два типа (RuntimeException и Error), наследники которых не являются проверяемыми и не должны описываться. Они выделены для исключений времени выполнения, которые могут возникнуть в любом месте, либо обычно не могут быть обработаны программистом (например, ошибки среды исполнения), и не должны указываться в объявлении throws.

C# проверяемые исключения не поддерживает. Их отсутствие является сознательным выбором разработчиков. Андерс Хейлсберг, главный архитектор C#, считает, что в Java они были в какой-то степени экспериментом и себя не оправдали [1].

Вопрос о пользе проверяемых исключений дискуссионный. Подробнее см. статью Обработка исключений.

Параллельное программирование

[править | править код]

В целом механизмы параллельного программирования в C# аналогичны тем, что предоставляет Java, различие состоит в деталях реализации. В обоих случаях имеется библиотечный класс Thread, реализующий понятие «потока». Java предоставляет два способа создания собственных потоков: либо путём расширения класса Thread, либо путём реализации интерфейса Runnable. В обоих случаях программист должен определить наследуемый (входящий в интерфейс) метод run(), содержащий тело потока — код, который будет в нём выполняться. C# вместо этого использует механизм делегатов: для создания потока создаётся экземпляр стандартного класса Thread, которому передаётся в виде параметра конструктора делегат, содержащий метод — тело потока.

В обоих языках есть возможность создать синхронно исполняемый блок кода; в Java это делается с помощью оператора synchronized(), в C# — оператором lock(). В Java имеется также возможность объявлять синхронные методы, используя модификатор synchronized в заголовке описания метода. Такие методы при исполнении блокируют свой объект-хозяин (таким образом, из синхронизированных методов класса, для одного и того же экземпляра, одновременно может выполняться только один и только в одном треде, остальные будут ждать). Аналогичная возможность в .NET реализуется с помощью атрибута реализации метода MethodImplAttribute MethodImplOptions.Synchronized, но, в отличие от Java, эта возможность формально не является частью языка C#.

В С# есть оператор lock(){} [2], захватывающий блокировку перед входом в блок и освобождающий её как при выходе, так и при выбросе исключения. Аналог в Java - synchronized () {}.

В C# 4.5 появились операторы async и await [3], а также новый класс Task, более эффективный, чем Thread для коротких параллельных задач. На этом же механизме реализована эффективная параллельная обработка перечислимых типов (Enumerable контейнеров).[4]

В обоих языках доступны также идентичные средства синхронизации, основанные на отправке и ожидании сигнала от одного потока к другому (другим). В Java это методы notify(), notifyAll() и wait(), в C# — методы Pulse(), PulseAll(), Wait() (тройки методов функционально попарно аналогичны). Различие состоит лишь в том, что в Java эти методы (и, соответственно, функциональность монитора) реализуется в классе Object, поэтому для синхронизации не требуется никаких дополнительных библиотек, а в C# эти методы реализованы как статические в отдельном библиотечном классе Monitor (неявно используемом оператором lock). В C# стандартная библиотека содержит также несколько дополнительных примитивов синхронизации параллельного исполнения потоков: мьютексы, семафоры, синхронизирующие таймеры. С версии 1.5 в JDK SE включены пакеты java.util.concurrent, java.util.concurrent.atomic и java.util.concurrent.locks содержащие исчерпывающий набор средств для реализации параллельных вычислений.

Низкоуровневый код

[править | править код]

Java Native Interface (JNI) позволяет программам вызывать из Java низкоуровневые, системно-зависимые функции (например, библиотек winAPI). Как правило, JNI используется при написании драйверов. При написании JNI-библиотек разработчик должен использовать специальный API, предоставляемый бесплатно. Выпускаются также специализированные библиотеки для взаимодействия Java с COM.

Технология Platform Invoke (P/Invoke), реализованная в .NET, позволяет вызывать из C# внешний код, который Microsoft называет неуправляемым. Через атрибуты в метаданных программист может точно управлять передачей (маршалингом) параметров и результатов, избегая таким образом необходимости дополнительного кода адаптации. P/Invoke предоставляет почти полный доступ к процедурным API (таким, как Win32 или POSIX), но не даёт прямого доступа к библиотекам классов C++.

.NET Framework предоставляет также мост между .NET и COM, позволяя обращаться к COM-компонентам так, как если бы они были родными объектами .NET, что требует дополнительных усилий программиста при использовании COM-компонент со сложными нетривиальными интерфейсами (например, в случае передачи структуры через массив байтов). В этих случаях приходится прибегать к unsafe коду (см. ниже) или другим обходным путям.

C# разрешает ограниченное использование указателей, которые проектировщики языков зачастую считают опасными. Подход C# в этом деле — требование ключевого слова unsafe при блоках кода или методах, использующих эту возможность. Это ключевое слово предупреждает пользователей такого кода о его потенциальной опасности. Оно также требует явного задания компилятору опции /unsafe, которая по умолчанию выключена. Такой «небезопасный» код используется для улучшения взаимодействия с неуправляемым API и иногда для повышения эффективности определённых участков кода.

C# также позволяет программисту отключить нормальную проверку типов и другие возможности безопасности CLR, разрешая использование переменных-указателей при условии применения ключевого слова unsafe. Преимуществом управляемого unsafe-кода над P/Invoke или JNI является то, что он позволяет программисту продолжать работать в знакомой среде C# для выполнения задач, которые при других методах потребовали бы вызова неуправляемого кода, написанного на другом языке.

Реализации

[править | править код]

Существуют многочисленные реализации JVM практически для всех присутствующих на рынке платформ. Разработкой JVM занимаются такие корпорации, как IBM, Sun Microsystems (с 2010 года Oracle), Bea и ряд других. Следует отметить, что Sun (Oracle) выпускает свою JVM как под своей собственной лицензией [5], так и под модифицированной (посредством т. н. «Classpath exception») лицензией GPLv2 [6] Архивировано 3 марта 2012 года..

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

C# тоже является кроссплатформенным стандартом. Его первичная платформа — Windows, но существуют и реализации для других платформ, самая значительная из которых — проект Mono.

.NET — это универсальная платформа разработки с открытым кодом, которую поддерживает корпорация Майкрософт и сообщество .NET на сайте GitHub. Она является кроссплатформенной (поддерживает Windows, macOS и Linux) и может использоваться для создания приложений для устройств, облака и Интернета вещей.

Технология ClickOnce предлагает функциональность, подобную Java Web Start, но она имеется только для клиентов Windows. Internet Explorer на Windows умеет показывать элементы интерфейса .NET Windows Forms, что даёт апплетоподобную функциональность, но ограничено конкретным браузером.

Стандартизация

[править | править код]

Развитие этих двух языков, и также их API, двоичных форматов и сред выполнения, управляется по-разному.

C# определён стандартами ECMA и ISO, которые задают синтаксис языка, формат выполнимых модулей (известный как CLI) и библиотеку базовых классов (Base Class Library, или BCL). Стандарты не включают многие новые библиотеки, реализованные Microsoft поверх стандартного каркаса, такие как библиотеки для баз данных, GUI и веб-приложений (Windows Forms, ASP.NET и ADO.NET). Однако Microsoft формально согласилась не преследовать в судебном порядке проекты сообщества за реализацию этих библиотек [7] (недоступная ссылка).

На сегодняшний день никакая составная часть среды Java не стандартизуется Ecma, ISO, ANSI или какой-либо другой сторонней организацией стандартов. В то время как Oracle сохраняет неограниченные исключительные юридические права на модификацию и лицензирование своих торговых марок Java, Oracle добровольно участвует в процессе, называемом Java Community Process (JCP), который позволяет заинтересованным сторонам предлагать изменения в любые Java-технологии Oracle (язык, инструментарий, API) через консультации и экспертные группы. По правилам JCP, любое предложение по изменению в JDK, среде выполнения Java или спецификации языка Java может быть односторонне отвергнуто Oracle, потому что для его одобрения требуется голос «за» со стороны Oracle. От коммерческих участников JCP требует членских взносов, в то время как некоммерческие организации и частные лица могут участвовать в нём бесплатно.

В то время как «Java» — торговая марка Oracle (ранее - Sun), и только Oracle может лицензировать имя «Java», существуют многочисленные свободные проекты, частично совместимые с Oracle Java. Например, GNU Classpath и GNU Compiler for Java (GCJ) поставляют свободную библиотеку классов и компилятор, частично совместимые с текущей версией Oracle Java[19]. В конце 2006 года Sun объявила, что весь исходный код Java, за исключением закрытого кода, на который они не сохраняют права, будет выпущен к марту 2007 года в качестве свободного программного обеспечения под видоизменённой лицензией GPL[20]. Oracle в настоящее время распространяет свою HotSpot Virtual Machine и компилятор Java под лицензией GPL, но на стандартную среду выполнения Java сейчас нет свободной лицензии[21][22]. Поскольку Oracle сохранит право собственности на свой исходный код Java, выпуск под лицензией GPL не запретит Oracle распространять несвободные или неоткрытые версии Java, или давать на это лицензии другим[23].

C#, среда CLR и большая часть соответствующей библиотеки классов стандартизированы и могут свободно реализовываться без лицензии. Уже реализовано несколько свободных систем C#, в том числе Mono и DotGNU. В проекте Mono также реализованы многие нестандартные библиотеки Microsoft путём изучения материалов Microsoft, аналогично GNU Classpath и Java. Целью проекта Mono является избежать посягательств на какие-либо патенты или копирайты, и проект может свободно распространяться и использоваться под лицензией GPL[24]. Microsoft в настоящее время распространяет Shared source-версию своей среды выполнения .NET для некоммерческого использования[25].

Использование

[править | править код]

Запуск программ

[править | править код]

Интерпретаторы Java могут устанавливаться копированием файлов и работают без ограничений на Windows как минимум с Windows 2000. Официальный C# фреймворк должен быть установлен в систему от администратора, определенные версии языка могут требовать определённую версию Windows.

Java построена на более открытой культуре с высокой конкурентностью фирм в различных областях функциональности. Большинство дополнительных библиотек доступно под свободными лицензиями с открытым исходным кодом. Также Sun приветствует практику описания какой-либо функциональности в виде спецификации (см. процесс JCP), оставляя реализацию сторонним разработчикам (возможно, предоставляя эталонную реализацию). Таким образом, решается вопрос независимости от производителя ПО.

Несмотря на существование Mono, C# тесно привязывает разработчиков к платформе Microsoft (включая ОС, офисные решения). Таким образом, пользователь программного обеспечения, написанного на .NET, часто не имеет выбора в использовании различных компонент системы. Это приводит к так называемому vendor-locking, при котором производитель стороннего ПО может диктовать покупателю практически любые условия на поддержку внедрённого проекта. В то время, как пользователь приложения Java, как правило, может сам выбрать поставщика дополнительного ПО (такого, как БД, ОС, сервера приложений и т. д.).

Однако, ныне стандартном для C# становится мультиплатформенный .NET Core. И проблема зависимости от платформы для C# ныне малоактуальна

Популярность и развитие

[править | править код]

Java старше, чем C# и построена на большой и активной пользовательской базе, став lingua franca во многих современных областях информатики, особенно таких, где задействованы сети. Java доминирует в курсах программирования американских университетов и колледжей, и литературы по Java сегодня намного больше, чем по C#. Зрелость и популярность Java привели к большему числу библиотек и API на Java (многие из которых открытые), чем на C#.

В отличие от Java, C# — язык относительно новый. Microsoft изучила существующие языки, такие как Java, Delphi и Visual Basic, и изменила некоторые аспекты языка для лучшего соответствия нуждам некоторых типов приложений.

В отношении Java можно услышать критику, что этот язык медленно развивается, в нём не хватает некоторых возможностей, которые облегчают модные шаблоны программирования и методологии. Язык C# критикуют в том, что его разработчики, возможно, слишком спешат угодить сиюминутным течениям в программировании ценой фокусировки и простоты языка. Очевидно, проектировщики Java заняли более консервативную позицию по добавлению крупных новых возможностей в синтаксис языка, чем в других современных языках — возможно, не желая привязать язык к течениям, которые в долгосрочной перспективе могут завести в тупик. С выпуском Java 5.0 эта тенденция во многом была нарушена, так как в ней ввели несколько крупных новых возможностей языка: цикл типа foreach, автоматическое заворачивание, методы с переменным числом параметров, перечислимые типы, обобщённые типы и аннотации (все они присутствуют и в C#). Начиная с Java 8, началось активное внедрение новых функций, в частности: лямбда-выражения, ключевое слово var, модульность в рамках проекта Jigsaw и так далее.

C#, в свою очередь, развивается быстрее, гораздо слабее ограничивая себя в добавлении новых проблемно-ориентированных возможностей. Особенно эта тенденция проявилась в версии C# 3.0, в которой, например, появились SQL-подобные запросы. (Новые возможности при этом строятся так, чтобы язык оставался языком общего назначения. Подробнее о C# 3.0 см в статье о C#). Проблемно-ориентированные дополнения к Java рассматривались, но, по крайней мере на сегодняшний день, были отвергнуты.

С момента появления C# он постоянно сравнивается с Java. Невозможно отрицать, что C# и его управляемая среда CLR многим обязаны Java и её JRE (Java Runtime Environment).

Можно спорить, является ли разработка C# в какой-то степени результатом признания Майкрософтом того, что среда управляемого кода, где лидирует Java, имеет множество достоинств в растущем сетевом мире, особенно при появлении интернета на устройствах, отличных от персональных компьютеров, и при растущей важности сетевой безопасности. До создания C# Microsoft модифицировала Java (создав J++), с тем чтобы добавить возможности, работающие только на ОС Windows, нарушив таким образом лицензионное соглашение Sun Microsystems. Пока Microsoft находилась на второй фазе своей бизнес-стратегии, известной как «Embrace, Extend, and Extinguish», развитие J++ было остановлено иском, поданным Sun’ом. Будучи лишённой возможности разрабатывать клон Java с нужными ей свойствами, Microsoft создала альтернативу, которая больше соответствовала их потребностям и видению будущего.

Несмотря на такое беспокойное начало, становится всё более очевидным, что два языка редко конкурируют друг с другом на рынке. Java доминирует в мобильном секторе и имеет много приверженцев на рынке веб-приложений. C# получил хорошее признание на рынке настольных приложений Windows и благодаря ASP.NET, C# также является игроком и на рынке веб-приложений.

Настольные приложения

[править | править код]

Для обоих языков имеется набор библиотек, предоставляющих возможности построения интерфейса пользователя для настольных приложений. В случае Java это мультиплатформенные библиотеки Swing и SWT, а также платформа JavaFX, позволяющая создавать RIA-приложения. В принципе, любая из них позволяет создавать кроссплатформенные настольные приложения на Java.

Для C# на платформе Windows основными платформами для разработки настольных графических приложений являются платформы Windows Forms и WPF. Для разработки под Windows 8 существует специальная платформа WinRT. Для разработки под Windows 10 существует специальная платформа UWP. Для прочих платформ используется библиотека gtk#, выполненная в рамках проекта Mono. Попытки свободной реализации Windows.Forms предпринимались и предпринимаются (например, в проекте DotGNU), однако они, в силу закрытости оригинала, неизбежно страдают вторичностью и неполнотой, вряд ли могут конкурировать с реализацией от Microsoft и потому могут применяться разве что для запаздывающего портирования Windows-приложений на другие платформы. Разработки, изначально базирующиеся на Windows, строятся обычно на Windows.Forms, и их перенос на другую платформу становится затруднительным. Разработки на C# в среде Mono, использующие gtk#, переносимы, но их существенно меньше. Реализация платформы WPF в рамках проекта Mono отсутствует, поэтому WPF приложения не обладают переносимостью на операционные системы на основе Linux.

C#, наравне с Java, постепенно становится популярным на нескольких операционных системах на основе Linux и BSD[26][27][28]. Реализация проекта Mono была юридически безболезненным процессом, поскольку CLR и язык C# стандартизированы Ecma и ISO, и любой может их реализовывать, не беспокоясь о правовой стороне дела[29]. В то же время, следует отметить, что приложение, написанное под средой Windows, может иметь значительные проблемы запуска под другой ОС.

Мобильные приложения

[править | править код]

J2ME (JavaME, Java(2) Micro Edition) имеет очень широкую базу на рынках мобильных телефонов и КПК, где только самые дешёвые устройства лишены KVM (урезанная Java Virtual Machine для устройств с ограниченными ресурсами). Программы на Java, включая множество игр, встречаются повсеместно.

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

Java используется для разработки приложений для Android, использующей нестандартную виртуальную машину Dalvik (или ART).

С# является основным языком для написания приложений для мобильной операционной системы Windows Phone, разрабатываемой Microsoft. Однако существует фреймворк для кроссплатформенной разработки Xamarin, позволяющий создавать нативные приложения для Android, IOS и Windows Phone.

Примечания

[править | править код]
  1. 1 2 Radeck, Kirk C# and Java: Comparing Programming Languages (англ.). MSDN (октябрь 2003). Дата обращения: 19 ноября 2013. Архивировано 28 ноября 2013 года.
  2. 1 2 3 Kurniawan, Budi Comparing C# and Java (англ.). O’Reilly Media (6 июля 2001). Дата обращения: 18 ноября 2013. Архивировано 10 июня 2015 года.
  3. Chandra, Shyamal Suhana; Chandra, Kailash. A comparison of Java and C# // Journal of Computing Sciences in Colleges. — Consortium for Computing Sciences in Colleges, 2005. — Вып. 20, № 3. — С. 238—254. — ISSN 1937-4771.
  4. 1 2 Gruntz, Dominik. C# and Java: The Smart Distinctions (англ.) // Journal of Object Technology. — 2002. — Iss. November-December, no. vol. 1, no. 5. — P. 163—176. Архивировано 18 марта 2014 года.
  5. "Что нового появилось в C# 7 и уже поддерживается в Visual Studio "15" Preview 4". Архивировано 21 сентября 2017. Дата обращения: 21 сентября 2017.
  6. BillWagner. Руководство по программированию на C#. Конструкторы (рус.). docs.microsoft.com. Дата обращения: 29 октября 2021. Архивировано 29 октября 2021 года.
  7. Rowe, 2004, pp. 204-206.
  8. Johnson, Mark C#: A language alternative or just J--?, Part 2 (англ.). JavaWorld (21 декабря 2000). Дата обращения: 18 ноября 2013. (недоступная ссылка)
  9. Krikorian, Raffi Contrasting C# and Java Syntax (англ.). O’Reilly Media (14 июня 2001). Дата обращения: 19 ноября 2013. Архивировано 10 июня 2015 года.
  10. Хотя сама среда исполнения позволяет порождать и инстанцировать типы, производные от наследников System.ValueType.
  11. sealed (Справочник по C#). Дата обращения: 16 марта 2010. Архивировано 5 марта 2010 года.
  12.  (англ.)Generics in C#, Java, and C++ Архивная копия от 7 октября 2006 на Wayback Machine
  13. August 1998 Java News. Дата обращения: 11 июня 2008. Архивировано 25 января 2009 года.
  14. Cabrera, 2002, pp. 30-32.
  15. Puvvala, 2003, pp. 62-63.
  16. Balagurusamy, 2008, p. 8.
  17. Джеффри Рихтер CLR via C#//М., Издательство «Русская редакция», 2007 — С.656. ISBN 978-5-91180-303-2
  18. 1 2 3 Johnson, Mark C#: A language alternative or just J--?, Part 1 (англ.). JavaWorld (22 ноября 2000). Дата обращения: 18 ноября 2013. (недоступная ссылка)
  19.  (англ.)Результаты сравнения jdk15 и classpath Архивировано 28 сентября 2007 года.
  20. Related Technologies | Oracle. Дата обращения: 4 декабря 2006. Архивировано из оригинала 14 мая 2007 года.
  21. Sun openjdk: Home. Дата обращения: 4 декабря 2006. Архивировано из оригинала 11 июня 2007 года.
  22. Sun Java 2 Runtime License Agreement. Дата обращения: 23 ноября 2006. Архивировано 2 января 2007 года.
  23. GNU General Public License — GNU Project — Free Software Foundation (FSF). Дата обращения: 4 декабря 2006. Архивировано 5 декабря 2006 года.
  24. Mono FAQ: Licensing (Patents). Дата обращения: 4 декабря 2006. Архивировано 24 июня 2018 года.
  25. Rotor: Shared Source CLI Provides Source Code for a FreeBSD Implementation of .NET. Дата обращения: 4 декабря 2006. Архивировано из оригинала 2 декабря 2006 года.
  26. Fedora embraces Mono — ZDNet UK. Дата обращения: 23 ноября 2006. Архивировано из оригинала 27 декабря 2007 года.
  27. Debian — mono. Дата обращения: 23 ноября 2006. Архивировано 25 декабря 2006 года.
  28. Wikipedia Uses Mono; Mono Integrated into Ubuntu/Debian — OSNews.com. Дата обращения: 23 ноября 2006. Архивировано 7 января 2006 года.
  29. ISO Standards Development:ISOTC home:00. ISO standards and patents. Дата обращения: 23 ноября 2006. Архивировано 9 декабря 2006 года.

Литература

[править | править код]