В чем различия между "родовыми" типами в С++ и Java?

Java имеет generics, а С++ - очень сильная модель программирования с template s. Итак, в чем разница между С++ и Java-дженериками?

Ответ 1

Между ними существует большая разница. В С++ вам не нужно указывать класс или интерфейс для родового типа. Вот почему вы можете создавать действительно общие функции и классы, с оговоркой о свободной печати. ​​

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

В С++ общие функции/классы могут быть определены только в заголовках, поскольку компилятор генерирует разные функции для разных типов (с которыми он ссылается). Таким образом, компиляция выполняется медленнее. В Java компиляция не имеет большого штрафа, но Java использует метод, называемый "erasure", где родовой тип стирается во время выполнения, поэтому во время выполнения Java фактически вызывает...

Something sum(Something a, Something b) { return a.add ( b ); }

Таким образом, универсальное программирование в Java не очень полезно, это лишь немного синтаксического сахара, чтобы помочь с новой конструкцией foreach.

РЕДАКТИРОВАТЬ: мнение выше о полезности было написано более молодым человеком. Java-генераторы, конечно же, помогают с безопасностью типов.

Ответ 2

Java Generics массово отличаются от шаблонов С++.

В основном в С++ шаблоны - это в основном прославленный препроцессор/макросета ( Примечание:, поскольку некоторые люди, похоже, неспособны понять аналогию, я не говорю, что обработка шаблона - это макрос). В Java они в основном являются синтаксическим сахаром, чтобы свести к минимуму шаблонное литье объектов. Вот довольно приличное введение в шаблоны С++ и общие дженерики Java.

Чтобы подробнее остановиться на этом вопросе: когда вы используете шаблон С++, вы в основном создаете еще одну копию кода, как если бы вы использовали макрос #define. Это позволяет вам делать такие вещи, как параметры int в определениях шаблонов, которые определяют размеры массивов и т.д.

Java не работает так. В Java все объекты, простирающиеся от java.lang.Object, поэтому, перед Generic, вы должны написать код следующим образом:

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> можно использовать всякий раз, когда вызывается Map). Сравните это с .Net/С#, где стирание стилей не используется, что приводит к разным различиям (например, вы можете использовать примитивные типы, а IEnumerable и IEnumerable<T> не имеют никакого отношения друг к другу).

И класс с использованием обобщений, скомпилированных с помощью компилятора Java 5+, можно использовать в JDK 1.4 (при условии, что он не использует никаких других функций или классов, для которых требуется Java 5 +).

Вот почему Java Generics называются синтаксическим сахаром.

Но это решение о том, как делать дженерики, имеет такие глубокие последствия, что часто возникали (превосходные) часто задаваемые вопросы Java Generics, чтобы ответить на много, много вопросов о Java Generics.

Шаблоны С++ имеют ряд функций, которые нет в Java Generics:

  • Использование аргументов примитивного типа.

    Например:

    template<class T, int i>
    class Matrix {
      int T[i][i];
      ...
    }
    

    Java не разрешает использование аргументов примитивного типа в generics.

  • Использование аргументов типа по умолчанию, что является одной из функций, которые я пропускаю на Java, но для этого есть причины обратной совместимости;

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

Например:

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

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

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

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

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

Ответ 3

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

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

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

Подумайте о шаблонах С++ как действительно хорошей макросистеме и Java-дженериках в качестве инструмента для автоматического создания приемов типов.

 

Ответ 4

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

Ответ 5

Существует большое объяснение этой темы в Java Generics and Collections  Морис Нафталин, Филипп Вадлер. Я очень рекомендую эту книгу. Цитировать:

Общие понятия в Java напоминают шаблоны в С++.... Синтаксис преднамеренно аналогичные и семантика намеренно разные.... Семантически, дженерики Java определяемый стиранием, где С++ шаблоны определяются расширением.

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

alt text http://oreilly.com/catalog/covers/0596527756_cat.gif

Ответ 6

Другим преимуществом шаблонов С++ является спецификация.

<typename T> T sum(T a, T b) { return a + b; }
<typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Special sum(const Special& a, const Special& b) { return a.plus(b); }

Теперь, если вы вызовете сумму с указателями, вызывается второй метод, если вы вызываете сумму с объектами без указателей, будет вызываться первый метод, и если вы вызовете sum() со специальными объектами, третий будет называется. Я не думаю, что это возможно с Java.

Ответ 7

В принципе, шаблоны AFAIK, С++ создают копию кода для каждого типа, тогда как Java-дженерики используют точно такой же код.

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

Если вы знакомы с механизмом шаблонов С++, вы можете подумать, что дженерики похожи, но сходство поверхностно. Дженерики не генерируют новый класс для каждой специализации и не допускают "метапрограммирование шаблонов".

from: Java Generics

Ответ 8

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

Ответ 9

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

Пример:

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

В 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");
        }
    }
}

В С# вы можете писать почти то же самое. Попробуйте переписать его на С++, и он не будет компилироваться, жалуясь на бесконечное расширение шаблонов.

Ответ 10

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

Ответ 11

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