Каковы различия между Дженерики в C # и Java ... и шаблоны в C ++?

голоса
204

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

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

Задан 28/08/2008 в 06:08
источник пользователем
На других языках...                            


13 ответов

голоса
365

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

C # Generics позволяют заявить что-то вроде этого.

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

а затем компилятор будет препятствовать вам положить вещи, которые не Personв списке.
За кулисами C # компилятор просто положить List<Person>в файл .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 или предыдущей виртуальной машины Java, который Microsoft сознательно решил не возиться с.

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

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

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

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

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

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

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

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

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

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 ++ страдает ни одна из этих проблем. Компилятор не заботится о прохождении типов вниз к любой VM - если оба ваши объекты имеют функцию .Name (), он будет компилировать. Если они этого не делают, это не будет. Просто.

Итак, у вас есть :-)

Ответил 28/08/2008 в 10:50
источник пользователем

голоса
61

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

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

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который имеет одно значение аргумента. Он также определяет специализацию для этого шаблона , который используется всякий раз , когда аргумент имеет значение 1. Это позволяет мне определить рекурсию определения шаблона. Я считаю , что это было впервые обнаружено Александреской .

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

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

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

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

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

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

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*.
Ответил 28/08/2008 в 08:11
источник пользователем

голоса
35

Сам Хейлсберг описал различия здесь « Generics в C #, Java и C ++ ».

Ответил 28/08/2008 в 06:14
источник пользователем

голоса
18

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

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

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

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

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

Конечно, ВС не идиоты или трусы. Причина, почему они «струсил», было то, что Java была значительно старше и более широко, чем .NET, когда они ввели генерики. (Они были введены примерно в то же время в обоих мирах.) Совместимость Ломать назад была бы огромная боль.

Положите еще один способ: в Java, Дженерики являются частью языка (что означает , что они применяются только к Java, а не на других языках), в .NET они являются частью виртуальной машины (что означает , что они распространяются на всех языках, а не только C # и Визуальный Basic.NET).

Сравните это с .NET функции , такие как LINQ, лямбда - выражения, локального вывода переменной типа, анонимные типы и деревья выражений: все эти языковые особенности. Вот почему есть тонкие различия между VB.NET и C #: если эти функции были частью VM, они будут одинаковыми на всех языках. Но 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.

Ответил 29/08/2008 в 01:57
источник пользователем

голоса
14

Последующие меры по моей предыдущей публикации.

Шаблоны одна из главных причин, почему C ++ терпит неудачу так плачевно в IntelliSense, независимо от используемого 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это не функция, а тип, это утверждение будет по- прежнему в силе и выполнения специальных литых (функция стиле произнесения) , которая часто вызова конструктора. Компилятор не может сказать , какие мы имеем в виду , поэтому мы должны неоднозначности здесь.

Ответил 28/08/2008 в 08:57
источник пользователем

голоса
6

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

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

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

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

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

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

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

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

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

Ответил 28/08/2008 в 09:52
источник пользователем

голоса
4

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

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

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

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

public void Foo(Collection<?> thing)

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

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

Ответил 10/07/2009 в 14:49
источник пользователем

голоса
2

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

Ответил 28/08/2008 в 06:14
источник пользователем

голоса
1

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

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

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

Ответил 01/09/2008 в 19:53
источник пользователем

голоса
1

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

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

Обратите внимание, что тип «а» является список массива, а не списка строк. Таким образом, тип списка бананов будет равен () список обезьян.

Так сказать.

Ответил 28/08/2008 в 08:22
источник пользователем

голоса
1

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

Ответил 28/08/2008 в 07:32
источник пользователем

голоса
1

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

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

Ответил 28/08/2008 в 06:15
источник пользователем

голоса
0

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

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

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

Ответил 03/08/2010 в 16:33
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more