Участник:Phersu/Java vs. CSharp

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

Адаптеры (анонимные классы) vs. делегаты

Адаптеры в Java более flexible, так как могут хранить своё состояние между вызовами (что бывает очень полезно). В случае с делегатами в C# состояние предполагается хранить во внешнем scope, что замусоривает код. И делегаты, и адаптеры поддерживают замыкания и отличаются только синтаксисом.

Синтаксис у делегатов покороче, однако адаптеры позволяют объединять несколько методов в один логический listener, благодаря чему в итоге можно сделать API в Java компактнее:

C#:

public delegate void ClickDelegate();
public delegate void MoveDelegate();
public delegate void KeyDelegate();
void obj_onClick() { }
void obj_onMove() { }
void obj_onKey() { }
obj.OnClick += obj_onClick;
obj.OnMove += obj_onMove;
obj.OnKey += obj_onKey;

Java:

obj.addListener(new Listener() {
public void onClick() { }
public void onMove() { }
public void onKey() { }
});

Также делегаты суть second-class citizens, чужды ООП, усложняют язык и VM, вводят дополнительные ненужные типы (MulticastDelegate наследует Delegate, при том что реально используется только класс MulticastDelegate, который нельзя наследовать (хотя он не sealed!), а класс Delegate вообще никак не используется и является технической ошибкой проектирования в .NET 1.0)

См. http://java.sun.com/docs/white/delegates.html

Generics

Параметризированные типы в C# поддерживаются на уровне VM, в Java же имеем type erasure. С одной стороны, generics в CLR более производительны: нет cast'ов и boxing/unboxing, как в Java. С другой стороны, на уровне языка C# и системы CLR вводятся очередные хаки:

  • примитивные типы наследуют ValueType, который наследует Object, но в то же время примитивные типы по семантике и поведению совсем не являются Object'ами и, наоборот, противопоставляются им (ну что общего у int и Object?)
  • вызов виртуальных методов на value types нуждается в магическом опкоде constrained
  • каждый параметризированный класс (напр. List<int>) имеет в памяти специальный инстанцированный объект класса, каждый со своими сгенерированными JIT-данными (хотя сейчас уже даже mono умеет sharing), каждый со своими внутренними структурами
  • противоречит ООП-иерархии, дублирует на уровне системы типов саму первоначальную идею, которая заключается в том, что все классы наследуют Object именно с целью иметь обобщённые контейнеры с апкастом в Object

Среди плюсов, конечно — лучшая производительность, возможность определить тип T для конкретно взятого контейнера. В Java же подход другой: generics не стоят в центре системы типов, а являются просто удобным инструментом для проверки типов в compile-time.

Минусы Java

  • примитивные объекты должны оборачиваться в wrappers типа Integer, Float и т.д. на куче. Зато это позволяет иметь систему типов довольно семантически интуитивной и без лишних хаков. А вот в C# даже boxed primitive types умудрились сделать через хак: они являются очередным «магическим» типом, реализуемом где-то в недрах VM, в то время как в Java это просто объекты с одним единственным полем int, float и т.д.
  • при добавлении объекта в контейнер происходит апкаст, который в 100% случаях no op, при извлечении же объекта нужен даункаст, но я не думаю, что это серьёзный bottleneck (бенчмарки?) Думаю, что JIT имеет возможность соптимизировать здесь.
  • нет возможности определить параметр типа в рантайме, напр. в Java нельзя написать new T[10] — нужно писать new Object[10].
  • overhead из-за boxing'а довольно большой, напр. заголок объекта на куче равен 8 байтам + выравнивание на 8 байт — итого один int в контейнере занимает 16 байт vs. 4 байта в C#.

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