Ответы:
Между ними есть большая разница. В 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 помогают, конечно, с безопасностью типов.
extends
или super
. Ответ неверный,
Java Generics сильно отличается от шаблонов 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 появился настолько поздно, что они не хотели нарушать обратную совместимость (a Map<String, String>
может использоваться всякий раз, когда требуется a Map
). Сравните это с .Net / C # , где типа стирание не используется, что приводит к разному роду различий (например , вы можете использовать примитивные типы и IEnumerable
и IEnumerable<T>
не имеете никакого отношения друг к другу).
И класс, использующий дженерики, скомпилированные с помощью компилятора Java 5+, можно использовать в JDK 1.4 (при условии, что он не использует никаких других функций или классов, требующих Java 5+).
Вот почему Java Generics называют синтаксическим сахаром .
Но это решение о том, как сделать дженерики, имеет глубокие последствия настолько, что возникли (превосходные) часто задаваемые вопросы по дженерикам Java, чтобы ответить на многие, многие вопросы, которые возникают у людей о дженериках Java.
Шаблоны C ++ имеют ряд функций, которых нет в Java Generics:
Использование аргументов примитивного типа.
Например:
template<class T, int i>
class Matrix {
int T[i][i];
...
}
Java не позволяет использовать аргументы примитивного типа в обобщениях.
Использование аргументов типа по умолчанию - это одна из функций, которую мне не хватает в Java, но для этого есть причины обратной совместимости;
Например:
public class ObservableList<T extends List> {
...
}
Действительно нужно подчеркнуть, что вызовы шаблонов с разными аргументами действительно являются разными типами. Они даже не разделяют статических членов. В Java это не так.
Помимо различий с дженериками, для полноты приведем базовое сравнение C ++ и Java (и еще одного ).
И я также могу предложить мышление на Java . Как программист на C ++, многие понятия, такие как объекты, уже будут второй натурой, но есть небольшие различия, поэтому может быть целесообразно иметь вводный текст, даже если вы просматриваете части.
Многое из того, что вы узнаете при изучении Java, - это все библиотеки (обе стандартные - то, что входит в JDK - и нестандартные, которые включают в себя часто используемые вещи, такие как Spring). Синтаксис Java более многословен, чем синтаксис C ++, и не имеет большого количества функций C ++ (например, перегрузка операторов, множественное наследование, механизм деструктора и т. Д.), Но это не делает его строго подмножеством C ++.
Map map = new HashMap<String, String>
. Это означает, что вы можете развернуть новый код на старой JVM, и он будет работать из-за сходства в байт-коде.
C ++ имеет шаблоны. В Java есть дженерики, которые выглядят как шаблоны C ++, но они очень разные.
Шаблоны работают, как следует из названия, предоставляя компилятору шаблон (подождите его ...), который он может использовать для генерации безопасного кода, заполняя параметры шаблона.
Обобщения, как я их понимаю, работают наоборот: параметры типа используются компилятором для проверки того, что код, использующий их, безопасен для типов, но полученный код генерируется без типов вообще.
Думайте о шаблонах C ++ как о действительно хорошей макросистеме, а обобщения Java как о инструменте для автоматической генерации типов типов.
const
. Объект в C ++ не будет изменен через const
указатель, если const
-ness не удалена. Аналогично, неявное приведение, созданное универсальными типами в Java, гарантированно будет «безопасным», если только параметры типа не будут вручную отброшены где-нибудь в коде.
Еще одна особенность, которая есть в шаблонах C ++, которых нет у дженериков Java, - это специализация. Это позволяет вам иметь другую реализацию для определенных типов. Таким образом, вы можете, например, иметь высоко оптимизированную версию для int , но при этом иметь универсальную версию для остальных типов. Или вы можете иметь разные версии для типов указателей и не указателей. Это удобно, если вы хотите работать с разыменованным объектом, когда передаете указатель.
Это прекрасное объяснение этой темы в Java Generics and Collections. Автор Maurice Naftalin, Philip Wadler. Я очень рекомендую эту книгу. Цитировать:
Обобщения в Java напоминают шаблоны в C ++. ... Синтаксис намеренно похож, а семантика намеренно отличается. ... Семантически, универсальные шаблоны Java определяются стиранием, а шаблоны C ++ - расширением.
Пожалуйста, прочитайте полное объяснение здесь .
(источник: oreilly.com )
По сути, шаблоны AFAIK, C ++ создают копию кода для каждого типа, в то время как шаблоны Java используют точно такой же код.
Да, вы можете сказать, что шаблон C ++ эквивалентен общему понятию Java (хотя правильнее было бы сказать, что дженерики Java по своей сути эквивалентны C ++)
Если вы знакомы с механизмом шаблонов C ++, вы можете подумать, что дженерики похожи, но сходство поверхностно. Обобщения не генерируют новый класс для каждой специализации, и при этом они не разрешают «шаблонное метапрограммирование».
от: Java Generics
Обобщения 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.
Я подведу итоги в одном предложении: шаблоны создают новые типы, общие ограничения ограничивают существующие типы.
@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); }
Ответ ниже взят из книги « Взлом кодовых решений для интервью» главы 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, использующие Tow, будут использовать статическую переменную.
/*** 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 ++ имеют ряд других отличий. Это включает:
Шаблоны - это не что иное, как макросистема. Синтаксис сахар. Они полностью раскрываются перед фактической компиляцией (или, по крайней мере, компиляторы ведут себя так, как если бы это было так).
Пример:
Допустим, мы хотим две функции. Одна функция берет две последовательности (список, массивы, векторы, что угодно) чисел и возвращает их внутреннее произведение. Другая функция принимает длину, генерирует две последовательности этой длины, передает их первой функции и возвращает ее результат. Уловка в том, что мы можем ошибиться во второй функции, так что эти две функции на самом деле не имеют одинаковую длину. Нам нужен компилятор, чтобы предупредить нас в этом случае. Не когда программа запущена, а когда она компилируется.
В 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 ++, и он не скомпилируется, жалуясь на бесконечное расширение шаблонов.
Я хотел бы процитировать Askanydifference здесь:
Основное различие между C ++ и Java заключается в их зависимости от платформы. В то время как C ++ является платформо-зависимым языком, Java является платформенно-независимым языком.
Вышеупомянутое утверждение является причиной того, почему C ++ может предоставить истинные универсальные типы. В то время как Java имеет строгую проверку и, следовательно, они не позволяют использовать дженерики так, как это позволяет C ++.