Java имеет универсальные шаблоны, а C++ предоставляет очень сильную модель программирования с template.
Итак, в чем разница между дженериками C++ и Java?




В C++ есть шаблоны. В Java есть дженерики, которые в некотором роде похожи на шаблоны C++, но они очень, очень разные.
Шаблоны работают, как следует из названия, предоставляя компилятору шаблон (подождите ...), который он может использовать для генерации типобезопасного кода путем заполнения параметров шаблона.
Дженерики, как я их понимаю, работают наоборот: параметры типа используются компилятором для проверки того, что код, использующий их, является типобезопасным, но результирующий код генерируется вообще без типов.
Подумайте о шаблонах C++ как о системе макросов действительно хорошо, а о дженериках Java как о инструменте для автоматической генерации приведений типов.
Это довольно хорошее краткое объяснение. У меня возникнет соблазн сделать одну настройку: дженерики Java - это инструмент для автоматической генерации типов которые гарантированно безопасны (с некоторыми условиями). В некотором роде они связаны с const в C++. Объект в C++ не будет изменен с помощью указателя const, если не будет отброшена его принадлежность к const. Точно так же неявное приведение типов, созданное универсальными типами в Java, гарантированно будет «безопасным», если только параметры типа не будут вручную выбраны где-нибудь в коде.
Между ними большая разница. В C++ вам не нужно указывать класс или интерфейс для универсального типа. Вот почему вы можете создавать по-настоящему общие функции и классы с осторожностью, касающейся более гибкого набора текста.
template <typename T> T sum(T a, T b) { return a + b; }
Вышеупомянутый метод добавляет два объекта одного типа и может использоваться для любого типа T, для которого доступен оператор «+».
В Java вам нужно указать тип, если вы хотите вызывать методы для переданных объектов, например:
<T extends Something> T sum(T a, T b) { return a.add ( b ); }
В C++ общие функции / классы могут быть определены только в заголовках, поскольку компилятор генерирует разные функции для разных типов (с которыми он вызывается). Так что компиляция идет медленнее. В Java компиляция не имеет серьезных недостатков, но в Java используется метод, называемый «стирание», когда общий тип стирается во время выполнения, поэтому во время выполнения Java фактически вызывает ...
Something sum(Something a, Something b) { return a.add ( b ); }
Так что универсальное программирование на Java бесполезно, это всего лишь небольшой синтаксический сахар, помогающий с новой конструкцией foreach.
Обновлено: мнение выше о полезности было написано более молодым человеком. Разумеется, дженерики Java помогают в обеспечении безопасности типов.
Он совершенно прав в том, что это просто сложный синтаксический сахар.
Это не чисто синтаксический сахар. Компилятор использует эту информацию для проверки типов. Несмотря на то, что информация недоступна во время выполнения, я бы не назвал то, что скомпилировано, просто «синтаксическим сахаром». Если вы так назовете, то C - это просто синтаксический сахар для сборки, и это просто синтаксический сахар для машинного кода :)
Я считаю синтаксический сахар полезным.
Вы пропустили главное отличие - то, что можно использовать для создания экземпляра универсального. В C++ можно использовать шаблон
Вы действительно нет должны «указать тип» в форме extends или super. Ответ неверный,
@EJP: Вы правы, говоря, что для дженериков не требуется указывать подкласс или суперкласс. Но в этом конкретном примере, где метод вызывается для объекта этого универсального типа, универсальный тип должен иметь суперкласс (или интерфейс), который объявляет этот метод.
@klaar создание подкласса не потребовалось бы, если бы интерфейс add был введен в качестве дополнительного параметра функции, также параметризованного в целом на T.
@ShelbyMooreIII Это очень интересно! Вы можете придумать пример, чтобы объяснить это?
@ user207421 Собственно нужно указать тип. Если вы этого не сделаете, это просто означает extends Object, в то время как C++ не имеет такого механизма за сценой.
Дженерики Java (и C#) кажутся простым механизмом подстановки типов во время выполнения.
Шаблоны C++ - это конструкция времени компиляции, которая дает вам возможность изменить язык в соответствии с вашими потребностями. На самом деле это чисто функциональный язык, который компилятор выполняет во время компиляции.
Еще одно преимущество шаблонов C++ - специализация.
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Special sum(const Special& a, const Special& b) { return a.plus(b); }
Теперь, если вы вызываете sum с указателями, будет вызван второй метод, если вы вызовете sum с объектами, не являющимися указателями, будет вызван первый метод, а если вы вызовете sum с объектами Special, будет вызван третий. Я не думаю, что это возможно с Java.
Может быть потому, что в Java нет указателей .. !! вы можете объяснить на лучшем примере?
@Keith:
Этот код на самом деле неверен, и помимо небольших сбоев (template опущен, синтаксис специализации выглядит иначе), частичная специализация не работает с шаблонами функций, только с шаблонами классов. Однако код будет работать без частичной специализации шаблона, вместо этого будет использоваться простая старая перегрузка:
template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Почему это ответ, а не комментарий?
@Laurence: на этот раз, потому что он был опубликован задолго до того, как комментарии были добавлены в Stack Overflow. Во-вторых, потому что это не только комментарий, но и ответ на вопрос: что-то вроде приведенного выше кода невозможно в Java.
По сути, AFAIK, шаблоны C++ создают копию кода для каждого типа, в то время как универсальные Java-шаблоны используют точно такой же код.
Да, вы можно сказать, что шаблон C++ эквивалентен универсальному Java концепция (хотя правильнее было бы сказать, что универсальные Java-шаблоны эквивалентны C++ по концепции)
If you are familiar with C++'s template mechanism, you might think that generics are similar, but the similarity is superficial. Generics do not generate a new class for each specialization, nor do they permit “template metaprogramming.”
из: Дженерики Java
Обобщения Java массово отличаются от шаблонов C++.
В основном в C++ шаблоны представляют собой прославленный набор препроцессоров / макросов (Примечание:, поскольку некоторые люди, кажется, не могут понять аналогию, я не говорю, что обработка шаблонов - это макрос). В Java они в основном представляют собой синтаксический сахар, позволяющий минимизировать шаблонное приведение объектов. Вот довольно приличный Введение в шаблоны C++ и дженерики Java.
Чтобы уточнить этот момент: когда вы используете шаблон C++, вы в основном создаете еще одну копию кода, как если бы вы использовали макрос #define. Это позволяет вам делать такие вещи, как параметры int в определениях шаблонов, которые определяют размеры массивов и т. д.
Java так не работает. В Java все объекты имеют экстент из java.lang.Object, поэтому до Generics вы должны написать такой код:
public class PhoneNumbers {
private Map phoneNumbers = new HashMap();
public String getPhoneNumber(String name) {
return (String) phoneNumbers.get(name);
}
}
потому что все типы коллекций Java использовали Object в качестве базового типа, поэтому вы могли помещать в них что угодно. Java 5 разворачивается и добавляет дженерики, чтобы вы могли делать такие вещи, как:
public class PhoneNumbers {
private Map<String, String> phoneNumbers = new HashMap<String, String>();
public String getPhoneNumber(String name) {
return phoneNumbers.get(name);
}
}
И это все Java Generics: оболочки для приведения объектов. Это потому, что Java Generics не усовершенствован. Они используют стирание шрифта. Это решение было принято потому, что Java Generics появились настолько поздно, что они не хотели нарушать обратную совместимость (Map<String, String> можно использовать всякий раз, когда требуется Map). Сравните это с .Net / C#, где стирание типов не используется, что приводит ко всевозможным различиям (например, вы можете использовать примитивные типы, а IEnumerable и IEnumerable<T> не имеют отношения друг к другу).
А класс, использующий дженерики, скомпилированные с помощью компилятора Java 5+, можно использовать в JDK 1.4 (при условии, что он не использует какие-либо другие функции или классы, требующие Java 5+).
Вот почему Java Generics называется синтаксический сахар.
Но это решение о том, как создавать дженерики, имеет настолько глубокие последствия, что (превосходный) Часто задаваемые вопросы по Java Generics возник, чтобы ответить на многие, многие вопросы людей о Java Generics.
Шаблоны C++ имеют ряд функций, которых нет в Java Generics:
Использование аргументов примитивного типа.
Например:
template<class T, int i>
class Matrix {
int T[i][i];
...
}
Java не позволяет использовать аргументы примитивного типа в обобщениях.
Использование аргументы типа по умолчанию, это одна функция, которую я упустил в Java, но для этого есть причины обратной совместимости;
Java позволяет ограничивать аргументы.
Например:
public class ObservableList<T extends List> {
...
}
Следует подчеркнуть, что вызовы шаблонов с разными аргументами действительно относятся к разным типам. У них даже нет статических членов. В Java это не так.
Помимо различий с дженериками, для полноты, вот базовое сравнение C++ и Java (и другой).
И еще могу предложить Мышление на Java. Как программист на C++, многие концепции, такие как объекты, уже станут второй натурой, но есть тонкие различия, поэтому может быть полезно иметь вводный текст, даже если вы просматриваете части.
Многое из того, что вы узнаете при изучении Java, - это все библиотеки (как стандартные, что входит в JDK, так и нестандартные, включая такие часто используемые вещи, как Spring). Синтаксис Java более подробный, чем синтаксис C++, и не имеет многих функций C++ (например, перегрузки операторов, множественного наследования, механизма деструктора и т. д.), Но это также не делает его строго подмножеством C++.
Они совершенно разные по реализации, но эквивалентны (или, по крайней мере, похожи) по концепции, - вот что спросила Тина.
Они не равнозначны по концепции. Лучшим примером является любопытно повторяющийся шаблон шаблона. Вторым по значимости является дизайн, ориентированный на политику. Третье из лучших - это то, что C++ позволяет передавать целые числа в угловых скобках (myArray).
Нет, концептуально они не равнозначны. В концепции есть некоторое совпадение, но не очень. Оба позволяют создавать List
Важно отметить, что проблема стирания типа означает больше, чем просто обратную совместимость для Map map = new HashMap<String, String>. Это означает, что вы можете развернуть новый код на старой JVM, и он будет работать из-за сходства байт-кода.
Шаблоны C++ не имеют ничего общего с препроцессорами или макросами. Они никогда не расширяются до текста.
Вы заметите, что я сказал «в основном прославленный препроцессор / макрос». Это была аналогия, потому что каждое объявление шаблона будет создавать больше кода (в отличие от Java / C#).
Возможно, он создаст, а может, и не создаст больше кода - современные компиляторы довольно хорошо удаляют повторяющийся код; но дело в том, что создание шаблона - это не что иное, как расширение текста макроса - мышление в таких терминах может привести к неприятным ошибкам.
Я не согласен. Если вы не думаете в этих терминах, вы можете ошибиться, думая, что статические члены экземпляра являются общими для разных типов шаблонов, а это не так. Код шаблона не намного превосходит копирование и вставку (и я не имею в виду это плохо), отсюда и сравнение макросов.
Код шаблона очень отличается от кода копирования и вставки. Если вы думаете о расширении макросов, рано или поздно вы столкнетесь с небольшими ошибками, такими как этот: womble.decadentplace.org.uk/c++/…
IEnumerable<T> является наследником IEnumerable, поэтому я бы не сказал, что они не имеют отношения друг к другу.
Обобщения Java - это синтаксический сахар нет, поскольку они расширяют Java система типов. Это правда, что ограничения JVM не позволяют им предлагать такие преимущества эффективности, которые другие языки получают от параметрического полиморфизма, но это проблема реализации.
@NemanjaTrifunovic Я использовал несколько компиляторов C++, где шаблоны действительно расширялись до текста.
@cletus Ссылка, которую вы предоставили для default type arguments, просто ведет на домашнюю страницу документации IBM. Это могло быть сломано. Проверь это.
Есть отличное объяснение этой темы в Обобщения и коллекции Java Авторы Морис Нафталин, Филип Вадлер. Я очень рекомендую эту книгу. Цитировать:
Generics in Java resemble templates in C++. ... The syntax is deliberately similar and the semantics are deliberately different. ... Semantically, Java generics are defined by erasure, where as C++ templates are defined by expansion.
Пожалуйста, прочтите полное объяснение здесь.

(source: oreilly.com)
Еще одна особенность шаблонов C++, которых нет у универсальных шаблонов Java, - это специализация. Это позволяет вам иметь разные реализации для определенных типов. Таким образом, вы можете, например, иметь высокооптимизированную версию для int, но при этом иметь общую версию для остальных типов. Или у вас могут быть разные версии для типов указателей и без указателей. Это удобно, если вы хотите работать с разыменованным объектом при передаче указателя.
Специализация шаблона +1 невероятно важна для метапрограммирования во время компиляции - это различие само по себе делает java-дженерики гораздо менее эффективными.
Шаблоны - это не что иное, как макросистема. Синтаксический сахар. Они полностью раскрываются перед фактической компиляцией (или, по крайней мере, компиляторы ведут себя так, как если бы это было так).
Пример:
Допустим, нам нужны две функции. Одна функция принимает две последовательности (список, массивы, векторы, что угодно) чисел и возвращает их внутренний продукт. Другая функция принимает длину, генерирует две последовательности такой длины, передает их первой функции и возвращает ее результат. Загвоздка в том, что мы можем ошибиться во второй функции, так что эти две функции на самом деле не одинаковой длины. В этом случае нам нужно, чтобы компилятор предупредил нас. Не когда программа запущена, а когда она компилируется.
В Java вы можете сделать что-то вроде этого:
import java.io.*;
interface ScalarProduct<A> {
public Integer scalarProduct(A second);
}
class Nil implements ScalarProduct<Nil>{
Nil(){}
public Integer scalarProduct(Nil second) {
return 0;
}
}
class Cons<A implements ScalarProduct<A>> implements ScalarProduct<Cons<A>>{
public Integer value;
public A tail;
Cons(Integer _value, A _tail) {
value = _value;
tail = _tail;
}
public Integer scalarProduct(Cons<A> second){
return value * second.value + tail.scalarProduct(second.tail);
}
}
class _Test{
public static Integer main(Integer n){
return _main(n, 0, new Nil(), new Nil());
}
public static <A implements ScalarProduct<A>>
Integer _main(Integer n, Integer i, A first, A second){
if (n == 0) {
return first.scalarProduct(second);
} else {
return _main(n-1, i+1,
new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
//the following line won't compile, it produces an error:
//return _main(n-1, i+1, first, new Cons<A>(i*i, second));
}
}
}
public class Test{
public static void main(String [] args){
System.out.print("Enter a number: ");
try {
BufferedReader is =
new BufferedReader(new InputStreamReader(System.in));
String line = is.readLine();
Integer val = Integer.parseInt(line);
System.out.println(_Test.main(val));
} catch (NumberFormatException ex) {
System.err.println("Not a valid number");
} catch (IOException e) {
System.err.println("Unexpected IO ERROR");
}
}
}
На C# можно написать почти то же самое. Попробуйте переписать его на C++, и он не будет компилироваться, жалуясь на бесконечное расширение шаблонов.
Хорошо, ему 3 года, но я все равно отвечаю. Я не понимаю твоего мнения. Вся причина, по которой Java генерирует ошибку для этой прокомментированной строки, заключается в том, что вы вызываете функцию, которая ожидает двух A с разными аргументами (A и Cons), и это действительно просто, а также происходит, когда не используются дженерики. C++ тоже это делает. Кроме того, этот код вызвал у меня рак, потому что он действительно ужасен. Тем не менее, вы все равно сделали бы это в C++, вы, конечно, должны сделать это, потому что C++ не является Java, но это не является преимуществом шаблонов C++.
@clocktown нет, вы НЕ МОЖЕТЕ сделать это на C++. Никакие модификации этого не позволят. И это недостаток шаблонов C++.
То, что ваш код должен был делать - предупреждать о разной длине - этого не делать. В вашем закомментированном примере он создает ошибки только из-за несовпадения аргументов. Это работает и на C++. Вы можете ввести код, который семантически эквивалентен и намного лучше, чем этот беспорядок в C++ и Java.
Оно делает. Аргументы не совпадают в точности из-за разной длины. Вы не можете этого сделать в C++.
Я резюмирую это одним предложением: шаблоны создают новые типы, обобщения ограничивают существующие типы.
Ваше объяснение так кратко! И имеет смысл для людей, которые хорошо разбираются в теме. Но людям, которые еще этого не понимают, это не очень помогает. (В таком случае кто-нибудь задает вопрос по поводу SO, понятно?)
Ответ ниже взят из книги Cracking The Coding Интервью Solutions to Chapter 13, что я считаю очень хорошим.
Реализация универсальных шаблонов Java основана на идее «стирания типов»: этот метод устраняет параметризованные типы, когда исходный код транслируется в байт-код виртуальной машины Java (JVM). Например, предположим, что у вас есть код Java ниже:
Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);
Во время компиляции этот код переписывается в:
Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);
Использование дженериков Java не сильно повлияло на наши возможности; это просто сделало вещи немного красивее. По этой причине универсальные шаблоны Java иногда называют «синтаксическим сахаром:».
Это сильно отличается от C++. В C++ шаблоны, по сути, представляют собой прославленный набор макросов, в котором компилятор создает новую копию кода шаблона для каждого типа. Доказательством этого является тот факт, что экземпляр MyClass не будет использовать статическую переменную совместно с MyClass. Однако буксируемые экземпляры MyClass будут совместно использовать статическую переменную.
/*** MyClass.h ***/
template<class T> class MyClass {
public:
static int val;
MyClass(int v) { val v;}
};
/*** MyClass.cpp ***/
template<typename T>
int MyClass<T>::bar;
template class MyClass<Foo>;
template class MyClass<Bar>;
/*** main.cpp ***/
MyClass<Foo> * fool
MyClass<Foo> * foo2
MyClass<Bar> * barl
MyClass<Bar> * bar2
new MyClass<Foo>(10);
new MyClass<Foo>(15);
new MyClass<Bar>(20);
new MyClass<Bar>(35);
int fl fool->val; // will equal 15
int f2 foo2->val; // will equal 15
int bl barl->val; // will equal 35
int b2 bar2->val; // will equal 35
В Java статические переменные совместно используются экземплярами MyClass, независимо от параметров разных типов.
Дженерики Java и шаблоны C++ имеют ряд других отличий. К ним относятся:
Я хотел бы процитировать здесь спросить любую разницу:
Основное различие между C++ и Java заключается в их зависимости от платформы. В то время как C++ - это платформенно-зависимый язык, Java - платформенно-независимый язык.
Вышеупомянутое утверждение является причиной того, почему C++ может предоставлять истинные универсальные типы. Хотя в Java есть строгая проверка, и, следовательно, они не позволяют использовать дженерики так, как это позволяет C++.