




В Википедии есть отличные статьи, в которых сравниваются шаблоны Дженерики Java / C# и Дженерики Java / C++. основная статья о дженериках кажется немного загроможденным, но в нем есть полезная информация.
Сам Андерс Хейлсберг описал различия здесь «Дженерики в C#, Java и C++».
Самая большая жалоба - стирание шрифтов. В этом случае универсальные шаблоны не применяются во время выполнения. Вот ссылка на некоторые документы Sun по этой теме..
Generics are implemented by type erasure: generic type information is present only at compile time, after which it is erased by the compiler.
Шаблоны C++ на самом деле намного мощнее, чем их аналоги на C# и Java, поскольку они оцениваются во время компиляции и поддерживают специализацию. Это позволяет использовать метапрограммирование шаблонов и делает компилятор C++ эквивалентным машине Тьюринга (т.е. в процессе компиляции вы можете вычислить все, что можно вычислить с помощью машины Тьюринга).
C++ редко использует терминологию «дженериков». Вместо этого используется более точное слово «шаблоны». Шаблоны описывает один техника для достижения универсального дизайна.
Шаблоны C++ сильно отличаются от того, что реализуют как C#, так и Java, по двум основным причинам. Первая причина заключается в том, что шаблоны C++ допускают не только аргументы типа времени компиляции, но также аргументы константного значения времени компиляции: шаблоны могут быть заданы как целые числа или даже сигнатуры функций. Это означает, что во время компиляции вы можете делать довольно забавные вещи, например расчеты:
template <unsigned int N>
struct product {
static unsigned int const VALUE = N * product<N - 1>::VALUE;
};
template <>
struct product<1> {
static unsigned int const VALUE = 1;
};
// Usage:
unsigned int const p5 = product<5>::VALUE;
Этот код также использует другую отличительную особенность шаблонов C++, а именно специализацию шаблонов. Код определяет один шаблон класса product с одним аргументом значения. Он также определяет специализацию для этого шаблона, которая используется всякий раз, когда аргумент оценивается как 1. Это позволяет мне определять рекурсию по определениям шаблонов. Я считаю, что это было впервые обнаружено Андрей Александреску.
Специализация шаблонов важна для C++, поскольку она допускает структурные различия в структурах данных. Шаблоны в целом - это средство унификации интерфейса между типами. Однако, хотя это желательно, все типы не могут обрабатываться одинаково внутри реализации. Это учитывается в шаблонах C++. Это почти то же самое различие, которое ООП делает между интерфейсом и реализацией с переопределением виртуальных методов.
Шаблоны C++ необходимы для его парадигмы алгоритмического программирования. Например, почти все алгоритмы для контейнеров определены как функции, которые принимают тип контейнера как тип шаблона и обрабатывают их единообразно. На самом деле, это не совсем так: C++ работает не с контейнерами, а с диапазоны, которые определяются двумя итераторами, указывающими на начало и конец контейнера. Таким образом, весь контент ограничен итераторами: begin
Использование итераторов вместо контейнеров полезно, поскольку позволяет работать с частями контейнера, а не с целым.
Еще одна отличительная черта C++ - возможность частичная специализация для шаблонов классов. Это в некоторой степени связано с сопоставлением с образцом аргументов в Haskell и других функциональных языках. Например, давайте рассмотрим класс, в котором хранятся элементы:
template <typename T>
class Store { … }; // (1)
Это работает для любого типа элемента. Но предположим, что мы можем хранить указатели более эффективно, чем другие типы, применяя специальный трюк. Мы можем сделать это, специализируясь на частично для всех типов указателей:
template <typename T>
class Store<T*> { … }; // (2)
Теперь, когда мы создаем экземпляр шаблона контейнера для одного типа, используется соответствующее определение:
Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.
Иногда мне хотелось, чтобы функция дженериков в .net позволяла использовать в качестве ключей не только типы. Если бы массивы типов значений были частью Framework (я удивлен, что они в каком-то смысле не так, учитывая необходимость взаимодействия со старыми API-интерфейсами, которые встраивают массивы фиксированного размера в структуры), было бы полезно объявить класс, содержащий несколько отдельных элементов, а затем массив значений типа, размер которого является универсальным параметром. Как бы то ни было, самое близкое, что можно сделать, - это иметь объект класса, который содержит отдельные элементы, а затем также содержит ссылку на отдельный объект, содержащий массив.
@supercat Если вы взаимодействуете с устаревшим API, идея состоит в том, чтобы использовать маршаллинг (который можно аннотировать с помощью атрибутов). В любом случае в CLR нет массивов фиксированного размера, поэтому наличие аргументов шаблона, не являющихся типом, здесь не поможет.
Я думаю, что меня озадачивает то, что казалось бы, иметь массивы значений-типов фиксированного размера не должно было быть сложной задачей, и это позволило бы упорядочить многие типы данных по ссылке, а не по значению. В то время как маршал по значению может быть полезен в случаях, которые действительно не могут быть обработаны каким-либо другим способом, я считаю, что маршал по ссылке лучше почти во всех случаях, когда его можно использовать, поэтому позволяя таким случаям включать структуры с фиксированными -размерные массивы показались бы полезной функцией.
Кстати, другая ситуация, когда общие параметры, не являющиеся типами, были бы полезны, была бы с типами данных, которые представляют измеряемые количества. Можно включить размерную информацию в экземпляры, которые представляют количества, но наличие такой информации в типе позволит указать, что коллекция должна содержать объекты, представляющие конкретную размерную единицу.
В Java универсальные шаблоны работают только на уровне компилятора, поэтому вы получаете:
a = new ArrayList<String>()
a.getClass() => ArrayList
Обратите внимание, что тип «a» - это список массивов, а не список строк. Таким образом, тип списка бананов будет равен () списку обезьян.
Так сказать.
Продолжение моей предыдущей публикации.
Шаблоны - одна из основных причин того, почему C++ так ужасно терпит неудачу в intellisense, независимо от используемой IDE. Из-за специализации шаблонов среда IDE никогда не может быть уверена, существует данный элемент или нет. Учитывать:
template <typename T>
struct X {
void foo() { }
};
template <>
struct X<int> { };
typedef int my_int_type;
X<my_int_type> a;
a.|
Теперь курсор находится в указанной позиции, и IDE чертовски сложно сказать в этот момент, есть ли и что у членов a. Для других языков синтаксический анализ будет простым, но для C++ требуется предварительная оценка.
Становится хуже. Что, если бы my_int_type также был определен внутри шаблона класса? Теперь его тип будет зависеть от аргумента другого типа. А здесь даже компиляторы терпят неудачу.
template <typename T>
struct Y {
typedef T my_type;
};
X<Y<int>::my_type> b;
Поразмыслив, программист может сделать вывод, что этот код совпадает с приведенным выше: Y<int>::my_type преобразуется в int, поэтому b должен быть того же типа, что и a, верно?
Неправильный. В момент, когда компилятор пытается разрешить этот оператор, он еще не знает Y<int>::my_type! Следовательно, он не знает, что это тип. Это может быть что-то еще, например функция-член или поле. Это может вызвать двусмысленность (но не в данном случае), поэтому компилятор не работает. Мы должны явно указать, что мы ссылаемся на имя типа:
X<typename Y<int>::my_type> b;
Теперь код компилируется. Чтобы увидеть, как возникают неоднозначности в этой ситуации, рассмотрим следующий код:
Y<int>::my_type(123);
Этот оператор кода совершенно допустим и указывает C++ выполнить вызов функции Y<int>::my_type. Однако, если my_type не функция, а тип, этот оператор все равно будет действителен и выполнит специальное приведение (приведение в стиле функции), которое часто является вызовом конструктора. Компилятор не может сказать, что мы имеем в виду, поэтому мы должны устранить неоднозначность здесь.
Я вполне согласен. Хотя есть надежда. Система автозаполнения и компилятор C++ должны очень тесно взаимодействовать. Я почти уверен, что в Visual Studio никогда не будет такой функции, но что-то может произойти в Eclipse / CDT или какой-либо другой IDE на основе GCC. НАДЕЯТЬСЯ ! :)
И Java, и C# представили дженерики после своего первого языкового выпуска. Однако есть различия в том, как основные библиотеки изменились, когда были введены дженерики. Дженерики C# - это не просто волшебство компилятора, и поэтому было невозможно генерировать существующих библиотечных классов без нарушения обратной совместимости.
Например, в Java существующий Фреймворк коллекций был полностью обобщенный. В Java нет универсальной и устаревшей неуниверсальной версии классов коллекций. В некотором смысле это намного чище - если вам нужно использовать коллекцию на C#, действительно очень мало причин использовать неуниверсальную версию, но эти устаревшие классы остаются на месте, загромождая ландшафт.
Еще одно заметное отличие - это классы Enum в Java и C#. Java Enum имеет это несколько извилистое определение:
// java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
(см. очень ясный объяснение того, почему Анжелики Лангер, это так. По сути, это означает, что Java может предоставить типобезопасный доступ из строки к ее значению Enum:
// Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");
Сравните это с версией C#:
// Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");
Поскольку Enum уже существовал в C# до того, как в язык были введены универсальные шаблоны, определение не могло быть изменено без нарушения существующего кода. Таким образом, как и коллекции, он остается в основных библиотеках в этом унаследованном состоянии.
Даже дженерики C# - это не просто волшебство компилятора, компилятор может творить чудеса для создания существующей библиотеки. Нет причин, по которым им нужно переименовать ArrayList в List<T> и поместить его в новое пространство имен. Дело в том, что если в исходном коде был класс, обозначенный как ArrayList<T>, он станет другим именем класса, сгенерированным компилятором, в коде IL, поэтому конфликтов имен возникнуть не может.
Я добавлю свой голос к шуму и попытаюсь прояснить ситуацию:
List<Person> foo = new List<Person>();
и тогда компилятор не позволит вам помещать в список вещи, не являющиеся Person.
За кулисами компилятор C# просто помещает List<Person> в DLL-файл .NET, но во время выполнения JIT-компилятор создает новый набор кода, как если бы вы написали специальный класс списка только для содержания людей - что-то вроде ListOfPerson.
Преимущество этого в том, что он делает это очень быстро. Здесь нет приведения или каких-либо других вещей, и поскольку dll содержит информацию о том, что это список Person, другой код, который просматривает его позже с помощью отражения, может сказать, что он содержит объекты Person (так что вы получаете intellisense и так далее).
Обратной стороной этого является то, что старый код C# 1.0 и 1.1 (до того, как они добавили дженерики) не понимает эти новые List<something>, поэтому вам нужно вручную преобразовать вещи обратно в простой старый List, чтобы взаимодействовать с ними. Это не такая уж большая проблема, потому что двоичный код C# 2.0 не имеет обратной совместимости. Это произойдет только в том случае, если вы обновите старый код C# 1.0 / 1.1 до C# 2.0.
ArrayList<Person> foo = new ArrayList<Person>();
На первый взгляд это выглядит так же, как и есть. Компилятор также не позволит вам помещать в список вещи, не относящиеся к Person.
Разница в том, что происходит за кулисами. В отличие от C#, Java не создает специальный ListOfPerson - он просто использует старый добрый ArrayList, который всегда был в Java. Когда вы достаете вещи из массива, обычный танец кастинга Person p = (Person)foo.get(1); все еще должен быть выполнен. Компилятор избавляет вас от нажатия клавиш, но скорость нажатия / приведения все равно остается такой же, как и всегда.
Когда люди упоминают «Type Erasure», они говорят именно об этом. Компилятор вставляет для вас приведение типов, а затем «стирает» тот факт, что это должен быть список Person, а не только Object.
Преимущество этого подхода в том, что старый код, который не понимает дженериков, не должен заботиться. Он по-прежнему имеет дело с тем же старым ArrayList, что и всегда. Это более важно в мире Java, потому что они хотели поддерживать компиляцию кода с использованием Java 5 с универсальными шаблонами и запускать его на старой версии 1.4 или предыдущих JVM, о чем Microsoft намеренно решила не беспокоиться.
Обратной стороной является снижение скорости, о котором я упоминал ранее, а также потому, что в файлах .class нет псевдокласса ListOfPerson или чего-либо подобного, кода, который просматривает его позже (с отражением или если вы вытащите его из другого коллекция, в которой он был преобразован в Object или так далее) никоим образом не может сказать, что это должен быть список, содержащий только Person, а не просто какой-либо другой список массивов.
std::list<Person>* foo = new std::list<Person>();
Похоже на дженерики C# и Java, и он будет делать то, что, по вашему мнению, должен делать, но за кулисами происходят разные вещи.
Он имеет больше всего общего с дженериками C# в том, что он создает специальный pseudo-classes, а не просто отбрасывает информацию о типе, как это делает java, но это совершенно другой котел с рыбой.
И C#, и Java производят вывод, предназначенный для виртуальных машин. Если вы напишете код, в котором есть класс Person, в обоих случаях некоторая информация о классе Person будет помещена в файл .dll или .class, и JVM / CLR будет что-то делать с этим.
C++ производит необработанный двоичный код x86. Все является объектом нет, и нет базовой виртуальной машины, которая должна знать о классе Person. Нет упаковки или распаковки, и функции не обязательно должны принадлежать классам или чему-то еще.
Из-за этого компилятор C++ не накладывает никаких ограничений на то, что вы можете делать с шаблонами - практически любой код, который вы можете написать вручную, вы можете получить шаблоны, которые будут написаны для вас. Самый очевидный пример - добавление вещей:
В C# и Java системе универсальных шаблонов необходимо знать, какие методы доступны для класса, и передавать их виртуальной машине. Единственный способ сказать это - либо жестко запрограммировать реальный класс, либо использовать интерфейсы. Например:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
Этот код не будет компилироваться на C# или Java, потому что он не знает, что тип T на самом деле предоставляет метод с именем Name (). Вы должны сказать это - в C# вот так:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
И затем вы должны убедиться, что вещи, которые вы передаете в addNames, реализуют интерфейс IHasName и так далее. Синтаксис java отличается (<T extends IHasName>), но имеет те же проблемы.
«Классический» случай этой проблемы - попытка написать функцию, которая делает это
string addNames<T>( T first, T second ) { return first + second; }
На самом деле вы не можете написать этот код, потому что нет способов объявить интерфейс с методом + в нем. Ты облажался.
C++ не страдает ни одной из этих проблем. Компилятор не заботится о передаче типов любой виртуальной машине - если оба ваших объекта имеют функцию .Name (), он будет компилироваться. Если они этого не сделают, этого не произойдет. Простой.
Итак, вот оно :-)
+1 за подробное описание самого необходимого
Сгенерированные псевдоклассы для типов ссылок в C# используют одну и ту же реализацию, поэтому у вас не будет точно ListOfPeople. Проверить blogs.msdn.com/ericlippert/archive/2009/07/30/…
Нет, вы можете нет скомпилировать код Java 5 с использованием дженериков и запустить его на старых виртуальных машинах 1.4 (по крайней мере, Sun JDK не реализует этого. Некоторые сторонние инструменты это делают). Вы можете использовать ранее скомпилированные файлы JAR 1.4 из 1.5 / 1.6 код.
+1 за указание на этот «Классический вариант этой проблемы - попытка написать функцию, которая делает это» ... это заставляет меня задуматься о том, как работают дженерики в C# и почему ему нужно знать интерфейс ... и т. д.
Термин псевдоклассы вводит в заблуждение. Это такие же новые типы, как и «непсевдоклассы». В остальном хороший ответ :)
Я возражаю против утверждения, что вы не можете написать int addNames<T>( T first, T second ) { return first + second; } на C#. Универсальный тип можно ограничить классом, а не интерфейсом, и есть способ объявить класс с оператором + в нем.
@Mashmagar: Хотя вы технически правы, ограничение универсального метода классом, чтобы вы могли вызывать метод +, не особенно полезно, поскольку большую часть времени вы хотите вызывать его для числовых типов или строк, все из которых запечатанный в .NET :-(
Один из лучших ответов без грязи. Просто по делу, не жертвуя деталями.
Когда вы говорите List
std::list<Person*> foo;. A big benefit of std containers is that they support RAII. See Примеры.
@AlexanderMalakhov это специально не идиоматично. Целью было не ознакомить с идиомами C++, а продемонстрировать, как один и тот же фрагмент кода по-разному обрабатывается на разных языках. Этой цели было бы труднее достичь, чем больше код выглядел бы по-разному.
-1 Потому что ты ошибаешься насчет Java-кастинга. Нет накладных расходов, потому что приведение не нужно проверять. Это неявно, как и приведение к более широкому типу (также бесплатное действие), потому что компилятор может проверить, что это безопасно, ничего не требует проверки во время выполнения. Очень удивлен, что никто другой не указал на это для такого популярного ответа.
То, что вы говорите о C++, вводит в заблуждение - это подразумевает требование определить, какие интерфейсы потребуются параметру типа, а не просто разрешить любую параметризацию, которая предоставляет все методы, требуемые для реализации универсального класса, является фундаментальным ограничением интерпретируемого языка, когда на самом деле это дизайнерское решение Java и C# (что дает ряд преимуществ). Я также не уверен, что вы знаете, что такое бокс, поскольку он вообще не имеет отношения к афиу, он связан с автооберткой примитивов в классах, но примитивы в любом случае не могут быть параметризацией, по крайней мере, в Java.
@EliasVasylenko - См. docs.oracle.com/javase/tutorial/java/generics/erasure.html - там четко указано «При необходимости вставьте типовые отливки для сохранения типовой безопасности».. Да, будут некоторые случаи, когда приведение типов не обязательно, но во многих случаях они будут (и, следовательно, повлекут за собой любые накладные расходы на производительность, связанные с приведением типа). Там написано «для параметризованных типов не создаются новые классы; следовательно, обобщения не несут дополнительных затрат времени выполнения». - это правда, но вводит в заблуждение. дженерики не добавляет накладных расходов, но приведение вставленного типа будет (если только приведение каким-то образом не стало нулевым)
@EliasVasylenko - re C++ - вы прочитали ответ полностью? Последний абзац пытается объяснить, что C++ будет компилироваться на основе сопоставления имен методов, а не требовать какой-либо параметризации интерфейса в соответствии с C# / java ...
@OrionEdwards - Мне было известно, что приведения добавлены, я просто предположил, что, поскольку компилятор обычно гарантирует им успешное выполнение, они не будут сопряжены с расходами, но, подумав, я бы не рассматривал загрязнение неуниверсальным кодом. Виноват.
@OrionEdwards - Можно было бы реализовать шаблоны стиля C++ для Java в качестве этапа предварительной компиляции (ну, по крайней мере, в той мере, в какой это относится к обсуждению). Просто создайте новый файл класса для каждого экземпляра и замените параметры типа для данной параметризации. Нет необходимости заранее удостовериться, что интерфейс предоставлен, если методы, необходимые для класса шаблона, доступны с параметризацией, сгенерированный класс будет компилироваться. Мы не могли скомпилировать такой шаблон в библиотеку, исходный код должен быть доступен при компиляции любого экземпляра, но это также верно для C++ afaiu?
@OrionEdwards: Признаюсь, мне тоже не нравится ваш фрагмент на C++. Просто по умолчанию в C++ вы так не работаете. По умолчанию вы объявляете автоматические объекты, а не указатели. Даже если вы может делаете их похожими, имхо, вам действительно не следует этого делать, потому что Java, C# и C++ - разные языки. Один мог сделает Haskell или Python похожими на C++, но будет ли это хорошей идеей? Я помню возмущение на каком-то собрании, где разработчики Java жаловались на отсутствие new в некоторых объявлениях C++, а код, подобный вашему, вносит вклад в эту путаницу.
@phresnel, конечно, это не современный идиоматический C++, и я бы не стал писать такое приложение обязательно. Но я не пишу приложение, я пытаюсь сравнить 3 языка программирования, поэтому чем ближе я смогу сделать 3 примера друг другу, тем лучше.
@OrionEdwards: (не пытаясь показаться грубым; если это так, извините) Imho, сравнение PL должно включать код, который показывает, как что-то является выполняется на каком-то языке.
@phresnel Я согласен в принципе, но если бы я написал этот фрагмент на идиоматическом C++, он был бы гораздо менее понятен разработчикам C# / Java, и, следовательно, (я считаю), объяснил бы разницу хуже. Согласимся не соглашаться по этому поводу :-)
Lulz по котировке Ты облажался.
Мне было легче понять ваше объяснение, чем интервью с Андерсом Хейлсбергом. Молодец!!!
Исправьте эту большую ошибку - универсальные типы Java имеют такое же снижение скорости, как и C# (т.е. нет). Единственное, что делает в Java приведение типов, - это проверять, что может произойти присвоение, иначе выдает ошибку. Дженерики подталкивают эту проверку ко времени компиляции, а не времени выполнения. Это отличается от приведений C++, которые включают изменение указателя во время выполнения (это следствие истинного множественного наследования).
Обновлено: извините, я только что прочитал ваше оправдание. Однако с практической точки зрения я не могу представить себе ситуацию, когда компилятор должен был бы это делать (опять же, без множественного наследования). Возможно, только при использовании старого кода Java от 1.5 и новее? Тем не менее, я думаю, было бы хорошо уточнить, что дженерики действительно предлагают существенный / ситуативный бонус к скорости. Это основная причина, по которой даже были добавлены дженерики.
@Philip - вы говорите, что дженерики подталкивают проверку типа к времени компиляции в java, подразумевая, что никакие приведения не будут выполняться во время выполнения. документация оракула указывает, что компилятор будет вставлять приведение типов (которые предположительно будут выполняться во время выполнения). Выполнение этих проверок типов будет иметь некоторое влияние на производительность, но незначительное.
На Какие уже есть много хороших ответов, но позвольте мне дать немного другую точку зрения и добавить Почему.
Как уже объяснялось, основное отличие заключается в стирание типа, то есть в том, что компилятор Java стирает общие типы, и они не попадают в сгенерированный байт-код. Однако возникает вопрос: зачем кому-то это делать? В этом нет смысла! Или нет?
Ну а какая альтернатива? Если вы не реализуете дженерики на языке, где делать вы их реализуете? И ответ: в виртуальной машине. Что нарушает обратную совместимость.
С другой стороны, стирание типов позволяет смешивать общие клиенты с неуниверсальными библиотеками. Другими словами: код, который был скомпилирован на Java 5, все еще можно развернуть на Java 1.4.
Однако Microsoft решила нарушить обратную совместимость для дженериков. Это, почему .NET Generics «лучше», чем Java Generics.
Конечно, Сан не идиоты и не трусы. Причина, по которой они «струсили», заключалась в том, что Java была значительно старше и распространена, чем .NET, когда они представили дженерики. (Они были представлены примерно одновременно в обоих мирах.) Нарушение обратной совместимости было бы огромной болью.
Иными словами: в Java Generics являются частью Язык (что означает, что они применяют Только к Java, а не к другим языкам), в .NET они являются частью Виртуальная машина (что означает, что они применяются к языкам все, а не только C# и Visual Basic.NET).
Сравните это с функциями .NET, такими как LINQ, лямбда-выражения, вывод типа локальной переменной, анонимные типы и деревья выражений: все это функции язык. Вот почему между VB.NET и C# есть тонкие различия: если бы эти функции были частью виртуальной машины, они были бы одинаковыми в языках все. Но среда CLR не изменилась: в .NET 3.5 SP1 она такая же, как и в .NET 2.0. Вы можете скомпилировать программу C#, которая использует LINQ, с компилятором .NET 3.5 и по-прежнему запускать ее на .NET 2.0, при условии, что вы не используете какие-либо библиотеки .NET 3.5. Тогда нет будет работать с дженериками и .NET 1.1, но бы будет работать с Java и Java 1.4.
LINQ - это прежде всего функция библиотеки (хотя C# и VB также добавили синтаксический сахар вместе с ней). Любой язык, ориентированный на 2.0 CLR, может в полной мере использовать LINQ, просто загрузив сборку System.Core.
Да, извините, я должен был быть более ясным. LINQ. Я имел в виду синтаксис запроса, а не стандартные монадические операторы запросов, методы расширения LINQ или интерфейс IQueryable. Очевидно, вы можете использовать их на любом языке .NET.
Думаю другой вариант на Яву. Даже Oracle не хочет нарушать обратную совместимость, они все же могут сделать некоторые уловки с компилятором, чтобы избежать стирания информации о типе. Например, ArrayList<T> может быть выдан как новый тип с внутренним именем со (скрытым) статическим полем Class<T>. Пока новая версия универсальной библиотеки развернута с кодом размером 1,5+ байта, она сможет работать на 1,4-JVM.
Похоже, среди других очень интересных предложений есть одно о доработке дженериков и нарушении обратной совместимости:
Currently, generics are implemented using erasure, which means that the generic type information is not available at runtime, which makes some kind of code hard to write. Generics were implemented this way to support backwards compatibility with older non-generic code. Reified generics would make the generic type information available at runtime, which would break legacy non-generic code. However, Neal Gafter has proposed making types reifiable only if specified, so as to not break backward compatibility.
11 месяцев спустя, но я думаю, что этот вопрос готов для некоторых вещей Java Wildcard.
Это синтаксическая особенность Java. Предположим, у вас есть метод:
public <T> void Foo(Collection<T> thing)
И предположим, что вам не нужно ссылаться на тип T в теле метода. Вы объявляете имя T, а затем используете его только один раз, так почему вы должны придумывать для него имя? Вместо этого вы можете написать:
public void Foo(Collection<?> thing)
Знак вопроса просит компилятор сделать вид, что вы объявили параметр обычного именованного типа, который должен появляться в этом месте только один раз.
С подстановочными знаками вы ничего не можете сделать, чего нельзя сделать и с параметром именованного типа (именно так это всегда делается в C++ и C#).
Еще 11 месяцев позже ... Есть вещи, которые вы можете сделать с помощью подстановочных знаков Java, чего нельзя сделать с параметрами именованного типа. Вы можете сделать это в Java: class Foo<T extends List<?>> и использовать Foo<StringList>, но в C# вы должны добавить этот дополнительный параметр типа: class Foo<T, T2> where T : IList<T2> и использовать неуклюжий Foo<StringList, String>.
NB: У меня недостаточно аргументов для комментариев, поэтому не стесняйтесь переместить это как комментарий к соответствующему ответу.
Вопреки распространенному мнению, откуда я никогда не понимаю, .net реализовал настоящие дженерики без нарушения обратной совместимости, и они приложили для этого явные усилия. Вам не нужно менять свой неуниверсальный код .net 1.0 на универсальный код только для использования в .net 2.0. И общие, и неуниверсальные списки по-прежнему доступны в .Net framework 2.0 даже до версии 4.0, только по причине обратной совместимости. Поэтому старые коды, которые все еще использовали неуниверсальный ArrayList, по-прежнему будут работать и будут использовать тот же класс ArrayList, что и раньше. Обратная совместимость кода всегда поддерживается с 1.0 до настоящего времени ... Так что даже в .net 4.0 вам все равно придется использовать любой не-общий класс из 1.0 BCL, если вы решите это сделать.
Поэтому я не думаю, что Java должна нарушать обратную совместимость для поддержки истинных дженериков.
Люди говорят не о такой обратной совместимости. Идея заключается в обратной совместимости для время выполнения: код, написанный с использованием универсальных шаблонов в .NET 2.0 не можешь, можно запускать в более старых версиях .NET framework / CLR. Точно так же, если бы Java представила «настоящие» универсальные шаблоны, более новый Java-код не смог бы работать на старых JVM (потому что это требует внесения изменений в байт-код).
Это .net, а не дженерики. Всегда требует перекомпиляции для конкретной версии CLR. Есть совместимость байт-кода, есть совместимость кода. Кроме того, я отвечал конкретно относительно необходимости преобразовать старый код, который использовал старый список, для использования нового списка дженериков, что совсем не соответствует действительности.
Я думаю, что люди говорят о прямая совместимость. Т.е. Код .net 2.0 для работы в .net 1.1, который сломается, потому что среда выполнения 1.1 ничего не знает о "псевдоклассе" 2.0. Разве тогда не должно быть, что «java не реализует истинный универсальный тип, потому что они хотят поддерживать прямую совместимость»? (а не назад)
Проблемы совместимости неуловимы. Я не думаю, что проблема заключалась в том, что добавление «настоящих» дженериков к Java повлияло бы на любые программы, использующие более старые версии Java, но скорее тот код, который использовал «новые улучшенные» дженерики, столкнулся бы с трудностями при замене таких объектов на более старый код, который ничего не знал о новых типах. Предположим, например, что у программы есть ArrayList<Foo>, который она хочет передать более старому методу, который должен заполнить ArrayList экземплярами Foo. Если ArrayList<foo> - это не ArrayList, как это сделать?
мне очень нравится это интервью. он дает понять парням, не связанным с C#, вроде меня, что происходит с дженериками C#.