Дженерики очень мощные и широко используются как в программировании на Java, так и на C#, особенно в фреймворках и библиотеках.
Дженерики в первую очередь обеспечивают безопасность типов и повышенную производительность за счет исключения необходимости приведения переменных.
Если вы использовали дженерики C#, а затем используете дженерики в Java – или наоборот, вы заметите, что дженерики Java и C# выглядят очень похоже. Но они очень отличаются с точки зрения того, как они реализованы и, как следствие, какими возможностями они обладают. Мы увидим это в остальной части этого поста.
Поддержка дженериков на языке требуется как во время компиляции, так и во время и время выполнения . Давайте воспользуемся примером, чтобы лучше понять это.
- Библиотека common-lib объявляет универсальный тип, как показано ниже. Эта библиотека создается и публикуется, которая затем используется в других программах.
public class GenericTest{ private T _ref; }
- Приложение с именем demo-app использует common-lib
public class App{ public static void main(String[] args){ GenericTestt = new GenericTest (); GenericTest s = new GenericTest (); //s = t; //allowed? type safety? } }
common-lib и demo-app являются физически разными артефактами. Когда demo-app компилируется, компилятор должен знать, что GenericTest является универсальным типом, поэтому с ним следует обращаться по-другому. Поэтому, когда common-lib компилируется, скомпилированный вывод должен содержать информацию об общем типе. Это позволит компилятору обеспечить безопасность типов в demo-app во время компиляции – как Java, так и C# гарантируют безопасность типов во время компиляции.
Отражение поддерживается как Java, так и C#. API-интерфейсы отражения позволяют получать доступ к информации о типах во время выполнения и создавать новые объекты, вызывать методы для объекта и т.д. во время выполнения. Для поддержки всех этих операций с универсальными типами также требуется поддержка универсальных типов во время выполнения.
Сборник
Java использует концепцию Стирания типов для поддержки дженериков в Java. Посредством стирания типов компилятор Java преобразует все ссылки на общие типы в не-общие типы во время компиляции. Подход стирания типов использовался для обеспечения обратной совместимости, чтобы не-универсальные типы могли быть переданы в более новый код, написанный с использованием универсальных типов.
Давайте разберемся на примере. Ниже приведен простой универсальный класс.
public class GenericTest{ private T _ref; public > boolean isEqual(T1 obj){ return obj.compareTo(this._ref) == 0 ? true : false; } }
Когда этот класс компилируется, параметры универсального типа удаляются и заменяются не-универсальными эквивалентами. Ниже приведен сгенерированный байт-код, показанный Средством просмотра байт-кода :
Посмотрите разницу между исходным кодом и скомпилированной версией:
//source code public class GenericTest//compiled code - GenericTest became just GenericTest public class generics/example/application/GenericTest //source code private T _ref; //compiled code - T was replaced with Object private java.lang.Object _ref; //source code public > boolean isEqual(T1 obj) //compiled code - T1 became Comparable because //of constraint that T1 should be subtype of Comparable public isEqual(java.lang.Comparable arg0)
Скомпилированный Java-код не содержит никаких следов универсальных типов. Все сопоставляется с необработанным типом Java.
Одним из побочных эффектов стирания типа является то, что GenericTest и Общий тест одинаковы после удаления типа компилятором, поэтому невозможно иметь оба в одном пакете.
Время выполнения
На уровне JVM универсальных типов не существует. Как объяснялось в предыдущем разделе, компилятор Java удаляет все следы универсальных типов, поэтому JVM не нужно делать ничего другого для обработки универсальных типов.
Сборник
Ниже приведен эквивалентный код C# приведенного выше примера на Java:
public class GenericTest{ private T _ref; public bool IsEqual (T1 obj) where T1 : IComparable { return obj.CompareTo(this._ref) == 0 ? true : false; } }
Когда приведенный выше код компилируется, компилятор C# сохраняет информацию об общем типе, которая используется .Net Среда выполнения для поддержки дженериков.
Заглянуть в скомпилированную библиотеку с помощью IL Disassembler :
IL-код метода isEqual (такой же, как байтовый код Java) – см. подчеркнутые разделы:
Время выполнения
.Net Среда выполнения (CLR) использует информацию об общем типе в скомпилированном коде для создания конкретных типов во время выполнения. Давайте разберемся на примере.
Следующий код создает три объекта GenericTest для трех разных типов.
GenericTestintObj = new GenericTest (); GenericTest doubleObj = new GenericTest (); GenericTest strObj = new GenericTest ();
Когда этот код выполняется, .Net Среда выполнения динамически создаст три конкретных типа на основе исходного определения универсального типа GenericTest:
- GenericTest : T заменен на int . Этот тип будет использоваться для создания всех новых объектов типа GenericTest
- GenericTest : T заменен на double . Этот тип будет использоваться для создания всех новых объектов типа GenericTest
- GenericTest : T заменен на |/System. Объект . Этот тип будет использоваться для создания всех новых объектов любого ссылочного типа, таких как GenericTest, GenericTest, GenericTest и т.д.
.Net Среда выполнения создает новый тип для каждого типа primitive \ value, что обеспечивает как безопасность типов, так и повышение производительности за счет исключения операций боксирования.
Для ссылочного типа существует только тип и .Net Механизм защиты типов во время выполнения обеспечивает безопасность типов.
Из-за характера реализации существует несколько областей различий между Java и C#:
- Поддержка примитивных типов
- Дженерики Java не поддерживают примитивные типы – потому что удаление типов в этом случае не может работать.
- C# поддерживает примитивные типы (или типы значений в C#) в общих чертах, что дает два преимущества (a) безопасность типов и (b) повышение производительности за счет устранения необходимости упаковки и распаковки – это достигается с помощью .Net Динамическое создание конкретного типа среды выполнения.
- Есть открытый пункт JEP 218: Дженерики над примитивными типами для поддержки примитивных типов в дженериках Java
- Результатом вышеуказанного ограничения в Java является ряд функциональных интерфейсов, таких как Int Function, LongFunction и т.д. Если примитивные типы могут поддерживаться универсальными, может быть достаточно только одного интерфейса:
public interface Function{ R apply(T value); }
- Удаление типов вставляет приведения везде, где это необходимо для обеспечения безопасности типов, но это приведет к увеличению затрат на производительность, а не к повышению производительности за счет исключения приведений с помощью дженериков. Напр.,
public void test() { ArrayListal = new ArrayList (); al.add(new MyClass()); //Compiler would add cast MyClass m = al.get(0); //source //MyClass m = (MyClass)al.get(0) //compiled //this will be fine as al.get(0) anyway returns Object. Object o = al.get(0); }
- Операции во время выполнения – Если вам нужно выполнить проверку типов во время выполнения для T (T экземпляра IEnumerable), отразить общие типы или выполнить операции типа new() , это либо невозможно в Java, либо вам придется использовать обходные пути. Давайте посмотрим на пример.
Давайте напишем функцию, которая десериализует строку Json в объект, используя общие параметры.
Ниже приведен код C # для того же самого, который будет работать:
public static T getObject(string json) { return (T)JsonConvert.DeserializeObject(json, typeof(T)); } // usage // MyClass m = getObject ("json string");
Но то же самое не может работать в Java, как T.class не будет компилироваться.
public staticT getObject(String json) { ObjectMapper m = new ObjectMapper(); return (T)m.readValue(json, T.class); }
Чтобы приведенный выше код работал, метод GetObject должен получить тип.
public staticT getObject(String json, Type t) { ObjectMapper m = new ObjectMapper(); return (T)m.readValue(json, t.getClass()); } //usage // MyClass m = getObject ("json string", MyClass.class);
В конце концов, и Java, и C# обеспечивают безопасность типов во время компиляции, используя разные подходы. Благодаря поддержке на уровне среды выполнения C# способен обеспечить повышение производительности и большую поддержку операций во время выполнения.
- Внутренние компоненты дженериков C#
- Если вы хотите перейти к подробным сведениям об уровне памяти, прочитайте .Net Дженерики под капотом
- Очень подробное сравнение характеристик – Сравнение Java и C# Дженерики – веб-журнал Джонатана Прайора
- Ядро Java SE 9 для нетерпеливых
Оригинал: “https://dev.to/pathiknd/generics-implementation-of-java-and-c-31b4”