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

C# для разработчика Java: Дженерики

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

В своем путешествии в мир C# я хотел поговорить о дженериках. Дженерики существуют как в языках Java, так и в C#, но их реализация очень отличается. Цель этого сообщения в блоге – объяснить различия и сходства между ними. TL;DR Дженерики Java – это ложь, дженерики C# – нет.

Дженерики Java – это ложь

Дженерики были введены в Java 5. До этого вам приходилось манипулировать Объектами и приводить их к нужному типу:

List apples = new ArrayList();
apples.add(new Apple());
// This is really a list of objects, so the cast is required
Apple firstApple = (Apple) apples.get(0);

В предыдущем фрагменте вы должны отметить, что нет абсолютно ничего, что мешало бы вам добавить Banana в список и привести к сбою программы во время выполнения. Кроме того, этот код по-прежнему действителен в последней версии Java (которая, как мы говорим, является Java 11). Мягко говоря, такой подход не очень безопасен и не использует систему типов так сильно, как мы могли бы ожидать.

Начиная с Java 5, можно использовать универсальную версию класса List |/:

List apples = new ArrayList();
apples.add(new Apple());
// The cast is not required any more thanks to the generics
Apple firstApple = apples.get(0);

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

/**
 * Filter objects of the given type
 */
 List filterObjectsOfType(List objects) {
  List filteredObjects = new ArrayList<>();
  for (Object o : objects) {
      if (o instanceof T) {
          filteredObjects.add((T) o);
      }
  }
  return filteredObjects;
}

Действительно, тип T стирается во время выполнения, и операция instanceof не может быть выполнена. Вот почему объект класса часто передается в качестве аргумента метода:

/**
 * Filter objects of the given type
 */
 List filterObjectsOfType(List objects, Class clazz) {
  List filteredObjects = new ArrayList<>();
  for (Object o : objects) {
      if (clazz.isInstance(o)) {
          filteredObjects.add((T) o);
      }
  }
  return filteredObjects;
}

Этот метод может быть вызван следующим образом:

List stringsOnly = filterObjectsOfType(Arrays.asList("hello", 2), String.class);
// stringsOnly contains only "hello"

Дженерики C# – это функция среды выполнения

C# generics, с другой стороны, – это совершенно другой зверь. Действительно, реальный тип сохраняется во время выполнения, и этот тип можно использовать для написания такого рода кода:

/**
 * Filter objects of the given type
 */
public IEnumerable FilterObjectsOfType(IEnumerable objects)
{
    List filteredObjects = new List();
    foreach (var obj in objects)
    {
        if (obj is T)
        {
            filteredObjects.Add((T) obj);
        }
    }
    return filteredObjects;
}

Этот метод может быть вызван следующим образом:

IEnumerable stringsOnly = FilterObjectsOfType(new List(new object[] { "hello", 2 }));
// stringsOnly contains only "hello"

Вывод

В Java дженерики используются для написания типобезопасного кода, но эта функция ограничена, и иногда код может быть неудобным из-за механизма стирания типов. В C# это функция среды выполнения, и ее использование намного проще.

PS: Я понимаю, что код, который я написал во фрагментах, на самом деле не идиоматичен, но я хотел иметь Java и C # код настолько похож, насколько это возможно, чтобы иметь возможность сосредоточиться только на использовании дженериков.

Оригинал: “https://dev.to/rnowif/c-for-the-java-developer-generics-1c72”