Які відмінності між "загальними" типами в 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.

EDIT: думку вище про корисність написала молодша власна особа. Загальна інформація 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 ++ - це в основному прославлений препроцесор / набір макросів ( Примітка: оскільки деякі люди здаються нездатними зрозуміти аналогію, я не кажу, що обробка шаблону є макросом). На Яві вони в основному синтаксичні цукру, щоб мінімізувати викидання об'єктів на колонці. Ось досить пристойний вступ до шаблонів C ++ та generic 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>може бути використана всякий раз, коли Mapвикликається необхідність). Порівняйте це з .Net / C #, де стирання типу не використовується, що призводить до різного роду відмінностей (наприклад, ви можете використовувати примітивні типи та IEnumerableіIEnumerable<T> не маєте ніякого відношення один до одного).

А клас, що використовує дженерики, зібрані з компілятором Java 5+, можна використовувати на JDK 1.4 (якщо припускати, що він не використовує жодних інших функцій чи класів, для яких потрібна Java 5+).

Ось чому Java Generics називають синтаксичним цукром .

Але це рішення про те, як робити генеріки, має настільки глибокі ефекти, що (чудовий) FAQ щодо генеріалів Java з’явився, щоб відповісти на безліч питань, що виникають у людей щодо 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 ++.


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

1
Ні, вони не еквівалентні в концепції. У концепції є певне збіг, але не дуже. Обидва дозволяють створити Список <T>, але це приблизно настільки, наскільки це йде. Шаблони C ++ йдуть набагато далі.
jalf

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

1
Ви зауважите, що я сказав "в основному прославлений препроцесор / макрос". Це було аналогією, оскільки кожна заява шаблону створюватиме більше коду (на відміну від Java / C #).
клент

4
Код шаблону сильно відрізняється від копіювання та вставки. Якщо ви думаєте з точки зору розширення макросу, рано чи пізно вас наткнуть
Неманя Трифунович

86

C ++ має шаблони. У Java є дженерики, які виглядають начебто на зразок шаблонів C ++, але вони дуже-дуже різні.

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

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

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

 


4
Це досить гарне, стисле пояснення. Мені б сподобатися, що Java generics - це інструмент для автоматичного генерування набору типів , які гарантовано є безпечними (за певних умов). Певним чином вони пов'язані з C ++ const. Об'єкт в C ++ не буде змінено через constпокажчик, якщо тільки const-ness не буде відкинуто. Аналогічно, неявні касти, створені загальними типами на Java, гарантовано є "безпечними", якщо параметри типу не відкидаються вручну десь у коді.
Лоранс Гонсальвес

16

Ще одна особливість, що у шаблонах C ++ є те, що дженерики Java не є спеціалізацією. Це дозволяє мати різну реалізацію для конкретних типів. Таким чином, ви можете, наприклад, мати оптимізовану версію для int , тоді як для решти типів ще маєте загальну версію. Або ви можете мати різні версії для типів вказівника та не вказівника. Це стане в нагоді, якщо ви хочете оперувати об'єктом, що перебуває під час передачі вказівника.


1
Спеціалізація шаблонів +1 надзвичайно важлива для метапрограмування під час компіляції - ця різниця сама по собі робить генеріки java набагато менш потужними
Faisal Vali

13

Цю тему є чудовим поясненням у Java Generics and Collections Моріса Нафталіна, Філіпа Вадлера. Я дуже рекомендую цю книгу. Цитувати:

Дженріки на Java нагадують шаблони на C ++. ... Синтаксис свідомо схожий, а семантика навмисно різна. ... Семантично, дженерики Java визначаються стиранням, де як C ++ шаблони визначаються розширенням.

Прочитайте повне пояснення тут .

alt текст
(джерело: oreilly.com )


5

В основному, шаблони AFAIK, C ++ створюють копію коду для кожного типу, тоді як Java generics використовує абсолютно той самий код.

Так, ви можете сказати, що шаблон C ++ еквівалентний загальній концепції Java (хоча правильніше було б сказати, що дженерики Java в концепції еквівалентні C ++)

Якщо ви знайомі з механізмом шаблонів C ++, ви можете подумати, що дженерики схожі, але подібність поверхнева. Generics не створює новий клас для кожної спеціалізації, а також не дозволяє "метапрограмування шаблонів".

від: 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з Specialоб'єктами, буде викликаний третій. Я не думаю, що це можливо для Java.


2
Можливо, тому, що у Java немає вказівників .. !! ви можете пояснити кращим прикладом?
Bhavuk Mathur

2

Я підсумую його в одному реченні: шаблони створюють нові типи, генерика обмежує існуючі типи.


2
Ваше пояснення таке коротке! І має цілком сенс для людей, які добре розуміють тему. Але для людей, які цього ще не розуміють, це не дуже допомагає. (Який випадок, коли хтось задає питання щодо SO, зрозумів?)
Якуб

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

Відповідь нижче - із книги Cracking The Coding Interview Solutions до розділу 13, що, на мою думку, дуже добре.

Реалізація дженерики Java базується на ідеї "стирання типу". Ця методика виключає параметризовані типи, коли вихідний код переводиться на байт-код Java Virtual Machine (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 generics та шаблони C ++ мають ряд інших відмінностей. До них належать:

  • Шаблони C ++ можуть використовувати примітивні типи, наприклад, int. Натомість Java не може і повинна використовувати Integer.
  • У Java ви можете обмежити параметри типу шаблону певним типом. Наприклад, ви можете використовувати generics для реалізації CardDeck і вказати, що параметр типу повинен поширюватися на CardGame.
  • У C ++ параметр типу може бути екземпляром, тоді як 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 і мінуси <A>), і це дійсно є базовим, а також відбувається, коли жодні дженерики не задіяні. C ++ теж робить це. Крім цього, цей код дав мені рак, оскільки він справді жахливий. Тим не менш, ти все одно зробиш це так, як у C ++, ти повинен зробити такі модифікації, звичайно, оскільки C ++ не є Java, але це не є недоліком шаблонів C ++.
годинник міста

@ clocktown ні, цього не можна робити на C ++. Жодна кількість модифікацій не дозволить цього зробити. І це є недоліком шаблонів C ++.
MigMit

Що повинен був зробити ваш Кодекс - попереджайте про різну довжину - цього не робите. У вашому коментованому прикладі він створює лише помилки через невідповідність аргументів. Це працює і в C ++. Ви можете ввести код, що семантично еквівалентний і набагато кращий, ніж цей безлад у C ++ та на Java.
годинник міста

Це робить. Аргументи точно не відповідають тому, що довжини різні. Ви не можете цього зробити на C ++.
MigMit

0

Я хотів би навести тут питання про різницю :

Основна відмінність C ++ від Java полягає в їх залежності від платформи. Хоча, C ++ - це залежна від платформи мова, Java - незалежна від платформи мова.

Наведене вище твердження є причиною того, що C ++ може надати справжні загальні типи. У той час як у Java є чітка перевірка, і тому вони не дозволяють використовувати дженерики так, як це дозволяє C ++.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.