В чем разница между дженериками в C # и Java ... и шаблонами в C ++?[закрыто]

StackOverflow https://stackoverflow.com/questions/31693

Вопрос

Я в основном использую Java, а дженерики относительно новы.Я продолжаю читать, что Java приняла неправильное решение или что.NET имеет лучшие реализации и т.д.и т.д.

Итак, каковы основные различия между C ++, C #, Java в generics?Плюсы / минусы каждого из них?

Это было полезно?

Решение

Я добавлю свой голос к общему шуму и попытаюсь внести ясность:

Дженерики C # позволяют вам объявить что-то вроде этого.

List<Person> foo = new List<Person>();

и тогда компилятор не позволит вам помещать то, чего нет Person внесите в список.
За кулисами компилятор C # просто помещает List<Person> в файл .NET dll, но во время выполнения JIT-компилятор переходит и создает новый набор кода, как если бы вы написали специальный класс list только для того, чтобы содержать людей - что-то вроде ListOfPerson.

Преимущество этого в том, что это делает процесс действительно быстрым.Там нет кастинга или каких-либо других вещей, и поскольку библиотека dll содержит информацию о том, что это список Person, другой код , который посмотрит на него позже с помощью отражения , может сказать , что он содержит Person объекты (таким образом, вы получаете intellisense и так далее).

Недостатком этого является то, что старый код C # 1.0 и 1.1 (до того, как они добавили дженерики) не понимает эти новые List<something>, поэтому вам придется вручную конвертировать все обратно в обычный старый формат. List взаимодействовать с ними.Это не такая уж большая проблема, потому что двоичный код C # 2.0 не имеет обратной совместимости.Единственный раз, когда это когда-либо произойдет, - это если вы обновляете какой-то старый код C # 1.0 / 1.1 до C # 2.0

Java Generics позволяют вам объявить что-то вроде этого.

ArrayList<Person> foo = new ArrayList<Person>();

На первый взгляд это выглядит так же, и в некотором роде так оно и есть.Компилятор также не позволит вам помещать вещи, которые не являются Person внесите в список.

Разница в том, что происходит за кулисами.В отличие от C #, Java не использует специальную ListOfPerson - он просто использует простой старый ArrayList который всегда был на Java.Когда вы извлекаете что-то из массива, происходит обычный Person p = (Person)foo.get(1); кастинг-танец еще предстоит сделать.Компилятор экономит вам время нажатия клавиш, но скорость нажатия / приведения по-прежнему сохраняется, как и всегда.
Когда люди упоминают "Удаление типа", именно об этом они и говорят.Компилятор вставляет приведения для вас, а затем "стирает" тот факт, что это должен быть список Person не просто Object

Преимущество такого подхода заключается в том, что старый код, который не понимает дженериков, не должен волновать.Он все еще имеет дело с тем же старым ArrayList как это было всегда.Это более важно в мире Java, потому что они хотели поддерживать компиляцию кода с использованием Java 5 с дженериками и запуск его на старой версии 1.4 или предыдущих JVM, с которыми Microsoft намеренно решила не заморачиваться.

Недостатком является снижение скорости, о котором я упоминал ранее, а также отсутствие ListOfPerson псевдокласс или что-то подобное, входящее в файлы .class, код, который просматривает его позже (с отражением, или если вы извлекаете его из другой коллекции, где он был преобразован в Object или так далее) никак не могу сказать, что это должен быть список, содержащий только Person и не просто какой-либо другой список массивов.

Шаблоны C ++ позволяют вам объявлять что-то вроде этого

std::list<Person>* foo = new std::list<Person>();

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

Он имеет больше всего общего с C # generics в том, что создает специальные pseudo-classes вместо того, чтобы просто выбрасывать информацию о типе, как это делает java, но это совершенно другой подход.

Как C #, так и Java выдают выходные данные, предназначенные для виртуальных машин.Если вы напишете какой-нибудь код, который имеет Person класс в нем, в обоих случаях некоторая информация о Person класс перейдет в файл .dll или .class, и JVM / CLR будет что-то делать с этим.

C ++ создает необработанный двоичный код x86.Все есть нет объект, и нет базовой виртуальной машины, которая должна знать о Person класс.Здесь нет упаковки или распаковки, и функции не обязательно должны принадлежать классам или вообще чему-либо еще.

Из-за этого компилятор C ++ не накладывает никаких ограничений на то, что вы можете делать с шаблонами - в принципе, любой код, который вы могли бы написать вручную, вы можете заставить шаблоны писать за вас.
Самый очевидный пример - добавление вещей:

В C # и Java система generics должна знать, какие методы доступны для класса, и ей необходимо передать это виртуальной машине.Единственный способ сообщить ему об этом - либо жестко запрограммировать сам класс, либо использовать интерфейсы.Например:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

Этот код не будет компилироваться на C # или Java, потому что он не знает, что тип T фактически предоставляет метод с именем Name() .Вы должны сказать это - на C # вот так:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

И затем вы должны убедиться, что вещи, которые вы передаете addNames, реализуют интерфейс IHasName и так далее.Синтаксис java отличается (<T extends IHasName>), но он страдает от тех же проблем.

"Классическим" случаем для этой проблемы является попытка написать функцию, которая делает это

string addNames<T>( T first, T second ) { return first + second; }

На самом деле вы не можете написать этот код, потому что нет способов объявить интерфейс с помощью + метод в этом.Ты терпишь неудачу.

C ++ не страдает ни от одной из этих проблем.Компилятор не заботится о передаче типов ни одной виртуальной машине - если оба ваших объекта имеют функцию .Name(), она будет скомпилирована.Если они этого не сделают, то и этого не будет.Просто.

Итак, вот оно:-)

Другие советы

C ++ редко использует терминологию “дженериков”.Вместо этого используется слово “шаблоны”, которое является более точным.Шаблоны описывают один из них техника для достижения общего дизайна.

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

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Этот код также использует другую отличительную особенность шаблонов C ++, а именно специализацию шаблонов.Код определяет один шаблон класса, product это имеет один аргумент value.Он также определяет специализацию для этого шаблона, которая используется всякий раз, когда аргумент принимает значение 1.Это позволяет мне определить рекурсию по определениям шаблонов.Я полагаю, что это было впервые обнаружено Андрей Александреску.

Специализация шаблонов важна для C ++, поскольку она допускает структурные различия в структурах данных.Шаблоны в целом - это средство унификации интерфейса разных типов.Однако, хотя это желательно, все типы не могут обрабатываться одинаково внутри реализации.Шаблоны C ++ учитывают это.Это почти то же самое различие, которое ООП делает между интерфейсом и реализацией с переопределением виртуальных методов.

Шаблоны C ++ необходимы для его алгоритмической парадигмы программирования.Например, почти все алгоритмы для контейнеров определяются как функции, которые принимают тип контейнера в качестве шаблонного типа и обрабатывают их единообразно.На самом деле, это не совсем так:C ++ работает не с контейнерами, а скорее с диапазоны которые определяются двумя итераторами, указывающими на начало и за концом контейнера.Таким образом, все содержимое ограничено итераторами:начинать <= элементы < конец.

Использование итераторов вместо контейнеров полезно, поскольку это позволяет работать с частями контейнера, а не со всем им в целом.

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

template <typename T>
class Store { … }; // (1)

Это работает для любого типа элемента.Но давайте предположим, что мы можем хранить указатели более эффективно, чем другие типы, применив какой-нибудь специальный трюк.Мы можем сделать это с помощью частично специализируясь на всех типах указателей:

template <typename T>
class Store<T*> { … }; // (2)

Теперь, всякий раз, когда мы создаем экземпляр шаблона контейнера для одного типа, используется соответствующее определение:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.

Андерс Хейлсберг сам описал здесь различия "Обобщения на C #, Java и C ++".

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

Как уже было объяснено, основным отличием является удаление типа, т. е.тот факт, что компилятор Java стирает универсальные типы, и они не попадают в сгенерированный байт-код.Однако вопрос заключается в следующем:зачем кому-то это делать?В этом нет никакого смысла!Или так оно и есть?

Ну, а какая есть альтернатива?Если вы не реализуете дженерики на этом языке, где делай вы их реализуете?И ответ таков:в Виртуальной машине.Что нарушает обратную совместимость.

С другой стороны, удаление типов позволяет вам смешивать универсальные клиенты с не-универсальными библиотеками.Другими словами:код, который был скомпилирован на Java 5, все еще может быть развернут на Java 1.4.

Microsoft, однако, решила нарушить обратную совместимость для дженериков. Это почему дженерики .NET "лучше", чем дженерики Java.

Конечно, мы не идиоты и не трусы.Причина, по которой они "струсили", заключалась в том, что Java была значительно старше и более распространена, чем .NET, когда они представили дженерики.(Они были представлены примерно в одно и то же время в обоих мирах.) Нарушение обратной совместимости было бы огромной проблемой.

Выразимся еще по -другому:в Java дженерики являются частью Язык (что означает, что они применяются Только для Java, а не для других языков), в .NET они являются частью Виртуальная машина (что означает, что они применимы к ВСЕ языки, а не только C # и Visual Basic.NET).

Сравните это с .СЕТЕВЫЕ функции, такие как LINQ, лямбда-выражения, вывод типов локальных переменных, анонимные типы и деревья выражений:это все язык возможности.Вот почему существуют тонкие различия между VB.NET и C#:если бы эти функции были частью виртуальной машины, они были бы такими же в ВСЕ языки.Но CLR не изменился:в .NET 3.5 SP1 это все то же самое, что и в .NET 2.0.Вы можете скомпилировать программу на C #, использующую LINQ, с помощью компилятора .NET 3.5 и по-прежнему запускать ее на .NET 2.0, при условии, что вы не используете никаких библиотек .NET 3.5.Это было бы нет работать с дженериками и .NET 1.1, но это бы работайте с Java и Java 1.4.

Продолжение моего предыдущего поста.

Шаблоны - одна из основных причин, по которой C ++ так катастрофически не работает в intellisense, независимо от используемой IDE.Из-за специализации шаблонов IDE никогда не может быть по-настоящему уверена, существует данный элемент или нет.Рассмотреть:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

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

Становится только хуже.Что , если my_int_type были ли они также определены внутри шаблона класса?Теперь его тип будет зависеть от другого аргумента типа.И здесь даже компиляторы терпят неудачу.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

Немного подумав, программист пришел бы к выводу, что этот код совпадает с приведенным выше: Y<int>::my_type принимает решение int, следовательно b должен быть того же типа, что и a, верно?

Неправильно.В тот момент, когда компилятор пытается разрешить это утверждение, он на самом деле не знает Y<int>::my_type пока!Следовательно, он не знает, что это тип.Это может быть что-то другое, напримерфункция-член или поле.Это может привести к возникновению двусмысленностей (хотя и не в данном случае), поэтому компилятор завершается с ошибкой.Мы должны явно указать ему, что мы ссылаемся на имя типа:

X<typename Y<int>::my_type> b;

Теперь код компилируется.Чтобы увидеть, как из этой ситуации возникают двусмысленности, рассмотрим следующий код:

Y<int>::my_type(123);

Этот оператор кода абсолютно корректен и сообщает C ++ выполнить вызов функции для Y<int>::my_type.Однако, если my_type поскольку это не функция, а скорее тип, этот оператор все равно был бы действителен и выполнял бы специальное приведение (приведение в стиле функции), которое часто является вызовом конструктора.Компилятор не может определить, что мы имеем в виду, поэтому мы должны устранить неоднозначность здесь.

И Java, и C # представили дженерики после своего первого языкового релиза.Однако существуют различия в том, как изменились основные библиотеки с появлением generics. Дженерики C # - это не просто магия компилятора и поэтому это было невозможно обобщать существующие библиотечные классы без нарушения обратной совместимости.

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

Другим заметным отличием являются классы Enum в Java и C #. Перечисление Java имеет это несколько извилистое определение:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(видите, Анжелика Лангер очень ясна объяснение того, почему именно это так.По сути, это означает, что Java может предоставить типобезопасный доступ из строки к ее перечисляемому значению:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

Сравните это с версией C #:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

Поскольку Enum уже существовал в C # до того, как в язык были введены generics, определение не могло измениться без нарушения существующего кода.Таким образом, как и коллекции, он остается в основных библиотеках в этом устаревшем состоянии.

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

Это синтаксическая особенность Java.Предположим, у вас есть метод:

public <T> void Foo(Collection<T> thing)

И предположим, вам не нужно ссылаться на тип T в теле метода.Вы объявляете имя T, а затем используете его только один раз, так зачем вам придумывать для него имя?Вместо этого вы можете написать:

public void Foo(Collection<?> thing)

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

С помощью подстановочных знаков вы не можете сделать ничего такого, чего нельзя было бы сделать с параметром именованного типа (именно так это всегда делается в C ++ и C #).

В Википедии есть отличные статьи, сравнивающие оба Дженерики Java / C # и Обобщения Java/ C ++ шаблоны.Тот Самый основная статья о дженериках кажется немного загроможденным, но в нем есть кое-какая полезная информация.

Самая большая жалоба - это стирание текста.При этом дженерики не применяются во время выполнения. Вот ссылка на некоторые документы Sun по этому вопросу.

Дженерики реализуются по типу стирание:информация об общем типе присутствует только во время компиляции, после чего она стирается компилятором.

Шаблоны C ++ на самом деле намного мощнее своих аналогов на C # и Java, поскольку они оцениваются во время компиляции и поддерживают специализацию.Это допускает шаблонное метапрограммирование и делает компилятор C ++ эквивалентным машине Тьюринга (т. е.в процессе компиляции вы можете вычислить все, что поддается вычислению с помощью машины Тьюринга).

В Java дженерики доступны только на уровне компилятора, поэтому вы получаете:

a = new ArrayList<String>()
a.getClass() => ArrayList

Обратите внимание, что тип 'a' - это список массивов, а не список строк.Таким образом, тип списка бананов будет равен() списку обезьян.

Так сказать.

Похоже, среди других очень интересных предложений есть одно об уточнении дженериков и нарушении обратной совместимости:

В настоящее время дженерики реализуются с использованием erasure, что означает, что информация об универсальном типе недоступна во время выполнения, что затрудняет написание некоторого вида кода.Дженерики были реализованы таким образом для поддержки обратной совместимости со старым непатентованным кодом.Овеществленные дженерики сделали бы информацию универсального типа доступной во время выполнения, что нарушило бы устаревший не универсальный код.Однако Нил Гафтер предложил сделать типы повторяемыми только в том случае, если они указаны, чтобы не нарушать обратную совместимость.

в Статья Алекса Миллера о предложениях Java 7

Примечание:У меня недостаточно аргументов для комментариев, поэтому не стесняйтесь перенести это в качестве комментария к соответствующему ответу.

Вопреки распространенному мнению, которое я никогда не пойму, откуда оно взялось, .net реализовал настоящие дженерики без нарушения обратной совместимости, и они потратили на это явные усилия.Вам не нужно изменять свой непатентованный код .net 1.0 на generics только для использования в .net 2.0.Как общие, так и нестандартные списки по-прежнему доступны в .Net Framework 2.0 даже до версии 4.0, именно по причине обратной совместимости.Поэтому старые коды, которые все еще использовали нестандартный ArrayList, все еще будут работать и использовать тот же класс ArrayList, что и раньше.Совместимость с обратным кодом всегда поддерживается начиная с версии 1.0 и по сей день...Таким образом, даже в .net 4.0 у вас все еще есть возможность использовать любой класс, не являющийся универсальным, из 1.0 BCL, если вы решите это сделать.

Поэтому я не думаю, что java должна нарушать обратную совместимость, чтобы поддерживать настоящие дженерики.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top