Shared rss feeds:

Servare Mentem - blog

Предыдущие сообщения

Мой предыдущий блог

среда, 20 февраля 2008 г.

Java vs C#

Начну свой новый блог, пожалуй, с очень интересной и актуальной темы: сравнение возможностей Java и C#. Нашёл очень хорошую статью, где всё разобрано подробно и разложено по полочкам. Для сравнения на некоторых сайтах попадались совершенно безграмотные обзоры. Очевидно, что люди совершенно не знают о возможностях Java и судят о них с потолка. Например, видел где-то упоминание о том, что в Java нет очень полезного и удобного оператора foreach или аналогичного ему (который, впрочем, MS спёрли у Perl), тогда как в java есть полный аналог foreach: for({type} {identifier} : {array})
Вообще, решил заняться этим вопросом, когда мне начали говорить, что C# несравнимо более богатый и продвинутый язык, нежели Java. Меня это несколько удивило - ведь я всегда считал, что C# - это просто фактическая копия явы с чуть изменённым синтаксисом. А утверждается, что в C# в отличие от Java есть: нормальные Generic, delegates, partial classes и closures.

closures & delegates

Я начал с последнего, залез в справку и увидел, что closures в Java в прямом понимании действительно нет (их введут только с JDK1.7). Но вот что интересно - так ли эти closures важны - то есть неужели их наличие играет огромную роль? Сам я, как человек, имеющий достаточно небольшой опыт работы и с Java и с C#, их никогда не использовал. Давайте посмотрим. Что такое по сути closure? Первый раз при программировании я столкнулся с этим понятием когда писал класс для рендеринга на C++ Builder и хотел спрятать таймер, по которому происходила бы отрисовка сцены внутрь него. Соответственно мне требовался callback, который задавался бы этому таймеру и я хотел задать в качестве оного - метод этого класса. Но с удивлением тогда для себя увидел, что компилятор этого мне этого сделать не позволяет. Почитав справку, я узнал что у него на это есть две причины – во-первых, чисто формальная. По правилам языка, указатель на метод класса имеет тип не указателя на функцию, а указателя на метод. И его нельзя приводить к указателю на функцию. Из-за второй причины – когда мы вызываем какой-то метод, то мы его вызываем не сам по себе, а для какого-то объекта. И указатель на этот объект передается неявно как указатель this, таким образом этот метод знает поля какого именно объекта надо модифицировать. Поэтому при использовании callback'а - метода класса, нужно как-то передавать ещё и указатель на объект. Для Borland эта проблема решилась изменением стандарта C++ и добавлением модификтора __closure в свой компилятор. Closure – это специальный вид XE(расширения стандарта) указателя на функцию, используемый в большинстве функций в библиотеках Windows. В отличие от обычных указателей на функции, эти содержат не только адрес вызываемой функции (четырехбайтный указатель), но так же и указатель на объект , для которого вызывается событие (указатель this). Использование выражения __closure некоторым образом ограничивает возможности системы, так как при нем возможно использовать лишь ограниченное число объектов одного класса. Считается, что использование указателей closure – весьма важная концепция, осуществленная в CBuilder. Хотя, лично я считаю, что любая концепция, которая там осуществлена и которая при этом отдаляет их среду от стандарта - есть зло. Для того, чтобы использовать closure и при этом не нарушать стандарт можно легко обойтись маленькой библиотчкой, которую вполне можно написать самому. Подобная же вещь реализована в библиотеке boost без всяких дополнительных модификаторов. Итак, а что понимается под closure в C# и почему же это так важно? В шарпе под closure понимается функция, которая захватывает значения своего лексического окружения, когда она создается во время исполнения. Лексическое окружение функции есть множество локальных переменных, полей и других членов классов, видимых из этой функции. Определение замыкания подразумевает понятие создания функции во время исполнения. Рассмотрим пример использования замыканий на C#:
int[] ary = { 1, 2, 3 };
int x = 2;
Array.ConvertAll(ary, delegate(int elem) { return elem * x; }); // { 2, 4, 6 }
Таким образом, C# использует анонимные методы, способные замыкаться на контекст в котором они определяются. Вещь, конечно, удобная и полезная - спору нет. Но ведь в Java тоже есть замыкания! Они реализуются с помощью анонимных классов. Анонимный класс имеет доступ к полям класса, в лексическом контексте которого он определен, а так же к переменным с модификатором final в лексическом контексте метода. Например:
public final calculateInSeparateThread(final URI uri) {
 // The expression "new Runnable() { ... }" is an anonymous class.
 Runnable runner = new Runnable() {
         void run() {
             // It can access final local variables:
             calculate(uri);
             // It can access private fields of the enclosing class:
             btnSave.setEnabled(true);
         }
     };
 new Thread(runner).start();
}
Согласен, в подобном синтаксисе есть некоторая громоздкость, но не говорите мне что в Java нет замыканий. Что же касается делегатов, то вполне можно обойтись без них. В C# delegate - это по сути указатель на метод какого-то объекта. Допустим, один объект представляет собой часть какой-то работы и не хочет завязываться на input/output, но нужно реализовать лог этой работы. Всё что нужно знать объекту - сигнатуру метода, который будет вести лог (например, выводить информацию о ходе процесса на консоль). То есть, объект ничего не будет знать о реализации данного метода, но будет его вызывать. Лично на мой взгляд подобная ситуация легко заменяется имплементацией объектом, предоставляющим вывод на консоль какого-либо интерфейса ILogger. Таким образом, классу выполняющему работу нужно будет знать только о существовании этого интерфейса. А чем это собственно, хуже? По-моему, это менее запутанная тропа чем какие-то сомнительные делегаты...

Generics

Generics в C# и Java реализованы действительно по-разному. Кто-то мне говорил что в Java генерики так сказать "формальные", то есть их там фактически нет. Я решил понять что он имеет ввиду. А выяснилось следующее. В Java они реализованы с использованием "затирания типа" (type erasure). Это значит, что представление о типа существует только на этапе компиляции, после которой компилятор заменяет тип во всех generic-объявлениях на Object. Затем компилятор добавляет приведения типов в нужные места. Причина реализации данного подхода состоит в требованиях совместимости кода, использующего Generics и старого кода, который их не поддерживает. Главная проблема с затиранием типа состоит в том, что информация о типе generic не доступна во время выполнения через Reflection или идентификацию типа во время выполнения. Другое последствие этого подхода в том, что generic типы структур данных должны всегда объявляться, используя объекты а не не примитивные типы. Таким образом нужно создавать Stack<Integer> вместо Stack<int> для работы с целыми числами.
В C# есть явная поддержка generics на уровне языка MSIL. Когда generic тип компилируется, произведенный MSIL содержит информацию об определенных типах. Во время выполнения, когда ссылка применена к generic типу (например, List<int>) система проверяет был ли осуществлён запрос типа или нет. Если тип уже запрашивался, то будет возвращен созданный предварительно определенный тип. В противном случае JIT компилятор создаст новый тип, заменив параметр generic типа в MSIL определенным типом (например заменит List<T> на List<int>). Нужно отметить, что, если требуемый тип - ссылочный, тогда параметр generic типа заменяется на Object. Однако, приведения типов во среде выполнения .NET при доступе к типам нет.
Ещё одним отличием generics в Java и в C# являются constraints(ограничения) на параметр типа. В Java всего одно ограничение - derivation constraint ограничение происхождения(можно указать что тип должен обязательно расширять какой-либо класс). В C# есть ещё 2 типа ограничений: можно указать, что generic тип должен обязательно иметь пустой конструктор(конструктор по-умолчанию), а так же наложить ограничение на то ссылочный ли это тип или простой тип.
Итак, в чём это принципиальное отличие которое делает C# языком с большой буквы в сравнении с Java? Лишь в том, что нельзя определить во время выполнения какой тип передан в generic в качестве параметра? Так ведь и с этим можно справиться более простыми методами. Например, можно опять же отнаследовать свой generic от класса, в котором определить метод:
protected Class<T> genericParameter;
//...
public Class<T> getGenericParameter() {
    return genericParameter;
}
Теперь для объекта MyGeneric<Integer> mygeneric я могу получить тип Integer вызвав: mygeneric.genericParameter(). Всё предельно просто и ясно.

partial classes

Угу. Есть такой момент - в C# можно тело класса раскидать на кучу файлов. В Java же всё строго - один класс - один файл. Да, не спорю, это немного стесняет свободу. Но что же делать - идеология Java нам советует дробить программу как только можно. Если класс разрастает до такой степени, что ему становится тесно в своём файле - значит это неправильный класс с точки зрения архитектуры, его следует разбить на подзадачи и вынести их в свои классы. А идеология Microsoft - это "давайте напишем такую штуку которая будет делать ВСЁ". И на тебе - всю функциональность давайте запихнём в один класс, вот будет прекрасно. Нет уж, ребята, по-моему, это лишнее и только затрудняет понимание архитектуры приложения, как, впрочем и все остальные вещи которые якобы выводят C# на несравнимо более высокий уровень.

conclusion

Ну что сказать. Разумеется, C# в отличие от Java предоставляет много "вкусностей". Но на мой взгляд они зачастую излишни и затрудняют понимание назначения кода, запутывают программиста. Конечно, я разобрал лишь малую толику отличий. В предлагаемой статье разобрано куда больше. Я не учёл такие моменты как: принудительный вызов GC (зачем?!); передача аргументов нессылочных типов по ссылке(излишне); различие между зубчатыми массивами и многомерными массивами(достаточно полезно, хотя и не столь принципиально); свойства(properties); препроцессорные директивы; перегрузка оператора []; недвусмысленная имплементация интерфейсов; дружественные пространства имён; и ещё многая прочая муть (подробнее - в статье).
В Java многое из этого не было реализовано именно из-за того, что эти вещи часто затрудняли читабельность кода в C++. C# больше похож на C++ чем Java - это факт. Но становится ли он от этого на ступень выше Java - вопрос очень спорный. Это тоже самое что сказать "Марс лучше Юпитера" или, скажем, "Интерференция на ступень выше дифракции" или "Берёза лучше Дуба"... Я не противник C#. Согласен, он предоставляет множество удобств, которых в Java нет. Но они не столь существенны. Оба языка дают широкие возможности для выражения собственных мыслей, и нет проблем, которые можно было бы решить на C# и нельзя на Java.

[to be continued...]

Ярлыки: , ,

Комментарии: 2:

В 3 января 2009 г. в 01:46 , Blogger Unknown сказал(а)...

It's good work

My opinion: CSharp is a monster and it must be life but natural selection will be kill him

But java will be survives

 
В 21 ноября 2009 г. в 20:14 , Anonymous Анонимный сказал(а)...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!

 

Отправить комментарий

Подпишитесь на каналы Комментарии к сообщению [Atom]

<< Главная страница

-->