Я добавлю свой голос к шуму и попытаюсь прояснить ситуацию:
Обобщения C # позволяют вам объявить что-то вроде этого.
List<Person> foo = new List<Person>();
и тогда компилятор будет препятствовать тому, чтобы вы помещали вещи, которых нет Person
в списке.
За кулисами компилятор C # просто помещает List<Person>
в DLL-файл .NET, но во время выполнения JIT-компилятор создает и создает новый набор кода, как если бы вы написали специальный класс списка только для содержания людей - что-то вроде 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);
танец кастинга все еще должен быть сделан. Компилятор спасает вас от нажатия клавиш, но скорость нажатия / сотворения все равно происходит, как и всегда.
Когда люди упоминают «Type Erasure», это то, о чем они говорят. Компилятор вставляет приведенные типы для вас, а затем «стирает» тот факт, что он должен быть списком 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, и оно будет делать то, что, как вы думаете, должно делать, но за кулисами происходят разные вещи.
Он наиболее pseudo-classes
похож на дженерики C # в том, что он создает специальные, а не просто отбрасывает информацию о типах, как это делает 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 (), он скомпилируется. Если они этого не сделают, это не так. Просто.
Итак, вот оно :-)
int addNames<T>( T first, T second ) { return first + second; }
на C #. Универсальный тип может быть ограничен классом вместо интерфейса, и есть способ объявить класс с+
оператором в нем.C ++ редко использует терминологию «обобщений». Вместо этого слово «шаблоны» используется и является более точным. Шаблоны описывает одну технику для достижения общего дизайна.
Шаблоны C ++ сильно отличаются от того, что и C #, и Java реализуют по двум основным причинам. Первая причина заключается в том, что шаблоны C ++ допускают не только аргументы типа времени компиляции, но и аргументы const-value времени компиляции: шаблоны могут быть заданы как целые числа или даже сигнатуры функций. Это означает, что вы можете делать довольно интересные вещи во время компиляции, например, вычисления:
Этот код также использует другую особенность шаблонов C ++, а именно специализацию шаблонов. Код определяет один шаблон класса,
product
который имеет один аргумент значения. Он также определяет специализацию для этого шаблона, которая используется всякий раз, когда аргумент оценивается как 1. Это позволяет мне определить рекурсию по определениям шаблона. Я считаю, что это впервые открыл Андрей Александреску .Специализация шаблона важна для C ++, потому что она учитывает структурные различия в структурах данных. Шаблоны в целом - это средство объединения интерфейса между типами. Однако, хотя это желательно, все типы не могут обрабатываться одинаково внутри реализации. Шаблоны C ++ учитывают это. Это очень большая разница между интерфейсом и реализацией ООП с переопределением виртуальных методов.
Шаблоны C ++ необходимы для его парадигмы алгоритмического программирования. Например, почти все алгоритмы для контейнеров определяются как функции, которые принимают тип контейнера в качестве типа шаблона и обрабатывают их единообразно. На самом деле, это не совсем правильно: C ++ работает не с контейнерами, а с диапазонами , которые определены двумя итераторами, указывающими на начало и конец контейнера. Таким образом, весь контент ограничен итераторами: begin <= elements <end.
Использование итераторов вместо контейнеров полезно, поскольку позволяет работать с частями контейнера, а не с целыми.
Еще одна отличительная черта C ++ - возможность частичной специализации для шаблонов классов. Это в некоторой степени связано с сопоставлением с образцом аргументов в Haskell и других функциональных языках. Например, давайте рассмотрим класс, который хранит элементы:
Это работает для любого типа элемента. Но допустим, что мы можем хранить указатели более эффективно, чем другие типы, применяя некоторые специальные приемы. Мы можем сделать это, частично специализируясь на всех типах указателей:
Теперь, когда мы создаем экземпляр шаблона контейнера для одного типа, используется соответствующее определение:
источник
Сам Андерс Хейлсберг описал различия здесь: « Обобщения в C #, Java и C ++ ».
источник
Есть уже много хороших ответов на то , что различия, поэтому позвольте мне дать несколько иной точки зрения и добавить почему .
Как уже было объяснено, основным отличием является стирание типов , то есть тот факт, что компилятор Java стирает универсальные типы, и они не попадают в сгенерированный байт-код. Однако вопрос в том, зачем это делать. Это не имеет смысла! Или это?
Ну, а какая альтернатива? Если вы не реализуете дженерики на языке, где бы вы их реализовать? И ответ: в виртуальной машине. Что нарушает обратную совместимость.
Стирание типа, с другой стороны, позволяет смешивать универсальные клиенты с неуниверсальными библиотеками. Другими словами: код, который был скомпилирован на Java 5, все еще может быть развернут на Java 1.4.
Microsoft, однако, решила сломать обратную совместимость для дженериков. Вот почему .NET Generics "лучше", чем Java Generics.
Конечно, Солнце не идиоты или трусы. Причиной, по которой они «скупились», было то, что Java была значительно старше и более распространена, чем .NET, когда они вводили дженерики. (Они были введены примерно одновременно в обоих мирах.) Нарушение обратной совместимости было бы огромной болью.
Иными словами, в Java Generics являются частью языка (что означает, что они применяются только к Java, а не к другим языкам), в .NET они являются частью виртуальной машины (что означает, что они применяются ко всем языкам, а не просто C # и Visual Basic .NET).
Сравните это с функциями .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.
источник
ArrayList<T>
может передаваться как новый тип с внутренним именем со (скрытым) статическимClass<T>
полем. Пока новая версия универсального lib была развернута с 1,5-байтовым кодом, она сможет работать на 1,4-JVM.Продолжение моей предыдущей публикации.
Шаблоны являются одной из основных причин, почему C ++ так ужасно терпит неудачу при intellisense, независимо от используемой IDE. Из-за специализации шаблона среда IDE никогда не может быть уверена, существует ли данный член или нет. Рассматривать:
Теперь курсор находится в указанной позиции, и для IDE чертовски сложно сказать, есть ли и что у членов
a
. Для других языков синтаксический анализ был бы простым, но для C ++, предварительно требуется немало вычислений.Становится хуже. Что если бы они
my_int_type
были определены внутри шаблона класса? Теперь его тип будет зависеть от аргумента другого типа. И здесь даже компиляторы выходят из строя.Подумав немного, программист придет к выводу, что этот код такой же, как и выше: он
Y<int>::my_type
разрешаетint
, поэтомуb
должен быть того же типа, чтоa
и верно?Неправильно. В тот момент, когда компилятор пытается разрешить это утверждение, он еще не знает
Y<int>::my_type
! Поэтому он не знает, что это тип. Это может быть что-то еще, например, функция-член или поле. Это может привести к неоднозначности (хотя не в данном случае), поэтому компилятор не работает. Мы должны явно сказать, что мы ссылаемся на имя типа:Теперь код компилируется. Чтобы увидеть, как возникают неопределенности в этой ситуации, рассмотрим следующий код:
Этот оператор кода совершенно допустим и говорит C ++ выполнить вызов функции
Y<int>::my_type
. Однако, еслиmy_type
это не функция, а скорее тип, этот оператор все равно будет действительным и будет выполнять специальное приведение (приведение в стиле функции), которое часто является вызовом конструктора. Компилятор не может сказать, что мы имеем в виду, поэтому мы должны устранить неоднозначность здесь.источник
И Java, и C # представили дженерики после выхода их первого языка. Тем не менее, существуют различия в том, как основные библиотеки изменились, когда появились дженерики. Обобщения C # - это не просто магия компилятора, и поэтому было невозможно генерировать существующие классы библиотеки без нарушения обратной совместимости.
Например, в Java существующая платформа коллекций была полностью обобщена . Java не имеет как универсальной, так и устаревшей неуниверсальной версии классов коллекций. В некотором смысле это намного чище - если вам нужно использовать коллекцию в C #, на самом деле очень мало причин использовать неуниверсальную версию, но эти унаследованные классы остаются на месте, загромождая ландшафт.
Другим заметным отличием являются классы Enum в Java и C #. Enum в Java имеет это несколько извилистое определение:
(См. очень ясное объяснение Анджелики Лангер, почему это так. По сути, это означает, что Java может предоставить безопасный тип доступа от строки к ее значению Enum:
Сравните это с версией C #:
Поскольку Enum уже существовал в C # до того, как дженерики были введены в язык, определение не могло быть изменено без нарушения существующего кода. Таким образом, как и коллекции, он остается в основных библиотеках в этом устаревшем состоянии.
источник
ArrayList
чтобыList<T>
и поместить его в новое пространство имен. Дело в том, что если в исходном коде появился класс, так какArrayList<T>
он стал бы другим именем класса, сгенерированным компилятором, в коде IL, то возникновение конфликтов имен невозможно.11 месяцев с опозданием, но я думаю, что этот вопрос готов для некоторых вещей Java Wildcard.
Это синтаксическая особенность Java. Предположим, у вас есть метод:
И предположим, вам не нужно ссылаться на тип T в теле метода. Вы объявляете имя T и затем используете его только один раз, так почему вы должны думать о названии для него? Вместо этого вы можете написать:
Знак вопроса просит компилятор сделать вид, что вы объявили нормальный именованный параметр типа, который должен появиться только один раз в этом месте.
Вы не можете ничего сделать с подстановочными знаками, которые вы не можете сделать с параметром именованного типа (именно так все это всегда делается в C ++ и C #).
источник
class Foo<T extends List<?>>
и использовать,Foo<StringList>
но в C # вы должны добавить этот дополнительный параметр типа:class Foo<T, T2> where T : IList<T2>
и использовать неуклюжийFoo<StringList, String>
.В Википедии есть отличные рецензии, в которых сравниваются как дженерики Java / C #, так и шаблоны дженериков Java / C ++ . Основная статья на Дженерики кажется немного суматоха , но у него есть некоторые хорошие данные в нем.
источник
Самая большая жалоба - стирание типа. При этом генерики не применяются во время выполнения. Вот ссылка на некоторые документы Sun по этому вопросу .
источник
Шаблоны C ++ на самом деле намного мощнее, чем их аналоги из C # и Java, поскольку они оцениваются во время компиляции и поддерживают специализацию. Это позволяет использовать мета-программирование шаблонов и делает компилятор C ++ эквивалентным машине Тьюринга (т. Е. В процессе компиляции вы можете вычислить все, что можно вычислить на машине Тьюринга).
источник
В Java обобщения являются только уровнем компилятора, поэтому вы получаете:
Обратите внимание, что тип «a» является списком массивов, а не списком строк. Таким образом, тип списка бананов будет равен () список обезьян.
Так сказать.
источник
Похоже, что среди других очень интересных предложений есть одно о доработке дженериков и нарушении обратной совместимости:
в статье Алекса Миллера о предложениях Java 7
источник
NB: У меня нет достаточно точек, чтобы комментировать, поэтому не стесняйтесь переместить это как комментарий к соответствующему ответу.
Вопреки распространенному мнению, которого я никогда не понимаю, откуда он взялся, .net реализовал истинные дженерики, не нарушая обратной совместимости, и они потратили на это явные усилия. Вам не нужно превращать неуниверсальный код .net 1.0 в дженерики, чтобы использовать его в .net 2.0. И общие, и неуниверсальные списки по-прежнему доступны в .Net framework 2.0 даже до 4.0, и это только по причине обратной совместимости. Поэтому старые коды, которые все еще использовали неуниверсальный ArrayList, будут по-прежнему работать и использовать тот же класс ArrayList, что и раньше. Совместимость с обратным кодом всегда поддерживается с 1.0 до сих пор ... Так что даже в .net 4.0 вам все равно придется использовать любой неуниверсальный класс из 1.0 BCL, если вы решите это сделать.
Поэтому я не думаю, что java должен нарушать обратную совместимость для поддержки истинных обобщений.
источник
ArrayList<Foo>
что она хочет передать старому методу, который должен заполнитьArrayList
экземплярамиFoo
. ЕслиArrayList<foo>
не являетсяArrayList
, как можно заставить это работать?