Каковы различия между «универсальными» типами в C ++ и Java?


Ответы:


144

Между ними есть большая разница. В 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 помогают, конечно, с безопасностью типов.


27
Он совершенно прав, что это просто сложный синтаксический сахар.
alphazero

31
Это не чисто синтаксический сахар. Компилятор использует эту информацию для проверки типов. Несмотря на то, что информация недоступна во время выполнения, я бы не назвал то, что использует скомпилированное, просто «синтаксический сахар». Если бы вы назвали это так, тогда C - это синтаксический сахар для сборки, а это просто синтаксический сахар для машинного кода :)
dtech

42
Я думаю, что синтаксический сахар полезен.
poitroae

5
Вы пропустили главное отличие, которое вы можете использовать для создания универсального. В c ++ можно использовать шаблон <int N> и получить другой результат для любого числа, использованного для его создания. Используется для метапрограммирования во время компиляции. Как ответ в: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal

2
Вы не должны «указать тип», в виде либо extendsили super. Ответ неверный,
маркиз Лорн

124

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, но для этого есть причины обратной совместимости;

  • Java позволяет ограничивать аргументы.

Например:

public class ObservableList<T extends List> {
  ...
}

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

Помимо различий с дженериками, для полноты приведем базовое сравнение C ++ и Javaеще одного ).

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

Многое из того, что вы узнаете при изучении Java, - это все библиотеки (обе стандартные - то, что входит в JDK - и нестандартные, которые включают в себя часто используемые вещи, такие как Spring). Синтаксис Java более многословен, чем синтаксис C ++, и не имеет большого количества функций C ++ (например, перегрузка операторов, множественное наследование, механизм деструктора и т. Д.), Но это не делает его строго подмножеством C ++.


1
Они не эквивалентны по концепции. Лучший пример - любопытно повторяющийся шаблон. На втором месте стоит дизайн, ориентированный на политику. На третьем месте - факт, что C ++ позволяет передавать целые числа в угловых скобках (myArray <5>).
Макс Либберт

1
Нет, они не эквивалентны по концепции. Есть некоторое совпадение в концепции, но не так много. Оба позволяют вам создавать List <T>, но это все, что нужно. Шаблоны C ++ идут намного дальше.
jalf

5
Важно отметить, что проблема удаления типа означает больше, чем просто обратная совместимость Map map = new HashMap<String, String>. Это означает, что вы можете развернуть новый код на старой JVM, и он будет работать из-за сходства в байт-коде.
Ювал Адам

1
Вы заметите, что я сказал «в основном прославленный препроцессор / макрос». Это была аналогия, потому что каждое объявление шаблона будет создавать больше кода (в отличие от Java / C #).
Клет

4
Код шаблона сильно отличается от копирования и вставки. Если вы думаете с точки зрения расширения макросов, рано или поздно вы столкнетесь с
Nemanja Trifunovic

86

C ++ имеет шаблоны. В Java есть дженерики, которые выглядят как шаблоны C ++, но они очень разные.

Шаблоны работают, как следует из названия, предоставляя компилятору шаблон (подождите его ...), который он может использовать для генерации безопасного кода, заполняя параметры шаблона.

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

Думайте о шаблонах C ++ как о действительно хорошей макросистеме, а обобщения Java как о инструменте для автоматической генерации типов типов.

 


4
Это довольно хорошее, краткое объяснение. Один из трюков, который мне хотелось бы сделать, состоит в том, что дженерики Java - это инструмент для автоматического создания типов типов , которые гарантированно безопасны (с некоторыми условиями). В некотором смысле они связаны с C ++ const. Объект в C ++ не будет изменен через constуказатель, если const-ness не удалена. Аналогично, неявное приведение, созданное универсальными типами в Java, гарантированно будет «безопасным», если только параметры типа не будут вручную отброшены где-нибудь в коде.
Лоуренс Гонсалвес

16

Еще одна особенность, которая есть в шаблонах C ++, которых нет у дженериков Java, - это специализация. Это позволяет вам иметь другую реализацию для определенных типов. Таким образом, вы можете, например, иметь высоко оптимизированную версию для int , но при этом иметь универсальную версию для остальных типов. Или вы можете иметь разные версии для типов указателей и не указателей. Это удобно, если вы хотите работать с разыменованным объектом, когда передаете указатель.


1
+1 специализация шаблона невероятно важна для метапрограммирования во время компиляции - эта разница сама по себе делает дженерики java гораздо менее мощными
Фейсал Вали

13

Это прекрасное объяснение этой темы в Java Generics and Collections. Автор Maurice Naftalin, Philip Wadler. Я очень рекомендую эту книгу. Цитировать:

Обобщения в Java напоминают шаблоны в C ++. ... Синтаксис намеренно похож, а семантика намеренно отличается. ... Семантически, универсальные шаблоны Java определяются стиранием, а шаблоны C ++ - расширением.

Пожалуйста, прочитайте полное объяснение здесь .

альтернативный текст
(источник: oreilly.com )


5

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

Да, вы можете сказать, что шаблон C ++ эквивалентен общему понятию Java (хотя правильнее было бы сказать, что дженерики Java по своей сути эквивалентны C ++)

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

от: Java Generics


3

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


3

Еще одним преимуществом шаблонов 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.


2
Может быть потому что у Java нет указателей .. !! Вы можете объяснить с лучшим примером?
Бхавук Матур

2

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


2
Ваше объяснение так кратко! И имеет смысл для людей, которые хорошо понимают эту тему. Но для людей, которые еще этого не понимают, это не очень помогает. (В каком случае кто-нибудь задает вопрос по SO, понял?)
Jakub

1

@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); }

2
Почему это ответ, а не комментарий?
Лоуренс Гонсалвес

3
@Laurence: на этот раз, потому что он был опубликован задолго до того, как комментарии были реализованы в переполнении стека. С другой стороны, потому что это не только комментарий, но и ответ на вопрос: что-то вроде приведенного выше кода невозможно в Java.
Конрад Рудольф

1

Ответ ниже взят из книги « Взлом кодовых решений для интервью» главы 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 ++ имеют ряд других отличий. Это включает:

  • Шаблоны C ++ могут использовать примитивные типы, такие как int. Java не может и должна вместо этого использовать Integer.
  • В Java вы можете ограничить параметры типа шаблона определенным типом. Например, вы можете использовать обобщенные элементы для реализации CardDeck и указать, что параметр типа должен расширяться из CardGame.
  • В C ++ может быть создан экземпляр параметра type, тогда как Java не поддерживает это.
  • В Java параметр типа (т. Е. Foo в MyClass) нельзя использовать для статических методов и переменных, поскольку они будут разделены между MyClass и MyClass. В C ++ эти классы различны, поэтому параметр типа может использоваться для статических методов и переменных.
  • В Java все экземпляры MyClass, независимо от параметров их типа, имеют одинаковый тип. Параметры типа стираются во время выполнения. В C ++ экземпляры с разными параметрами типов являются разными типами.

0

Шаблоны - это не что иное, как макросистема. Синтаксис сахар. Они полностью раскрываются перед фактической компиляцией (или, по крайней мере, компиляторы ведут себя так, как если бы это было так).

Пример:

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

В 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 <A>), и это действительно просто, а также происходит, когда не используются никакие обобщения. C ++ тоже это делает. Кроме того, этот код дал мне рак, потому что он действительно ужасен. Тем не менее, вы все равно будете делать это в C ++, вам, конечно, придется вносить изменения, потому что C ++ не является Java, но это не является недостатком шаблонов C ++.
Clocktown

@clocktown нет, вы не можете сделать это в C ++. Никакие изменения не позволят этого. И это является недостатком шаблонов C ++.
MigMit

То, что должен был делать ваш Кодекс - предупреждать о разной длине - не делает. В вашем закомментированном примере он выдает только ошибки из-за несоответствующих аргументов. Это работает и в C ++. Вы можете набирать код, который семантически эквивалентен и намного лучше, чем этот беспорядок в C ++ и в Java.
Clocktown

Оно делает. Аргументы не совпадают точно, потому что длины разные. Вы не можете сделать это в C ++.
MigMit

0

Я хотел бы процитировать Askanydifference здесь:

Основное различие между C ++ и Java заключается в их зависимости от платформы. В то время как C ++ является платформо-зависимым языком, Java является платформенно-независимым языком.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.