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

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

stackoverflow.com/questions/31693
n611x007 15.12.2012 22:08
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
158
1
97 530
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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

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

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

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

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

Laurence Gonsalves 01.03.2010 23:13
Ответ принят как подходящий

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

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

alphazero 05.08.2011 03:56

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

dtech 04.02.2013 02:17

Я считаю синтаксический сахар полезным.

poitroae 14.02.2013 13:08

Вы пропустили главное отличие - то, что можно использовать для создания экземпляра универсального. В C++ можно использовать шаблон и получить другой результат для любого числа, использованного для его создания. Он используется для мета-программирования во время компиляции. Как ответ в: stackoverflow.com/questions/189172/c-templates-turing-comple te

stonemetal 24.08.2013 20:32

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

user207421 26.04.2016 13:06

@EJP: Вы правы, говоря, что для дженериков не требуется указывать подкласс или суперкласс. Но в этом конкретном примере, где метод вызывается для объекта этого универсального типа, универсальный тип должен иметь суперкласс (или интерфейс), который объявляет этот метод.

klaar 03.05.2016 17:54

@klaar создание подкласса не потребовалось бы, если бы интерфейс add был введен в качестве дополнительного параметра функции, также параметризованного в целом на T.

Shelby Moore III 16.01.2018 00:50

@ShelbyMooreIII Это очень интересно! Вы можете придумать пример, чтобы объяснить это?

klaar 16.01.2018 11:14
«В C++ общие функции / классы могут быть определены только в заголовках, ...» not necessarily. See this stackoverflow.com/a/115821
Ayxan Haqverdili 06.07.2020 16:10

@ user207421 Собственно нужно указать тип. Если вы этого не сделаете, это просто означает extends Object, в то время как C++ не имеет такого механизма за сценой.

Sourav Kannantha B 08.02.2021 13:12

Дженерики 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 нет указателей .. !! вы можете объяснить на лучшем примере?

Bhavuk Mathur 22.08.2016 05:41

@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 Gonsalves 01.03.2010 23:15

@Laurence: на этот раз, потому что он был опубликован задолго до того, как комментарии были добавлены в Stack Overflow. Во-вторых, потому что это не только комментарий, но и ответ на вопрос: что-то вроде приведенного выше кода невозможно в Java.

Konrad Rudolph 01.03.2010 23:30

По сути, 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++.

Они совершенно разные по реализации, но эквивалентны (или, по крайней мере, похожи) по концепции, - вот что спросила Тина.

OscarRyz 31.01.2009 08:33

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

Max Lybbert 31.01.2009 11:40

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

jalf 31.01.2009 21:56

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

Yuval Adam 31.01.2009 21:57

Шаблоны C++ не имеют ничего общего с препроцессорами или макросами. Они никогда не расширяются до текста.

Nemanja Trifunovic 01.02.2009 02:01

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

cletus 01.02.2009 02:03

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

Nemanja Trifunovic 01.02.2009 02:58

Я не согласен. Если вы не думаете в этих терминах, вы можете ошибиться, думая, что статические члены экземпляра являются общими для разных типов шаблонов, а это не так. Код шаблона не намного превосходит копирование и вставку (и я не имею в виду это плохо), отсюда и сравнение макросов.

cletus 01.02.2009 03:26

Код шаблона очень отличается от кода копирования и вставки. Если вы думаете о расширении макросов, рано или поздно вы столкнетесь с небольшими ошибками, такими как этот: womble.decadentplace.org.uk/c++/…

Nemanja Trifunovic 02.02.2009 05:20

IEnumerable<T> является наследником IEnumerable, поэтому я бы не сказал, что они не имеют отношения друг к другу.

svick 25.09.2010 00:57

Обобщения Java - это синтаксический сахар нет, поскольку они расширяют Java система типов. Это правда, что ограничения JVM не позволяют им предлагать такие преимущества эффективности, которые другие языки получают от параметрического полиморфизма, но это проблема реализации.

dfeuer 09.01.2015 10:28

@NemanjaTrifunovic Я использовал несколько компиляторов C++, где шаблоны действительно расширялись до текста.

user207421 26.04.2016 13:07

@cletus Ссылка, которую вы предоставили для default type arguments, просто ведет на домашнюю страницу документации IBM. Это могло быть сломано. Проверь это.

Sourav Kannantha B 08.02.2021 13:22

Есть отличное объяснение этой темы в Обобщения и коллекции 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.

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

alt text
(source: oreilly.com)

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

Специализация шаблона +1 невероятно важна для метапрограммирования во время компиляции - это различие само по себе делает java-дженерики гораздо менее эффективными.

Faisal Vali 15.06.2009 22:38

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

Пример:

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

В 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 04.12.2016 18:12

@clocktown нет, вы НЕ МОЖЕТЕ сделать это на C++. Никакие модификации этого не позволят. И это недостаток шаблонов C++.

MigMit 05.12.2016 14:06

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

clocktown 06.12.2016 14:30

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

MigMit 06.12.2016 16:04

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

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

Jakub 03.11.2016 18:08

Ответ ниже взят из книги 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++ могут использовать примитивные типы, такие как int. Java не может и должна вместо этого используйте Integer.
  • В Java вы можете ограничить параметры типа шаблона, чтобы они имели определенного типа. Например, вы можете использовать дженерики для реализации CardDeck и укажите, что параметр типа должен расширяться от Карточная игра.
  • В C++ можно создать экземпляр параметра типа, в то время как Java этого не делает. поддержите это.
  • В Java параметр типа (т.е. Foo в MyClass) не может быть используется для статических методов и переменных, поскольку они будут совместно использоваться MyClass и MyClass. В C++ эти классы разные, поэтому параметр типа можно использовать для статических методов и переменных.
  • В Java все экземпляры MyClass, независимо от их параметров типа, относятся к одному типу. Параметры типа стираются во время выполнения. В C++ экземпляры с разными параметрами типа являются разными типами.

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

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

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

Другие вопросы по теме