Стирается ли стирание Java Generics в результате полного литья?

Я знаю, что когда компилятор Java компилирует универсальный тип, он выполняет стирание типа и удаляет все ссылки на универсальный из кода, и мой ArrayList<Cheesecake> становится просто ArrayList.

Вопрос, на который у меня нет четкого ответа, заключается в том, вызывает ли это замедление отсутствие этого типа (и, следовательно, обязательное приведение типов). Другими словами, если я использую стандартный компилятор Java и стандартную JVM 1.7 от Oracle:

  • Включает ли байт-код приведение типа?
  • Если да, включает ли он проверку во время выполнения, чтобы убедиться, что это правильный тип?
  • Само преобразование занимает нетривиальное количество времени?
  • Если бы я CheesecakeList свой собственный класс CheesecakeList который выглядит идентично ArrayList, просто со всеми Object обращенными к Cheesecake, я бы получил что-нибудь (опять же, только гипотетический, и меня не интересуют какие-либо побочные эффекты наличия большего двоичного файла из-за большего количества классов или дублирование в моем коде).

Меня не интересуют какие-либо другие проблемы, связанные с типом стирания, просто простой ответ от человека, который понимает JVM лучше, чем я.

Меня больше всего интересует стоимость в этом примере:

List<Cheesecake> list = new ArrayList<Cheesecake>();
for (int i = 0; i < list.size(); i++)
{
   // I know this gets converted to ((Cheesecake)list.get(i)).at(me)
   list.get(i).eat(me); 
}

Это бросок внутри цикла дорогой и/или значительный по сравнению с:

CheesecakeList list = new CheesecakeList();
for (int i = 0; i < list.size(); i++)
{
   //where the return of list.get() is a Cheesecake, therefore there is no casting.
   list.get(i).eat(me); 
}

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Это в основном вопрос академического любопытства. Я действительно сомневаюсь, что есть какие-то существенные проблемы с производительностью приведения типов, и устранение необходимости в них не было бы даже одной из пяти лучших вещей, которые я бы сделал, если бы обнаружил ошибку в производительности в своем коде. Если вы читаете это, потому что у вас действительно есть проблема с производительностью, сделайте себе одолжение, запустите профилировщик и выясните, где находится горлышко бутылки. Если вы действительно верите в приведение типов, только тогда вы должны попытаться как-то оптимизировать его.

Ответ 1

Рассказ: Да, есть проверка типа. Здесь доказательство -

Учитывая следующие классы:

// Let define a generic class.
public class Cell<T> {
  public void set(T t) { this.t = t; }
  public T get() { return t; }

  private T t;
}

public class A {

  static Cell<String> cell = new Cell<String>();  // Instantiate it.

  public static void main(String[] args) {
    // Now, let use it.
    cell.set("a");
    String s = cell.get(); 
    System.out.println(s);
  }  
}

Байт-код, который A.main() скомпилирован (декомпилирован через javap -c A.class), выглядит следующим образом:

public static void main(java.lang.String[]);
Code:
   0: getstatic     #20                 // Field cell:Lp2/Cell;
   3: ldc           #22                 // String a
   5: invokevirtual #24                 // Method p2/Cell.set:(Ljava/lang/Object;)V
   8: getstatic     #20                 // Field cell:Lp2/Cell;
  11: invokevirtual #30                 // Method p2/Cell.get:()Ljava/lang/Object;
  14: checkcast     #34                 // class java/lang/String
  17: astore_1      
  18: getstatic     #36                 // Field java/lang/System.out:Ljava/io/PrintStream;
  21: aload_1       
  22: invokevirtual #42                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  25: return        
}

Как вы можете в offset 14, результат cell.get() проверяется на тип, чтобы проверить, действительно ли это строка:

 14: checkcast     #34                 // class java/lang/String

Это наносит некоторый штраф за выполнение. Однако, поскольку JVM довольно хорошо оптимизирован, эффект этого, вероятно, будет минимальным.

Более длинная история:

Как вы собираетесь реализовать такой класс CheesecakeList? не будет ли этот класс определять массив для хранения элементов? имейте в виду, что каждое присваивание массиву берет скрытую версию typecheck. Таким образом, вы не получите столько, сколько сможете (хотя, скорее всего, ваша программа будет выполнять больше операций чтения, чем операции записи, поэтому массив Cheesecak[] даст вам что-то).

Нижняя строка: преждевременно не оптимизируйте.

Последний комментарий. Люди часто думают, что стирание стилей означает, что Cell<String> скомпилирован в Cell<Object>. Это неправда. Стирание применяется только к определению общего класса/метода. Он не применяется к сайтам использования этих классов/методов.

Другими словами, класс Cell<T> компилируется так, как если бы он был написан как Cell<Object>. Если T имеет верхнюю границу (скажем, Number), то она компилируется в Cell<Number>. В других местах кода обычно переменные/параметры, тип которых является экземпляром класса Cell (например, Cell<String> myCell), стирание не применяется. Тот факт, что myCell имеет тип Cell<String>, хранится в файле классов. Это позволяет компилятору правильно проверить программу.

Ответ 2

Проверка типов выполняется во время компиляции. Если вы это сделаете:

List<Cheesecake> list = new ArrayList<Cheesecake>();

тогда общие типы могут быть проверены во время компиляции. Это стирает:

List list = new ArrayList();

который ничем не отличается от любого другого up-cast (например, Object o = new Integer(5);).

Ответ 3

Включен ли в байт-код листинг типа?

Да. Типовое стирание кратко описано в этот учебник

Если да, включает ли он проверку времени выполнения, чтобы проверить, соответствует ли ее правильный тип?

Нет. Безопасность типа гарантируется во время компиляции, пока у вас нет никаких предупреждений. Однако существуют некоторые мостовые методы, используемые, как описано в учебнике выше

[ Изменить: Для пояснения "Нет" означает, что нет дополнительной проверки времени выполнения, которая вставляется компилятором из-за использования дженериков. Любая проверка времени выполнения во время кастинга все еще выполняется]

Делает ли само преобразование нетривиальное количество времени?

Под преобразованием вы подразумеваете литье? Если да, то это будет так, даже если у вас не было дженериков

Создал бы свой собственный класс CheesecakeList, который выглядит как ArrayList,...

В общем, это не простой вопрос для ответа и, конечно, не без учета других факторов. Прежде всего, мы больше не говорим о дженериках. Вы спрашиваете, будет ли специализированный класс быстрее, чем ArrayList. Гипотетически да, имея специальный класс, который избегал бы кастинга, должен быть быстрее. Но реальный вопрос: , если вы беспокоитесь об этом?

Чтобы ответить на последний вопрос, вы должны быть конкретными. Каждый случай отличается. Например, сколько объектов мы говорим? Миллионы? Повторные звонки? Является ли дополнительное время заметным в производительности программы? Вы приурочили его? Как мы ожидаем, что наши данные будут расширяться в будущем? etc и т.д.

Также не забывайте, что компиляторы развиваются. В будущей версии может быть оптимизация, которая автоматически устраняет проблему с производительностью (если вы можете жить с ней, то есть), а если вы придерживаетесь базы кода, которая становится бесполезной, она, вероятно, останется с проектом до тех пор, пока она живет.

Итак, мой совет: a) убедитесь, что у вас есть проблема в этой конкретной области, иначе не пытайтесь превзойти компилятор или сам язык. b) время вашего кода, установите некоторые стандарты того, что вы хотите достичь (например, сколько задержек приемлемо, что мне нужно получить и т.д.), и в) действовать только в том случае, если вы уверены, что находитесь на правильном пути. Никто не может вам этого рассказать.

Надеюсь, что поможет

Ответ 4

Если вы взяли исходный код JDK для ArrayList, и вы клонировали его в CheesecakeList и изменили все вхождения Object в ArrayList на Cheesecake, результат (если вы сделали это правильно) был бы классом, который вы могли бы использовать в своем приложении, и требовать меньше checkcast, используя ArrayList<Cheesecake>.

Тем не менее, операция checkcast чрезвычайно быстро, особенно если вы проверяете объект с именованным классом, а не подкласс. Это присутствие, вероятно, более важно для незначительной оптимизации оптимизации JITC, чем для фактической стоимости исполнения.