Рубрики
Без рубрики

Универсальная реализация Java и C#

Введение Дженерики очень эффективны и широко используются как в программировании на Java, так и на C#… С тегами java, csharp, dotnet.

Дженерики очень мощные и широко используются как в программировании на 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){
        GenericTest t = 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 для трех разных типов.

GenericTest intObj = new GenericTest();
GenericTest doubleObj = new GenericTest();
GenericTest strObj = new GenericTest();

Когда этот код выполняется, .Net Среда выполнения динамически создаст три конкретных типа на основе исходного определения универсального типа GenericTest:

  1. GenericTest : T заменен на int . Этот тип будет использоваться для создания всех новых объектов типа GenericTest
  2. GenericTest : T заменен на double . Этот тип будет использоваться для создания всех новых объектов типа GenericTest
  3. 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() {
    ArrayList al = 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 static  T getObject(String json) {
    ObjectMapper m = new ObjectMapper();
    return (T)m.readValue(json, T.class);
}

Чтобы приведенный выше код работал, метод GetObject должен получить тип.

public static  T 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# способен обеспечить повышение производительности и большую поддержку операций во время выполнения.

Оригинал: “https://dev.to/pathiknd/generics-implementation-of-java-and-c-31b4”